


































@@ -89,6 +89,7 @@ const mocks = vi.hoisted(() => ({
8989(runs: Map<string, import("./subagent-registry.types.js").SubagentRunRecord>) => new Map(runs),
9090),
9191captureSubagentCompletionReply: vi.fn(async () => "final completion reply"),
92+cleanupBrowserSessionsForLifecycleEnd: vi.fn(async () => {}),
9293runSubagentAnnounceFlow: vi.fn(async () => true),
9394getGlobalHookRunner: vi.fn(() => null),
9495ensureRuntimePluginsLoaded: vi.fn(),
@@ -189,6 +190,7 @@ describe("subagent registry seam flow", () => {
189190},
190191});
191192mocks.getGlobalHookRunner.mockReturnValue(null);
193+mocks.cleanupBrowserSessionsForLifecycleEnd.mockResolvedValue(undefined);
192194mocks.resolveContextEngine.mockResolvedValue({
193195onSubagentEnded: mocks.onSubagentEnded,
194196});
@@ -206,7 +208,7 @@ describe("subagent registry seam flow", () => {
206208mod.__testing.setDepsForTest({
207209callGateway: mocks.callGateway,
208210captureSubagentCompletionReply: mocks.captureSubagentCompletionReply,
209-cleanupBrowserSessionsForLifecycleEnd: async () => {},
211+cleanupBrowserSessionsForLifecycleEnd: mocks.cleanupBrowserSessionsForLifecycleEnd,
210212onAgentEvent: mocks.onAgentEvent,
211213persistSubagentRunsToDisk: mocks.persistSubagentRunsToDisk,
212214resolveAgentTimeoutMs: mocks.resolveAgentTimeoutMs,
@@ -558,6 +560,64 @@ describe("subagent registry seam flow", () => {
558560expect(mocks.persistSubagentRunsToDisk).toHaveBeenCalledTimes(6);
559561});
560562563+it("continues completion announce cleanup when lifecycle cleanup fails", async () => {
564+mocks.cleanupBrowserSessionsForLifecycleEnd.mockRejectedValueOnce(
565+new Error("browser cleanup unavailable"),
566+);
567+568+mod.registerSubagentRun({
569+runId: "run-cleanup-warning",
570+childSessionKey: "agent:main:subagent:child",
571+requesterSessionKey: "agent:main:main",
572+requesterDisplayKey: "main",
573+task: "finish despite cleanup warning",
574+cleanup: "keep",
575+});
576+577+await waitForFast(() => {
578+expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
579+});
580+581+expect(mocks.cleanupBrowserSessionsForLifecycleEnd).toHaveBeenCalledTimes(1);
582+expectRecordFields(
583+getMockCallArg(mocks.runSubagentAnnounceFlow, 0, 0, "completion announce"),
584+{
585+childSessionKey: "agent:main:subagent:child",
586+childRunId: "run-cleanup-warning",
587+task: "finish despite cleanup warning",
588+},
589+"completion announce params",
590+);
591+592+const run = mod
593+.listSubagentRunsForRequester("agent:main:main")
594+.find((entry) => entry.runId === "run-cleanup-warning");
595+expect(run?.cleanupCompletedAt).toBeTypeOf("number");
596+});
597+598+it("retries completion hooks before resuming ended cleanup", async () => {
599+mocks.ensureRuntimePluginsLoaded.mockRejectedValueOnce(new Error("runtime unavailable"));
600+601+mod.registerSubagentRun({
602+runId: "run-hook-retry",
603+childSessionKey: "agent:main:subagent:child",
604+requesterSessionKey: "agent:main:main",
605+requesterDisplayKey: "main",
606+task: "finish after hook retry",
607+cleanup: "keep",
608+expectsCompletionMessage: false,
609+});
610+611+await waitForFast(() => {
612+expect(mocks.ensureRuntimePluginsLoaded).toHaveBeenCalledTimes(2);
613+const run = mod
614+.listSubagentRunsForRequester("agent:main:main")
615+.find((entry) => entry.runId === "run-hook-retry");
616+expect(run?.cleanupCompletedAt).toBeTypeOf("number");
617+});
618+expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
619+});
620+561621it("suppresses stale timeout announces when the same child run later finishes successfully", async () => {
562622mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
563623if (request.method === "agent.wait") {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。