fix(background-agent): retain completed tasks via archive fallback after cleanup
MessageAbortedError/worker shutdown could race with scheduled removeTask, leaving background_output's manager.getTask returning 'Task not found' even though the task had completed cleanly. Fix: add completedTaskArchive (max 500, FIFO eviction). On removeTask, archive non-running/pending tasks with sessionId. getTask falls back to archive on active-map miss. addTask clears stale archive entries on re-registration. Fixes #3895
This commit is contained in:
@@ -5991,6 +5991,33 @@ describe("BackgroundManager regression fixes - resume and aborted notification",
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
|
||||
test("should keep completed task retrievable after scheduled removal", () => {
|
||||
//#given
|
||||
const manager = createBackgroundManager()
|
||||
const task: BackgroundTask = {
|
||||
id: "task-archive-regression",
|
||||
sessionId: "session-archive-regression",
|
||||
parentSessionId: "parent-session",
|
||||
parentMessageId: "msg-1",
|
||||
description: "archive regression",
|
||||
prompt: "test",
|
||||
agent: "explore",
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
}
|
||||
getTaskMap(manager).set(task.id, task)
|
||||
|
||||
//#when
|
||||
;(cast<{ removeTask: (task: BackgroundTask) => void }>(manager)).removeTask(task)
|
||||
|
||||
//#then
|
||||
expect(getTaskMap(manager).has(task.id)).toBe(false)
|
||||
expect(manager.getTask(task.id)?.sessionId).toBe(task.sessionId)
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
})
|
||||
|
||||
describe("BackgroundManager - tool permission spread order", () => {
|
||||
|
||||
@@ -188,6 +188,7 @@ export interface SubagentSessionCreatedEvent {
|
||||
export type OnSubagentSessionCreated = (event: SubagentSessionCreatedEvent) => Promise<void>
|
||||
|
||||
const MAX_TASK_REMOVAL_RESCHEDULES = 6
|
||||
const MAX_COMPLETED_TASK_ARCHIVE_SIZE = 500
|
||||
|
||||
export interface BackgroundManagerConfig {
|
||||
pluginContext: PluginInput
|
||||
@@ -222,6 +223,7 @@ export class BackgroundManager {
|
||||
private queuesByKey: Map<string, QueueItem[]> = new Map()
|
||||
private processingKeys: Set<string> = new Set()
|
||||
private completionTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
||||
private completedTaskArchive: Map<string, BackgroundTask> = new Map()
|
||||
private completedTaskSummaries: Map<string, BackgroundTaskNotificationTask[]> = new Map()
|
||||
private idleDeferralTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
||||
private notificationQueueByParent: Map<string, Promise<void>> = new Map()
|
||||
@@ -347,6 +349,7 @@ export class BackgroundManager {
|
||||
}
|
||||
|
||||
private addTask(task: BackgroundTask): void {
|
||||
this.completedTaskArchive.delete(task.id)
|
||||
this.tasks.set(task.id, task)
|
||||
if (!task.parentSessionId) {
|
||||
return
|
||||
@@ -358,10 +361,30 @@ export class BackgroundManager {
|
||||
}
|
||||
|
||||
private removeTask(task: BackgroundTask): void {
|
||||
this.archiveCompletedTask(task)
|
||||
this.tasks.delete(task.id)
|
||||
this.removeTaskFromParentIndex(task.id, task.parentSessionId)
|
||||
}
|
||||
|
||||
private archiveCompletedTask(task: BackgroundTask): void {
|
||||
if (!task.sessionId) {
|
||||
return
|
||||
}
|
||||
if (task.status === "running" || task.status === "pending") {
|
||||
return
|
||||
}
|
||||
|
||||
this.completedTaskArchive.set(task.id, task)
|
||||
if (this.completedTaskArchive.size <= MAX_COMPLETED_TASK_ARCHIVE_SIZE) {
|
||||
return
|
||||
}
|
||||
|
||||
const oldestTaskID = this.completedTaskArchive.keys().next().value
|
||||
if (typeof oldestTaskID === "string") {
|
||||
this.completedTaskArchive.delete(oldestTaskID)
|
||||
}
|
||||
}
|
||||
|
||||
private updateTaskParent(task: BackgroundTask, parentSessionID: string): void {
|
||||
if (task.parentSessionId === parentSessionID) {
|
||||
return
|
||||
@@ -830,7 +853,7 @@ The fallback retry session is now created and can be inspected directly.
|
||||
}
|
||||
|
||||
getTask(id: string): BackgroundTask | undefined {
|
||||
return this.tasks.get(id)
|
||||
return this.tasks.get(id) ?? this.completedTaskArchive.get(id)
|
||||
}
|
||||
|
||||
getTasksByParentSession(sessionID: string): BackgroundTask[] {
|
||||
|
||||
Reference in New Issue
Block a user