Merge pull request #1500 from code-yeongyu/fix/background-abort-tui-crash
fix(background-agent): gracefully handle aborted parent session in notifyParentSession
This commit is contained in:
commit
11b883da6c
@ -875,6 +875,90 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
||||
})
|
||||
})
|
||||
|
||||
describe("BackgroundManager.notifyParentSession - aborted parent", () => {
|
||||
test("should skip notification when parent session is aborted", async () => {
|
||||
//#given
|
||||
let promptCalled = false
|
||||
const client = {
|
||||
session: {
|
||||
prompt: async () => {
|
||||
promptCalled = true
|
||||
return {}
|
||||
},
|
||||
abort: async () => ({}),
|
||||
messages: async () => {
|
||||
const error = new Error("User aborted")
|
||||
error.name = "MessageAbortedError"
|
||||
throw error
|
||||
},
|
||||
},
|
||||
}
|
||||
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
|
||||
const task: BackgroundTask = {
|
||||
id: "task-aborted-parent",
|
||||
sessionID: "session-child",
|
||||
parentSessionID: "session-parent",
|
||||
parentMessageID: "msg-parent",
|
||||
description: "task aborted parent",
|
||||
prompt: "test",
|
||||
agent: "explore",
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
}
|
||||
getPendingByParent(manager).set("session-parent", new Set([task.id, "task-remaining"]))
|
||||
|
||||
//#when
|
||||
await (manager as unknown as { notifyParentSession: (task: BackgroundTask) => Promise<void> })
|
||||
.notifyParentSession(task)
|
||||
|
||||
//#then
|
||||
expect(promptCalled).toBe(false)
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
|
||||
test("should swallow aborted error from prompt", async () => {
|
||||
//#given
|
||||
let promptCalled = false
|
||||
const client = {
|
||||
session: {
|
||||
prompt: async () => {
|
||||
promptCalled = true
|
||||
const error = new Error("User aborted")
|
||||
error.name = "MessageAbortedError"
|
||||
throw error
|
||||
},
|
||||
abort: async () => ({}),
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
}
|
||||
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
|
||||
const task: BackgroundTask = {
|
||||
id: "task-aborted-prompt",
|
||||
sessionID: "session-child",
|
||||
parentSessionID: "session-parent",
|
||||
parentMessageID: "msg-parent",
|
||||
description: "task aborted prompt",
|
||||
prompt: "test",
|
||||
agent: "explore",
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
}
|
||||
getPendingByParent(manager).set("session-parent", new Set([task.id]))
|
||||
|
||||
//#when
|
||||
await (manager as unknown as { notifyParentSession: (task: BackgroundTask) => Promise<void> })
|
||||
.notifyParentSession(task)
|
||||
|
||||
//#then
|
||||
expect(promptCalled).toBe(true)
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
})
|
||||
|
||||
function buildNotificationPromptBody(
|
||||
task: BackgroundTask,
|
||||
currentMessage: CurrentMessage | null
|
||||
|
||||
@ -1123,7 +1123,14 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
if (this.isAbortedSessionError(error)) {
|
||||
log("[background-agent] Parent session aborted, skipping notification:", {
|
||||
taskId: task.id,
|
||||
parentSessionID: task.parentSessionID,
|
||||
})
|
||||
return
|
||||
}
|
||||
const messageDir = getMessageDir(task.parentSessionID)
|
||||
const currentMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
|
||||
agent = currentMessage?.agent ?? task.parentAgent
|
||||
@ -1154,6 +1161,13 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
noReply: !allComplete,
|
||||
})
|
||||
} catch (error) {
|
||||
if (this.isAbortedSessionError(error)) {
|
||||
log("[background-agent] Parent session aborted, skipping notification:", {
|
||||
taskId: task.id,
|
||||
parentSessionID: task.parentSessionID,
|
||||
})
|
||||
return
|
||||
}
|
||||
log("[background-agent] Failed to send notification:", error)
|
||||
}
|
||||
|
||||
@ -1192,6 +1206,28 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
return `${seconds}s`
|
||||
}
|
||||
|
||||
private isAbortedSessionError(error: unknown): boolean {
|
||||
const message = this.getErrorText(error)
|
||||
return message.toLowerCase().includes("aborted")
|
||||
}
|
||||
|
||||
private getErrorText(error: unknown): string {
|
||||
if (!error) return ""
|
||||
if (typeof error === "string") return error
|
||||
if (error instanceof Error) {
|
||||
return `${error.name}: ${error.message}`
|
||||
}
|
||||
if (typeof error === "object" && error !== null) {
|
||||
if ("message" in error && typeof error.message === "string") {
|
||||
return error.message
|
||||
}
|
||||
if ("name" in error && typeof error.name === "string") {
|
||||
return error.name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private hasRunningTasks(): boolean {
|
||||
for (const task of this.tasks.values()) {
|
||||
if (task.status === "running") return true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user