



























@@ -104,6 +104,41 @@ function createRunEntry(overrides: Partial<SubagentRunRecord> = {}): SubagentRun
104104};
105105}
106106107+function expectFields(value: unknown, expected: Record<string, unknown>): void {
108+expect(value).toBeTypeOf("object");
109+expect(value).not.toBeNull();
110+const record = value as Record<string, unknown>;
111+for (const [key, expectedValue] of Object.entries(expected)) {
112+expect(record[key], key).toEqual(expectedValue);
113+}
114+}
115+116+function firstCallArg(mock: ReturnType<typeof vi.fn>): Record<string, unknown> {
117+const [arg] = mock.mock.calls[0] ?? [];
118+expect(arg).toBeTypeOf("object");
119+expect(arg).not.toBeNull();
120+return arg as Record<string, unknown>;
121+}
122+123+function findCallArg(
124+mock: ReturnType<typeof vi.fn>,
125+predicate: (arg: Record<string, unknown>) => boolean,
126+): Record<string, unknown> {
127+for (const [arg] of mock.mock.calls) {
128+if (arg && typeof arg === "object" && predicate(arg as Record<string, unknown>)) {
129+return arg as Record<string, unknown>;
130+}
131+}
132+throw new Error("expected matching mock call");
133+}
134+135+function hasDeliveredTaskStatusUpdate(runId: string): boolean {
136+return taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId.mock.calls.some(([arg]) => {
137+const record = arg as { runId?: unknown; deliveryStatus?: unknown } | undefined;
138+return record?.runId === runId && record.deliveryStatus === "delivered";
139+});
140+}
141+107142function createLifecycleController({
108143 entry,
109144 runs = new Map([[entry.runId, entry]]),
@@ -168,15 +203,14 @@ describe("subagent registry lifecycle hardening", () => {
168203}),
169204).resolves.toBeUndefined();
170205171-expect(warn).toHaveBeenCalledWith(
172-"failed to finalize subagent background task state",
173-expect.objectContaining({
174-error: { name: "Error", message: "task store boom" },
175-runId: "***",
176-childSessionKey: "agent:main:…",
177-outcomeStatus: "ok",
178-}),
179-);
206+expect(warn).toHaveBeenCalledTimes(1);
207+expect(warn.mock.calls[0]?.[0]).toBe("failed to finalize subagent background task state");
208+expectFields(warn.mock.calls[0]?.[1], {
209+error: { name: "Error", message: "task store boom" },
210+runId: "***",
211+childSessionKey: "agent:main:…",
212+outcomeStatus: "ok",
213+});
180214expect(helperMocks.persistSubagentSessionTiming).toHaveBeenCalledTimes(1);
181215expect(lifecycleEventMocks.emitSessionLifecycleEvent).toHaveBeenCalledWith({
182216sessionKey: "agent:main:subagent:child",
@@ -213,15 +247,16 @@ describe("subagent registry lifecycle hardening", () => {
213247}),
214248).resolves.toBeUndefined();
215249216-expect(warn).toHaveBeenCalledWith(
250+expect(warn).toHaveBeenCalledTimes(1);
251+expect(warn.mock.calls[0]?.[0]).toBe(
217252"failed to update subagent background task delivery state",
218-expect.objectContaining({
219-error: { name: "Error", message: "delivery state boom" },
220-runId: "***",
221-childSessionKey: "agent:main:…",
222-deliveryStatus: "failed",
223-}),
224253);
254+expectFields(warn.mock.calls[0]?.[1], {
255+error: { name: "Error", message: "delivery state boom" },
256+runId: "***",
257+childSessionKey: "agent:main:…",
258+deliveryStatus: "failed",
259+});
225260expect(entry.cleanupCompletedAt).toBeTypeOf("number");
226261expect(persist).toHaveBeenCalled();
227262});
@@ -245,17 +280,14 @@ describe("subagent registry lifecycle hardening", () => {
245280}),
246281).resolves.toBeUndefined();
247282248-expect(browserLifecycleCleanupMocks.cleanupBrowserSessionsForLifecycleEnd).toHaveBeenCalledWith(
249-{
250-sessionKeys: [entry.childSessionKey],
251-onWarn: expect.any(Function),
252-},
253-);
254-expect(runSubagentAnnounceFlow).toHaveBeenCalledWith(
255-expect.objectContaining({
256-childSessionKey: entry.childSessionKey,
257-}),
283+const browserCleanupArg = firstCallArg(
284+browserLifecycleCleanupMocks.cleanupBrowserSessionsForLifecycleEnd,
258285);
286+expectFields(browserCleanupArg, { sessionKeys: [entry.childSessionKey] });
287+expect(browserCleanupArg.onWarn).toBeTypeOf("function");
288+expectFields(firstCallArg(runSubagentAnnounceFlow), {
289+childSessionKey: entry.childSessionKey,
290+});
259291});
260292261293it("skips announce delivery when completion messages are disabled", async () => {
@@ -278,19 +310,13 @@ describe("subagent registry lifecycle hardening", () => {
278310}),
279311).resolves.toBeUndefined();
280312281-expect(browserLifecycleCleanupMocks.cleanupBrowserSessionsForLifecycleEnd).toHaveBeenCalledWith(
282-{
283-sessionKeys: [entry.childSessionKey],
284-onWarn: expect.any(Function),
285-},
313+const browserCleanupArg = firstCallArg(
314+browserLifecycleCleanupMocks.cleanupBrowserSessionsForLifecycleEnd,
286315);
316+expectFields(browserCleanupArg, { sessionKeys: [entry.childSessionKey] });
317+expect(browserCleanupArg.onWarn).toBeTypeOf("function");
287318expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
288-expect(taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId).not.toHaveBeenCalledWith(
289-expect.objectContaining({
290-runId: entry.runId,
291-deliveryStatus: "delivered",
292-}),
293-);
319+expect(hasDeliveredTaskStatusUpdate(entry.runId)).toBe(false);
294320await vi.waitFor(() => expect(entry.cleanupCompletedAt).toBeTypeOf("number"));
295321expect(entry.completionAnnouncedAt).toBeUndefined();
296322});
@@ -334,12 +360,7 @@ describe("subagent registry lifecycle hardening", () => {
334360}),
335361);
336362expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
337-expect(taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId).not.toHaveBeenCalledWith(
338-expect.objectContaining({
339-runId: entry.runId,
340-deliveryStatus: "delivered",
341-}),
342-);
363+expect(hasDeliveredTaskStatusUpdate(entry.runId)).toBe(false);
343364await vi.waitFor(() => expect(runs.has(entry.runId)).toBe(false));
344365expect(entry.completionAnnouncedAt).toBeUndefined();
345366});
@@ -363,11 +384,15 @@ describe("subagent registry lifecycle hardening", () => {
363384}),
364385).resolves.toBeUndefined();
365386366-expect(bundleMcpRuntimeMocks.retireSessionMcpRuntimeForSessionKey).toHaveBeenCalledWith({
387+const retireArg = findCallArg(
388+bundleMcpRuntimeMocks.retireSessionMcpRuntimeForSessionKey,
389+(arg) => arg.reason === "subagent-run-cleanup",
390+);
391+expectFields(retireArg, {
367392sessionKey: entry.childSessionKey,
368393reason: "subagent-run-cleanup",
369-onError: expect.any(Function),
370394});
395+expect(retireArg.onError).toBeTypeOf("function");
371396});
372397373398it("keeps bundle MCP runtimes warm for persistent session-mode cleanup", async () => {
@@ -419,18 +444,12 @@ describe("subagent registry lifecycle hardening", () => {
419444elapsedMs: 2_250,
420445};
421446expect(entry.outcome).toEqual(enrichedOutcome);
422-expect(taskExecutorMocks.failTaskRunByRunId).toHaveBeenCalledWith(
423-expect.objectContaining({
424-status: "timed_out",
425-}),
426-);
427-expect(runSubagentAnnounceFlow).toHaveBeenCalledWith(
428-expect.objectContaining({
429-startedAt: 2_000,
430-endedAt: 4_250,
431-outcome: enrichedOutcome,
432-}),
433-);
447+expectFields(firstCallArg(taskExecutorMocks.failTaskRunByRunId), { status: "timed_out" });
448+expectFields(firstCallArg(runSubagentAnnounceFlow), {
449+startedAt: 2_000,
450+endedAt: 4_250,
451+outcome: enrichedOutcome,
452+});
434453expect(persist).toHaveBeenCalled();
435454});
436455@@ -521,13 +540,11 @@ describe("subagent registry lifecycle hardening", () => {
521540522541expect(captureSubagentCompletionReply).not.toHaveBeenCalled();
523542expect(entry.frozenResultText).toBeNull();
524-expect(taskExecutorMocks.failTaskRunByRunId).toHaveBeenCalledWith(
525-expect.objectContaining({
526-status: "failed",
527-error: "All models failed (2): timeout",
528-progressSummary: undefined,
529-}),
530-);
543+expectFields(firstCallArg(taskExecutorMocks.failTaskRunByRunId), {
544+status: "failed",
545+error: "All models failed (2): timeout",
546+progressSummary: undefined,
547+});
531548expect(persist).toHaveBeenCalled();
532549});
533550@@ -657,13 +674,14 @@ describe("subagent registry lifecycle hardening", () => {
657674}),
658675).resolves.toBeUndefined();
659676660-expect(warn).toHaveBeenCalledWith(
677+expect(warn).toHaveBeenCalledTimes(1);
678+expect(warn.mock.calls[0]?.[0]).toBe(
661679"failed to update subagent background task delivery state",
662-expect.objectContaining({
663-error: { name: "Error", message: "delivery status boom" },
664-deliveryStatus: "delivered",
665-}),
666680);
681+expectFields(warn.mock.calls[0]?.[1], {
682+error: { name: "Error", message: "delivery status boom" },
683+deliveryStatus: "delivered",
684+});
667685expect(emitSubagentEndedHookForRun).toHaveBeenCalledTimes(1);
668686expect(helperMocks.safeRemoveAttachmentsDir).toHaveBeenCalledTimes(1);
669687expect(entry.cleanupCompletedAt).toBeTypeOf("number");
@@ -729,16 +747,14 @@ describe("subagent registry lifecycle hardening", () => {
729747}),
730748).resolves.toBeUndefined();
731749732-expect(taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId).toHaveBeenCalledWith(
733-expect.objectContaining({
734-runId: entry.runId,
735-runtime: "subagent",
736-sessionKey: entry.childSessionKey,
737-deliveryStatus: "failed",
738-error:
739-"UNAVAILABLE: requester wake failed; direct-primary: UNAVAILABLE: requester wake failed",
740-}),
741-);
750+expectFields(firstCallArg(taskExecutorMocks.setDetachedTaskDeliveryStatusByRunId), {
751+runId: entry.runId,
752+runtime: "subagent",
753+sessionKey: entry.childSessionKey,
754+deliveryStatus: "failed",
755+error:
756+"UNAVAILABLE: requester wake failed; direct-primary: UNAVAILABLE: requester wake failed",
757+});
742758expect(entry.lastAnnounceDeliveryError).toBe(
743759"UNAVAILABLE: requester wake failed; direct-primary: UNAVAILABLE: requester wake failed",
744760);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。