




















@@ -2000,69 +2000,7 @@ describe("subagent registry seam flow", () => {
20002000expect(replacement?.endedAt).toBeUndefined();
20012001});
200220022003-it("keeps yield terminals paused when the lifecycle event also signals abort (#92448)", async () => {
2004-// sessions_yield ends the turn by aborting the run signal, so a depth-1
2005-// subagent's yield terminal can arrive carrying yielded plus aborted (or
2006-// stopReason="aborted"). The event handler must still pause the run, not
2007-// settle it `cancelled` and deliver a false notice to the requester.
2008-mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
2009-if (request.method === "agent.wait") {
2010-return { status: "pending" };
2011-}
2012-return {};
2013-});
2014-2015-const cases = [
2016-{ runId: "run-yield-stopreason-aborted", extra: { stopReason: "aborted" } },
2017-{ runId: "run-yield-aborted-flag", extra: { aborted: true } },
2018-];
2019-2020-for (const testCase of cases) {
2021-mod.registerSubagentRun({
2022-runId: testCase.runId,
2023-childSessionKey: `agent:main:subagent:${testCase.runId}`,
2024-requesterSessionKey: "agent:main:main",
2025-requesterDisplayKey: "main",
2026-task: "wait for child continuation",
2027-cleanup: "keep",
2028-});
2029-2030-const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
2031-mocks.onAgentEvent.mock.calls.length - 1
2032-] as unknown as
2033-| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
2034-| undefined;
2035-const lifecycleHandler = lastOnAgentEventCall?.[0];
2036-expect(lifecycleHandler).toBeTypeOf("function");
2037-2038-lifecycleHandler?.({
2039-runId: testCase.runId,
2040-stream: "lifecycle",
2041-data: {
2042-phase: "end",
2043-startedAt: 111,
2044-endedAt: 222,
2045-yielded: true,
2046- ...testCase.extra,
2047-},
2048-});
2049-2050-await waitForFast(() => {
2051-const run = mod
2052-.listSubagentRunsForRequester("agent:main:main")
2053-.find((entry) => entry.runId === testCase.runId);
2054-expect(run?.pauseReason).toBe("sessions_yield");
2055-expect(run?.outcome?.status).not.toBe("error");
2056-});
2057-}
2058-2059-// Paused, never killed → no farewell/cancellation notice reaches the requester.
2060-expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
2061-});
2062-2063-it("cancels a pending grace timer when a yield follows an intermediate aborted terminal (#92448)", async () => {
2064-// An earlier aborted terminal schedules a deferred kill grace timer; a
2065-// following yield must clear it, or it fires and settles the now-paused run.
2003+it("keeps CLI lifecycle-yielded subagent runs paused instead of completing cleanup", async () => {
20662004mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
20672005if (request.method === "agent.wait") {
20682006return { status: "pending" };
@@ -2071,12 +2009,13 @@ describe("subagent registry seam flow", () => {
20712009});
2072201020732011mod.registerSubagentRun({
2074-runId: "run-yield-after-pending-timeout",
2075-childSessionKey: "agent:main:subagent:pending-timeout",
2012+runId: "run-cli-lifecycle-yield-paused",
2013+childSessionKey: "agent:main:subagent:child",
20762014requesterSessionKey: "agent:main:main",
20772015requesterDisplayKey: "main",
2078-task: "wait for child continuation",
2079-cleanup: "keep",
2016+task: "pause after CLI lifecycle yield",
2017+cleanup: "delete",
2018+expectsCompletionMessage: true,
20802019});
2081202020822021const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
@@ -2087,96 +2026,30 @@ describe("subagent registry seam flow", () => {
20872026const lifecycleHandler = lastOnAgentEventCall?.[0];
20882027expect(lifecycleHandler).toBeTypeOf("function");
208920282090-// Intermediate aborted terminal → schedules the deferred kill grace timer.
20912029lifecycleHandler?.({
2092-runId: "run-yield-after-pending-timeout",
2030+runId: "run-cli-lifecycle-yield-paused",
20932031stream: "lifecycle",
2094-data: { phase: "end", startedAt: 111, endedAt: 222, aborted: true },
2095-});
2096-// Yield terminal → must pause and cancel the pending grace timer.
2097-lifecycleHandler?.({
2098-runId: "run-yield-after-pending-timeout",
2099-stream: "lifecycle",
2100-data: { phase: "end", startedAt: 111, endedAt: 333, yielded: true },
2032+data: {
2033+phase: "end",
2034+startedAt: 333,
2035+endedAt: 444,
2036+yielded: true,
2037+livenessState: "paused",
2038+stopReason: "end_turn",
2039+},
21012040});
2102204121032042await waitForFast(() => {
21042043const run = mod
21052044.listSubagentRunsForRequester("agent:main:main")
2106-.find((entry) => entry.runId === "run-yield-after-pending-timeout");
2045+.find((entry) => entry.runId === "run-cli-lifecycle-yield-paused");
2046+expect(run?.endedAt).toBe(444);
21072047expect(run?.pauseReason).toBe("sessions_yield");
2048+expect(run?.outcome).toBeUndefined();
21082049});
2109-2110-// Advancing well past the 15s grace window must not undo the pause.
2111-await vi.advanceTimersByTimeAsync(60_000);
2112-const run = mod
2113-.listSubagentRunsForRequester("agent:main:main")
2114-.find((entry) => entry.runId === "run-yield-after-pending-timeout");
2115-expect(run?.pauseReason).toBe("sessions_yield");
2116-expect(run?.outcome?.status).not.toBe("error");
2117-expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
2118-});
2119-2120-it("cancels a pending grace timer when agent.wait observes the yield after an aborted terminal (#92448)", async () => {
2121-let resolveWait: (value: {
2122-status: "ok";
2123-startedAt: number;
2124-endedAt: number;
2125-yielded: true;
2126-}) => void = () => {};
2127-const waitResult = new Promise<{
2128-status: "ok";
2129-startedAt: number;
2130-endedAt: number;
2131-yielded: true;
2132-}>((resolve) => {
2133-resolveWait = resolve;
2134-});
2135-mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
2136-if (request.method === "agent.wait") {
2137-return waitResult;
2138-}
2139-return {};
2140-});
2141-2142-mod.registerSubagentRun({
2143-runId: "run-wait-yield-after-pending-timeout",
2144-childSessionKey: "agent:main:subagent:pending-wait-timeout",
2145-requesterSessionKey: "agent:main:main",
2146-requesterDisplayKey: "main",
2147-task: "wait for child continuation through wait",
2148-cleanup: "keep",
2149-});
2150-2151-const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
2152-mocks.onAgentEvent.mock.calls.length - 1
2153-] as unknown as
2154-| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
2155-| undefined;
2156-const lifecycleHandler = lastOnAgentEventCall?.[0];
2157-expect(lifecycleHandler).toBeTypeOf("function");
2158-2159-lifecycleHandler?.({
2160-runId: "run-wait-yield-after-pending-timeout",
2161-stream: "lifecycle",
2162-data: { phase: "end", startedAt: 111, endedAt: 222, aborted: true },
2163-});
2164-resolveWait({ status: "ok", startedAt: 111, endedAt: 333, yielded: true });
2165-2166-await waitForFast(() => {
2167-const run = mod
2168-.listSubagentRunsForRequester("agent:main:main")
2169-.find((entry) => entry.runId === "run-wait-yield-after-pending-timeout");
2170-expect(run?.pauseReason).toBe("sessions_yield");
2171-});
2172-2173-await vi.advanceTimersByTimeAsync(60_000);
2174-const run = mod
2175-.listSubagentRunsForRequester("agent:main:main")
2176-.find((entry) => entry.runId === "run-wait-yield-after-pending-timeout");
2177-expect(run?.pauseReason).toBe("sessions_yield");
2178-expect(run?.outcome?.status).not.toBe("timeout");
21792050expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
2051+expect(mocks.cleanupBrowserSessionsForLifecycleEnd).not.toHaveBeenCalled();
2052+expect(mod.countPendingDescendantRuns("agent:main:main")).toBe(1);
21802053});
2181205421822055it("announces blocked agent.wait snapshots as errors instead of success", async () => {
@@ -2777,101 +2650,6 @@ describe("subagent registry seam flow", () => {
27772650expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
27782651});
277926522780-it("announces restart lifecycle end events as killed subagent failures", async () => {
2781-mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
2782-if (request.method === "agent.wait") {
2783-return { status: "pending" };
2784-}
2785-return {};
2786-});
2787-2788-mod.registerSubagentRun({
2789-runId: "run-restart-end",
2790-childSessionKey: "agent:main:subagent:child",
2791-requesterSessionKey: "agent:main:main",
2792-requesterDisplayKey: "main",
2793-task: "restart task",
2794-cleanup: "keep",
2795-expectsCompletionMessage: true,
2796-});
2797-2798-const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
2799-mocks.onAgentEvent.mock.calls.length - 1
2800-] as unknown as
2801-| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
2802-| undefined;
2803-const lifecycleHandler = lastOnAgentEventCall?.[0];
2804-lifecycleHandler?.({
2805-runId: "run-restart-end",
2806-stream: "lifecycle",
2807-data: {
2808-phase: "end",
2809-startedAt: 10,
2810-endedAt: 20,
2811-aborted: true,
2812-stopReason: "restart",
2813-},
2814-});
2815-2816-await waitForFast(() => {
2817-const run = mod
2818-.listSubagentRunsForRequester("agent:main:main")
2819-.find((entry) => entry.runId === "run-restart-end");
2820-expect(run?.endedReason).toBe("subagent-killed");
2821-expect(run?.outcome?.status).toBe("error");
2822-expect(run?.cleanupCompletedAt).toBeTypeOf("number");
2823-expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
2824-});
2825-});
2826-2827-it("announces restart lifecycle error events as killed subagent failures", async () => {
2828-mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
2829-if (request.method === "agent.wait") {
2830-return { status: "pending" };
2831-}
2832-return {};
2833-});
2834-2835-mod.registerSubagentRun({
2836-runId: "run-restart-error",
2837-childSessionKey: "agent:main:subagent:child",
2838-requesterSessionKey: "agent:main:main",
2839-requesterDisplayKey: "main",
2840-task: "restart error task",
2841-cleanup: "keep",
2842-expectsCompletionMessage: true,
2843-});
2844-2845-const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
2846-mocks.onAgentEvent.mock.calls.length - 1
2847-] as unknown as
2848-| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
2849-| undefined;
2850-const lifecycleHandler = lastOnAgentEventCall?.[0];
2851-lifecycleHandler?.({
2852-runId: "run-restart-error",
2853-stream: "lifecycle",
2854-data: {
2855-phase: "error",
2856-startedAt: 10,
2857-endedAt: 20,
2858-error: "ACP turn failed before completion",
2859-aborted: true,
2860-stopReason: "restart",
2861-},
2862-});
2863-2864-await waitForFast(() => {
2865-const run = mod
2866-.listSubagentRunsForRequester("agent:main:main")
2867-.find((entry) => entry.runId === "run-restart-error");
2868-expect(run?.endedReason).toBe("subagent-killed");
2869-expect(run?.outcome?.status).toBe("error");
2870-expect(run?.cleanupCompletedAt).toBeTypeOf("number");
2871-expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
2872-});
2873-});
2874-28752653it("resumes ended cleanup when lifecycle killed completion rejects before cleanup", async () => {
28762654mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
28772655if (request.method === "agent.wait") {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。