

























@@ -9,6 +9,10 @@ const taskExecutorMocks = vi.hoisted(() => ({
99setDetachedTaskDeliveryStatusByRunId: vi.fn(),
1010}));
111112+const gatewayMocks = vi.hoisted(() => ({
13+callGateway: vi.fn(async () => ({})),
14+}));
15+1216const helperMocks = vi.hoisted(() => ({
1317persistSubagentSessionTiming: vi.fn(async () => {}),
1418safeRemoveAttachmentsDir: vi.fn(async () => {}),
@@ -117,6 +121,7 @@ function createLifecycleController({
117121emitSubagentEndedHookForRun: vi.fn(async () => {}),
118122notifyContextEngineSubagentEnded: vi.fn(async () => {}),
119123resumeSubagentRun: vi.fn(),
124+callGateway: gatewayMocks.callGateway,
120125captureSubagentCompletionReply: vi.fn(async () => "final completion reply"),
121126runSubagentAnnounceFlow: vi.fn(async () => true),
122127warn: vi.fn(),
@@ -127,6 +132,11 @@ function createLifecycleController({
127132describe("subagent registry lifecycle hardening", () => {
128133beforeEach(() => {
129134vi.clearAllMocks();
135+taskExecutorMocks.completeTaskRunByRunId.mockReset();
136+taskExecutorMocks.failTaskRunByRunId.mockReset();
137+taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId.mockReset();
138+gatewayMocks.callGateway.mockReset();
139+gatewayMocks.callGateway.mockResolvedValue({});
130140browserLifecycleCleanupMocks.cleanupBrowserSessionsForLifecycleEnd.mockClear();
131141bundleMcpRuntimeMocks.retireSessionMcpRuntimeForSessionKey.mockClear();
132142bundleMcpRuntimeMocks.retireSessionMcpRuntimeForSessionKey.mockResolvedValue(true);
@@ -214,7 +224,7 @@ describe("subagent registry lifecycle hardening", () => {
214224it("cleans up tracked browser sessions before subagent cleanup flow", async () => {
215225const persist = vi.fn();
216226const entry = createRunEntry({
217-expectsCompletionMessage: false,
227+expectsCompletionMessage: true,
218228});
219229const runSubagentAnnounceFlow = vi.fn(async () => true);
220230@@ -243,6 +253,92 @@ describe("subagent registry lifecycle hardening", () => {
243253);
244254});
245255256+it("skips announce delivery when completion messages are disabled", async () => {
257+const persist = vi.fn();
258+const entry = createRunEntry({
259+expectsCompletionMessage: false,
260+retainAttachmentsOnKeep: true,
261+});
262+const runSubagentAnnounceFlow = vi.fn(async () => true);
263+264+const controller = createLifecycleController({ entry, persist, runSubagentAnnounceFlow });
265+266+await expect(
267+controller.completeSubagentRun({
268+runId: entry.runId,
269+endedAt: 4_000,
270+outcome: { status: "ok" },
271+reason: SUBAGENT_ENDED_REASON_COMPLETE,
272+triggerCleanup: true,
273+}),
274+).resolves.toBeUndefined();
275+276+expect(browserLifecycleCleanupMocks.cleanupBrowserSessionsForLifecycleEnd).toHaveBeenCalledWith(
277+{
278+sessionKeys: [entry.childSessionKey],
279+onWarn: expect.any(Function),
280+},
281+);
282+expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
283+expect(taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId).not.toHaveBeenCalledWith(
284+expect.objectContaining({
285+runId: entry.runId,
286+deliveryStatus: "delivered",
287+}),
288+);
289+await vi.waitFor(() => expect(entry.cleanupCompletedAt).toBeTypeOf("number"));
290+expect(entry.completionAnnouncedAt).toBeUndefined();
291+});
292+293+it("archives delete-mode sessions when completion messages are disabled", async () => {
294+const persist = vi.fn();
295+const entry = createRunEntry({
296+cleanup: "delete",
297+expectsCompletionMessage: false,
298+spawnMode: "session",
299+});
300+const runs = new Map([[entry.runId, entry]]);
301+const runSubagentAnnounceFlow = vi.fn(async () => true);
302+303+const controller = createLifecycleController({
304+ entry,
305+ runs,
306+ persist,
307+ runSubagentAnnounceFlow,
308+});
309+310+await expect(
311+controller.completeSubagentRun({
312+runId: entry.runId,
313+endedAt: 4_000,
314+outcome: { status: "ok" },
315+reason: SUBAGENT_ENDED_REASON_COMPLETE,
316+triggerCleanup: true,
317+}),
318+).resolves.toBeUndefined();
319+320+await vi.waitFor(() =>
321+expect(gatewayMocks.callGateway).toHaveBeenCalledWith({
322+method: "sessions.delete",
323+params: {
324+key: entry.childSessionKey,
325+deleteTranscript: true,
326+emitLifecycleHooks: true,
327+},
328+timeoutMs: 10_000,
329+}),
330+);
331+expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
332+expect(taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId).not.toHaveBeenCalledWith(
333+expect.objectContaining({
334+runId: entry.runId,
335+deliveryStatus: "delivered",
336+}),
337+);
338+await vi.waitFor(() => expect(runs.has(entry.runId)).toBe(false));
339+expect(entry.completionAnnouncedAt).toBeUndefined();
340+});
341+246342it("retires bundle MCP runtimes when run-mode cleanup completes", async () => {
247343const entry = createRunEntry({
248344endedAt: 4_000,
@@ -296,7 +392,7 @@ describe("subagent registry lifecycle hardening", () => {
296392const runSubagentAnnounceFlow = vi.fn(async () => true);
297393const entry = createRunEntry({
298394startedAt: 2_000,
299-expectsCompletionMessage: false,
395+expectsCompletionMessage: true,
300396});
301397302398const controller = createLifecycleController({ entry, persist, runSubagentAnnounceFlow });
@@ -531,7 +627,7 @@ describe("subagent registry lifecycle hardening", () => {
531627const emitSubagentEndedHookForRun = vi.fn(async () => {});
532628const entry = createRunEntry({
533629endedAt: 4_000,
534-expectsCompletionMessage: false,
630+expectsCompletionMessage: true,
535631retainAttachmentsOnKeep: false,
536632});
537633taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId.mockImplementation(() => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。