

















@@ -329,6 +329,177 @@ describe("subagent registry seam flow", () => {
329329expect(run?.outcome).toBeUndefined();
330330});
331331332+it("keeps parent run active when agent.wait times out before child session settles", async () => {
333+let waitAttempts = 0;
334+let resolveSecondWait: (value: {
335+status: "ok";
336+startedAt: number;
337+endedAt: number;
338+}) => void = () => {};
339+const secondWait = new Promise<{ status: "ok"; startedAt: number; endedAt: number }>(
340+(resolve) => {
341+resolveSecondWait = resolve;
342+},
343+);
344+mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
345+if (request.method === "agent.wait") {
346+waitAttempts += 1;
347+if (waitAttempts === 1) {
348+return { status: "timeout" };
349+}
350+return secondWait;
351+}
352+return {};
353+});
354+mocks.loadSessionStore.mockReturnValue({
355+"agent:main:subagent:child": {
356+sessionId: "sess-child",
357+updatedAt: 1,
358+status: "running",
359+},
360+});
361+362+mod.registerSubagentRun({
363+runId: "run-waiter-timeout",
364+childSessionKey: "agent:main:subagent:child",
365+requesterSessionKey: "agent:main:main",
366+requesterDisplayKey: "main",
367+task: "eventually complete",
368+cleanup: "keep",
369+});
370+371+await waitForFast(() => {
372+expect(waitAttempts).toBeGreaterThanOrEqual(1);
373+});
374+await waitForFast(() => {
375+expect(waitAttempts).toBeGreaterThanOrEqual(2);
376+});
377+const activeRun = mod
378+.listSubagentRunsForRequester("agent:main:main")
379+.find((entry) => entry.runId === "run-waiter-timeout");
380+expect(activeRun?.endedAt).toBeUndefined();
381+expect(activeRun?.outcome).toBeUndefined();
382+383+resolveSecondWait({
384+status: "ok",
385+startedAt: 111,
386+endedAt: 222,
387+});
388+await waitForFast(() => {
389+const completedRun = mod
390+.listSubagentRunsForRequester("agent:main:main")
391+.find((entry) => entry.runId === "run-waiter-timeout");
392+expect(waitAttempts).toBeGreaterThanOrEqual(2);
393+expect(completedRun?.endedAt).toBe(222);
394+expectRecordFields(completedRun?.outcome, { status: "ok" }, "completed run outcome");
395+});
396+expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
397+});
398+399+it("records terminal agent.wait timeouts even before session store timing is persisted", async () => {
400+mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
401+if (request.method === "agent.wait") {
402+return {
403+status: "timeout",
404+startedAt: 111,
405+endedAt: 222,
406+stopReason: "rpc",
407+};
408+}
409+return {};
410+});
411+mocks.loadSessionStore.mockReturnValue({
412+"agent:main:subagent:child": {
413+sessionId: "sess-child",
414+updatedAt: 1,
415+status: "running",
416+},
417+});
418+419+mod.registerSubagentRun({
420+runId: "run-terminal-timeout",
421+childSessionKey: "agent:main:subagent:child",
422+requesterSessionKey: "agent:main:main",
423+requesterDisplayKey: "main",
424+task: "time out terminally",
425+cleanup: "keep",
426+});
427+428+await waitForFast(() => {
429+const run = mod
430+.listSubagentRunsForRequester("agent:main:main")
431+.find((entry) => entry.runId === "run-terminal-timeout");
432+expect(run?.endedAt).toBe(222);
433+expectRecordFields(run?.outcome, { status: "timeout" }, "terminal timeout outcome");
434+});
435+expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
436+});
437+438+it("ignores stale terminal session-store rows from older child runs", async () => {
439+let waitAttempts = 0;
440+let resolveSecondWait: (value: {
441+status: "ok";
442+startedAt: number;
443+endedAt: number;
444+}) => void = () => {};
445+const secondWait = new Promise<{ status: "ok"; startedAt: number; endedAt: number }>(
446+(resolve) => {
447+resolveSecondWait = resolve;
448+},
449+);
450+mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
451+if (request.method === "agent.wait") {
452+waitAttempts += 1;
453+if (waitAttempts === 1) {
454+return { status: "timeout" };
455+}
456+return secondWait;
457+}
458+return {};
459+});
460+const staleEndedAt = Date.parse("2026-03-24T11:59:00Z");
461+mocks.loadSessionStore.mockReturnValue({
462+"agent:main:subagent:child": {
463+sessionId: "sess-child",
464+updatedAt: staleEndedAt,
465+status: "done",
466+startedAt: staleEndedAt - 100,
467+endedAt: staleEndedAt,
468+},
469+});
470+471+mod.registerSubagentRun({
472+runId: "run-reactivated-timeout",
473+childSessionKey: "agent:main:subagent:child",
474+requesterSessionKey: "agent:main:main",
475+requesterDisplayKey: "main",
476+task: "new run after stale terminal row",
477+cleanup: "keep",
478+});
479+480+await waitForFast(() => {
481+expect(waitAttempts).toBeGreaterThanOrEqual(2);
482+});
483+const activeRun = mod
484+.listSubagentRunsForRequester("agent:main:main")
485+.find((entry) => entry.runId === "run-reactivated-timeout");
486+expect(activeRun?.endedAt).toBeUndefined();
487+expect(activeRun?.outcome).toBeUndefined();
488+expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
489+490+resolveSecondWait({
491+status: "ok",
492+startedAt: Date.parse("2026-03-24T12:00:01Z"),
493+endedAt: Date.parse("2026-03-24T12:00:02Z"),
494+});
495+await waitForFast(() => {
496+const completedRun = mod
497+.listSubagentRunsForRequester("agent:main:main")
498+.find((entry) => entry.runId === "run-reactivated-timeout");
499+expectRecordFields(completedRun?.outcome, { status: "ok" }, "reactivated run outcome");
500+});
501+});
502+332503it("keeps sessions_yield-ended subagent runs paused instead of announcing no output", async () => {
333504mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
334505if (request.method === "agent.wait") {
@@ -397,6 +568,7 @@ describe("subagent registry seam flow", () => {
397568},
398569});
399570571+vi.setSystemTime(persistedStartedAt - 1);
400572mod.registerSubagentRun({
401573runId: "run-stale-terminal",
402574childSessionKey: "agent:main:subagent:child",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。