feat(compaction): wire TaskHistory into BackgroundManager and compaction pipeline
Records task history at 6 status transitions (pending, running×2, error, cancelled, completed). Exports TaskHistory from background-agent barrel. Passes backgroundManager and sessionID through compaction hook chain.
This commit is contained in:
parent
0946a6c8f3
commit
e3924437ce
@ -1,4 +1,5 @@
|
|||||||
export * from "./types"
|
export * from "./types"
|
||||||
export { BackgroundManager, type SubagentSessionCreatedEvent, type OnSubagentSessionCreated } from "./manager"
|
export { BackgroundManager, type SubagentSessionCreatedEvent, type OnSubagentSessionCreated } from "./manager"
|
||||||
|
export { TaskHistory, type TaskHistoryEntry } from "./task-history"
|
||||||
export { ConcurrencyManager } from "./concurrency"
|
export { ConcurrencyManager } from "./concurrency"
|
||||||
export { TaskStateManager } from "./state"
|
export { TaskStateManager } from "./state"
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import type {
|
|||||||
LaunchInput,
|
LaunchInput,
|
||||||
ResumeInput,
|
ResumeInput,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
|
import { TaskHistory } from "./task-history"
|
||||||
import { log, getAgentToolRestrictions, promptWithModelSuggestionRetry } from "../../shared"
|
import { log, getAgentToolRestrictions, promptWithModelSuggestionRetry } from "../../shared"
|
||||||
import { ConcurrencyManager } from "./concurrency"
|
import { ConcurrencyManager } from "./concurrency"
|
||||||
import type { BackgroundTaskConfig, TmuxConfig } from "../../config/schema"
|
import type { BackgroundTaskConfig, TmuxConfig } from "../../config/schema"
|
||||||
@ -90,6 +91,7 @@ export class BackgroundManager {
|
|||||||
private completionTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
private completionTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
||||||
private idleDeferralTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
private idleDeferralTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
||||||
private notificationQueueByParent: Map<string, Promise<void>> = new Map()
|
private notificationQueueByParent: Map<string, Promise<void>> = new Map()
|
||||||
|
readonly taskHistory = new TaskHistory()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ctx: PluginInput,
|
ctx: PluginInput,
|
||||||
@ -144,6 +146,7 @@ export class BackgroundManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.tasks.set(task.id, task)
|
this.tasks.set(task.id, task)
|
||||||
|
this.taskHistory.record(input.parentSessionID, { id: task.id, agent: input.agent, description: input.description, status: "pending", category: input.category })
|
||||||
|
|
||||||
// Track for batched notifications immediately (pending state)
|
// Track for batched notifications immediately (pending state)
|
||||||
if (input.parentSessionID) {
|
if (input.parentSessionID) {
|
||||||
@ -291,6 +294,7 @@ export class BackgroundManager {
|
|||||||
task.concurrencyKey = concurrencyKey
|
task.concurrencyKey = concurrencyKey
|
||||||
task.concurrencyGroup = concurrencyKey
|
task.concurrencyGroup = concurrencyKey
|
||||||
|
|
||||||
|
this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt })
|
||||||
this.startPolling()
|
this.startPolling()
|
||||||
|
|
||||||
log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent })
|
log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent })
|
||||||
@ -486,6 +490,7 @@ export class BackgroundManager {
|
|||||||
this.tasks.set(task.id, task)
|
this.tasks.set(task.id, task)
|
||||||
subagentSessions.add(input.sessionID)
|
subagentSessions.add(input.sessionID)
|
||||||
this.startPolling()
|
this.startPolling()
|
||||||
|
this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID: input.sessionID, agent: input.agent || "task", description: input.description, status: "running", startedAt: task.startedAt })
|
||||||
|
|
||||||
if (input.parentSessionID) {
|
if (input.parentSessionID) {
|
||||||
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set()
|
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set()
|
||||||
@ -741,6 +746,7 @@ export class BackgroundManager {
|
|||||||
task.status = "error"
|
task.status = "error"
|
||||||
task.error = errorMessage ?? "Session error"
|
task.error = errorMessage ?? "Session error"
|
||||||
task.completedAt = new Date()
|
task.completedAt = new Date()
|
||||||
|
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
|
||||||
|
|
||||||
if (task.concurrencyKey) {
|
if (task.concurrencyKey) {
|
||||||
this.concurrencyManager.release(task.concurrencyKey)
|
this.concurrencyManager.release(task.concurrencyKey)
|
||||||
@ -951,6 +957,7 @@ export class BackgroundManager {
|
|||||||
if (reason) {
|
if (reason) {
|
||||||
task.error = reason
|
task.error = reason
|
||||||
}
|
}
|
||||||
|
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "cancelled", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
|
||||||
|
|
||||||
if (task.concurrencyKey) {
|
if (task.concurrencyKey) {
|
||||||
this.concurrencyManager.release(task.concurrencyKey)
|
this.concurrencyManager.release(task.concurrencyKey)
|
||||||
@ -1095,6 +1102,7 @@ export class BackgroundManager {
|
|||||||
// Atomically mark as completed to prevent race conditions
|
// Atomically mark as completed to prevent race conditions
|
||||||
task.status = "completed"
|
task.status = "completed"
|
||||||
task.completedAt = new Date()
|
task.completedAt = new Date()
|
||||||
|
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "completed", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
|
||||||
|
|
||||||
// Release concurrency BEFORE any async operations to prevent slot leaks
|
// Release concurrency BEFORE any async operations to prevent slot leaks
|
||||||
if (task.concurrencyKey) {
|
if (task.concurrencyKey) {
|
||||||
|
|||||||
@ -82,7 +82,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
|||||||
if (!hooks.compactionContextInjector) {
|
if (!hooks.compactionContextInjector) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
output.context.push(hooks.compactionContextInjector())
|
output.context.push(hooks.compactionContextInjector(_input.sessionID))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export function createContinuationHooks(args: {
|
|||||||
: null
|
: null
|
||||||
|
|
||||||
const compactionContextInjector = isHookEnabled("compaction-context-injector")
|
const compactionContextInjector = isHookEnabled("compaction-context-injector")
|
||||||
? safeHook("compaction-context-injector", () => createCompactionContextInjector())
|
? safeHook("compaction-context-injector", () => createCompactionContextInjector(backgroundManager))
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const compactionTodoPreserver = isHookEnabled("compaction-todo-preserver")
|
const compactionTodoPreserver = isHookEnabled("compaction-todo-preserver")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user