fix(#2732): enhance notification for failed/crashed subagent tasks

- completedTaskSummaries now includes status and error info
- notifyParentSession: noReply=false for failed tasks so parent reacts
- Batch notification distinguishes successful vs failed/cancelled tasks
- notification-template updated to show task errors
- task-poller: session-gone tests (85 new lines)
- CI: add Bun shim to PATH for legacy plugin migration tests
This commit is contained in:
YeonGyu-Kim
2026-03-27 15:48:07 +09:00
parent e22e13cd29
commit 3be26cb97f
8 changed files with 178 additions and 46 deletions
+39 -13
View File
@@ -147,7 +147,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 completedTaskSummaries: Map<string, Array<{id: string, description: string}>> = new Map()
private completedTaskSummaries: Map<string, Array<{id: string, description: string, status: string, error?: string}>> = new Map()
private idleDeferralTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
private notificationQueueByParent: Map<string, Promise<void>> = new Map()
private rootDescendantCounts: Map<string, number>
@@ -1552,6 +1552,8 @@ export class BackgroundManager {
this.completedTaskSummaries.get(task.parentSessionID)!.push({
id: task.id,
description: task.description,
status: task.status,
error: task.error,
})
// Update pending tracking and check if all tasks complete
@@ -1573,7 +1575,7 @@ export class BackgroundManager {
}
const completedTasks = allComplete
? (this.completedTaskSummaries.get(task.parentSessionID) ?? [{ id: task.id, description: task.description }])
? (this.completedTaskSummaries.get(task.parentSessionID) ?? [{ id: task.id, description: task.description, status: task.status, error: task.error }])
: []
if (allComplete) {
@@ -1591,20 +1593,40 @@ export class BackgroundManager {
let notification: string
if (allComplete) {
const completedTasksText = completedTasks
.map(t => `- \`${t.id}\`: ${t.description}`)
.join("\n")
const succeededTasks = completedTasks.filter(t => t.status === "completed")
const failedTasks = completedTasks.filter(t => t.status !== "completed")
const succeededText = succeededTasks.length > 0
? succeededTasks.map(t => `- \`${t.id}\`: ${t.description}`).join("\n")
: ""
const failedText = failedTasks.length > 0
? failedTasks.map(t => `- \`${t.id}\`: ${t.description} [${t.status.toUpperCase()}]${t.error ? ` - ${t.error}` : ""}`).join("\n")
: ""
const hasFailures = failedTasks.length > 0
const header = hasFailures
? `[ALL BACKGROUND TASKS FINISHED - ${failedTasks.length} FAILED]`
: "[ALL BACKGROUND TASKS COMPLETE]"
let body = ""
if (succeededText) {
body += `**Completed:**\n${succeededText}\n`
}
if (failedText) {
body += `\n**Failed:**\n${failedText}\n`
}
if (!body) {
body = `- \`${task.id}\`: ${task.description} [${task.status.toUpperCase()}]${task.error ? ` - ${task.error}` : ""}\n`
}
notification = `<system-reminder>
[ALL BACKGROUND TASKS COMPLETE]
${header}
**Completed:**
${completedTasksText || `- \`${task.id}\`: ${task.description}`}
${body.trim()}
Use \`background_output(task_id="<id>")\` to retrieve each result.
Use \`background_output(task_id="<id>")\` to retrieve each result.${hasFailures ? `\n\n**ACTION REQUIRED:** ${failedTasks.length} task(s) failed. Check errors above and decide whether to retry or proceed.` : ""}
</system-reminder>`
} else {
// Individual completion - silent notification
notification = `<system-reminder>
[BACKGROUND TASK ${statusText}]
**ID:** \`${task.id}\`
@@ -1612,7 +1634,7 @@ Use \`background_output(task_id="<id>")\` to retrieve each result.
**Duration:** ${duration}${errorInfo}
**${remainingCount} task${remainingCount === 1 ? "" : "s"} still in progress.** You WILL be notified when ALL complete.
Do NOT poll - continue productive work.
${statusText === "COMPLETED" ? "Do NOT poll - continue productive work." : "**ACTION REQUIRED:** This task failed. Check the error and decide whether to retry, cancel remaining tasks, or continue."}
Use \`background_output(task_id="${task.id}")\` to retrieve this result when ready.
</system-reminder>`
@@ -1675,11 +1697,14 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
resolvedModel: model,
})
const isTaskFailure = task.status === "error" || task.status === "cancelled" || task.status === "interrupt"
const shouldReply = allComplete || isTaskFailure
try {
await this.client.session.promptAsync({
path: { id: task.parentSessionID },
body: {
noReply: !allComplete,
noReply: !shouldReply,
...(agent !== undefined ? { agent } : {}),
...(model !== undefined ? { model } : {}),
...(resolvedTools ? { tools: resolvedTools } : {}),
@@ -1689,7 +1714,8 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
log("[background-agent] Sent notification to parent session:", {
taskId: task.id,
allComplete,
noReply: !allComplete,
isTaskFailure,
noReply: !shouldReply,
})
} catch (error) {
if (isAbortedSessionError(error)) {