mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix: preserve yielded CLI lifecycle state
This commit is contained in:
committed by
Vincent Koc
parent
c8c94e15ad
commit
eea350f2ff
@@ -2000,69 +2000,7 @@ describe("subagent registry seam flow", () => {
|
||||
expect(replacement?.endedAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps yield terminals paused when the lifecycle event also signals abort (#92448)", async () => {
|
||||
// sessions_yield ends the turn by aborting the run signal, so a depth-1
|
||||
// subagent's yield terminal can arrive carrying yielded plus aborted (or
|
||||
// stopReason="aborted"). The event handler must still pause the run, not
|
||||
// settle it `cancelled` and deliver a false notice to the requester.
|
||||
mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
|
||||
if (request.method === "agent.wait") {
|
||||
return { status: "pending" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const cases = [
|
||||
{ runId: "run-yield-stopreason-aborted", extra: { stopReason: "aborted" } },
|
||||
{ runId: "run-yield-aborted-flag", extra: { aborted: true } },
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
mod.registerSubagentRun({
|
||||
runId: testCase.runId,
|
||||
childSessionKey: `agent:main:subagent:${testCase.runId}`,
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
task: "wait for child continuation",
|
||||
cleanup: "keep",
|
||||
});
|
||||
|
||||
const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
|
||||
mocks.onAgentEvent.mock.calls.length - 1
|
||||
] as unknown as
|
||||
| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
|
||||
| undefined;
|
||||
const lifecycleHandler = lastOnAgentEventCall?.[0];
|
||||
expect(lifecycleHandler).toBeTypeOf("function");
|
||||
|
||||
lifecycleHandler?.({
|
||||
runId: testCase.runId,
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "end",
|
||||
startedAt: 111,
|
||||
endedAt: 222,
|
||||
yielded: true,
|
||||
...testCase.extra,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForFast(() => {
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester("agent:main:main")
|
||||
.find((entry) => entry.runId === testCase.runId);
|
||||
expect(run?.pauseReason).toBe("sessions_yield");
|
||||
expect(run?.outcome?.status).not.toBe("error");
|
||||
});
|
||||
}
|
||||
|
||||
// Paused, never killed → no farewell/cancellation notice reaches the requester.
|
||||
expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("cancels a pending grace timer when a yield follows an intermediate aborted terminal (#92448)", async () => {
|
||||
// An earlier aborted terminal schedules a deferred kill grace timer; a
|
||||
// following yield must clear it, or it fires and settles the now-paused run.
|
||||
it("keeps CLI lifecycle-yielded subagent runs paused instead of completing cleanup", async () => {
|
||||
mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
|
||||
if (request.method === "agent.wait") {
|
||||
return { status: "pending" };
|
||||
@@ -2071,81 +2009,13 @@ describe("subagent registry seam flow", () => {
|
||||
});
|
||||
|
||||
mod.registerSubagentRun({
|
||||
runId: "run-yield-after-pending-timeout",
|
||||
childSessionKey: "agent:main:subagent:pending-timeout",
|
||||
runId: "run-cli-lifecycle-yield-paused",
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
task: "wait for child continuation",
|
||||
cleanup: "keep",
|
||||
});
|
||||
|
||||
const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
|
||||
mocks.onAgentEvent.mock.calls.length - 1
|
||||
] as unknown as
|
||||
| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
|
||||
| undefined;
|
||||
const lifecycleHandler = lastOnAgentEventCall?.[0];
|
||||
expect(lifecycleHandler).toBeTypeOf("function");
|
||||
|
||||
// Intermediate aborted terminal → schedules the deferred kill grace timer.
|
||||
lifecycleHandler?.({
|
||||
runId: "run-yield-after-pending-timeout",
|
||||
stream: "lifecycle",
|
||||
data: { phase: "end", startedAt: 111, endedAt: 222, aborted: true },
|
||||
});
|
||||
// Yield terminal → must pause and cancel the pending grace timer.
|
||||
lifecycleHandler?.({
|
||||
runId: "run-yield-after-pending-timeout",
|
||||
stream: "lifecycle",
|
||||
data: { phase: "end", startedAt: 111, endedAt: 333, yielded: true },
|
||||
});
|
||||
|
||||
await waitForFast(() => {
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester("agent:main:main")
|
||||
.find((entry) => entry.runId === "run-yield-after-pending-timeout");
|
||||
expect(run?.pauseReason).toBe("sessions_yield");
|
||||
});
|
||||
|
||||
// Advancing well past the 15s grace window must not undo the pause.
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester("agent:main:main")
|
||||
.find((entry) => entry.runId === "run-yield-after-pending-timeout");
|
||||
expect(run?.pauseReason).toBe("sessions_yield");
|
||||
expect(run?.outcome?.status).not.toBe("error");
|
||||
expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("cancels a pending grace timer when agent.wait observes the yield after an aborted terminal (#92448)", async () => {
|
||||
let resolveWait: (value: {
|
||||
status: "ok";
|
||||
startedAt: number;
|
||||
endedAt: number;
|
||||
yielded: true;
|
||||
}) => void = () => {};
|
||||
const waitResult = new Promise<{
|
||||
status: "ok";
|
||||
startedAt: number;
|
||||
endedAt: number;
|
||||
yielded: true;
|
||||
}>((resolve) => {
|
||||
resolveWait = resolve;
|
||||
});
|
||||
mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
|
||||
if (request.method === "agent.wait") {
|
||||
return waitResult;
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
mod.registerSubagentRun({
|
||||
runId: "run-wait-yield-after-pending-timeout",
|
||||
childSessionKey: "agent:main:subagent:pending-wait-timeout",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
task: "wait for child continuation through wait",
|
||||
cleanup: "keep",
|
||||
task: "pause after CLI lifecycle yield",
|
||||
cleanup: "delete",
|
||||
expectsCompletionMessage: true,
|
||||
});
|
||||
|
||||
const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
|
||||
@@ -2157,26 +2027,29 @@ describe("subagent registry seam flow", () => {
|
||||
expect(lifecycleHandler).toBeTypeOf("function");
|
||||
|
||||
lifecycleHandler?.({
|
||||
runId: "run-wait-yield-after-pending-timeout",
|
||||
runId: "run-cli-lifecycle-yield-paused",
|
||||
stream: "lifecycle",
|
||||
data: { phase: "end", startedAt: 111, endedAt: 222, aborted: true },
|
||||
data: {
|
||||
phase: "end",
|
||||
startedAt: 333,
|
||||
endedAt: 444,
|
||||
yielded: true,
|
||||
livenessState: "paused",
|
||||
stopReason: "end_turn",
|
||||
},
|
||||
});
|
||||
resolveWait({ status: "ok", startedAt: 111, endedAt: 333, yielded: true });
|
||||
|
||||
await waitForFast(() => {
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester("agent:main:main")
|
||||
.find((entry) => entry.runId === "run-wait-yield-after-pending-timeout");
|
||||
.find((entry) => entry.runId === "run-cli-lifecycle-yield-paused");
|
||||
expect(run?.endedAt).toBe(444);
|
||||
expect(run?.pauseReason).toBe("sessions_yield");
|
||||
expect(run?.outcome).toBeUndefined();
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester("agent:main:main")
|
||||
.find((entry) => entry.runId === "run-wait-yield-after-pending-timeout");
|
||||
expect(run?.pauseReason).toBe("sessions_yield");
|
||||
expect(run?.outcome?.status).not.toBe("timeout");
|
||||
expect(mocks.runSubagentAnnounceFlow).not.toHaveBeenCalled();
|
||||
expect(mocks.cleanupBrowserSessionsForLifecycleEnd).not.toHaveBeenCalled();
|
||||
expect(mod.countPendingDescendantRuns("agent:main:main")).toBe(1);
|
||||
});
|
||||
|
||||
it("announces blocked agent.wait snapshots as errors instead of success", async () => {
|
||||
@@ -2777,101 +2650,6 @@ describe("subagent registry seam flow", () => {
|
||||
expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("announces restart lifecycle end events as killed subagent failures", async () => {
|
||||
mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
|
||||
if (request.method === "agent.wait") {
|
||||
return { status: "pending" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
mod.registerSubagentRun({
|
||||
runId: "run-restart-end",
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
task: "restart task",
|
||||
cleanup: "keep",
|
||||
expectsCompletionMessage: true,
|
||||
});
|
||||
|
||||
const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
|
||||
mocks.onAgentEvent.mock.calls.length - 1
|
||||
] as unknown as
|
||||
| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
|
||||
| undefined;
|
||||
const lifecycleHandler = lastOnAgentEventCall?.[0];
|
||||
lifecycleHandler?.({
|
||||
runId: "run-restart-end",
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "end",
|
||||
startedAt: 10,
|
||||
endedAt: 20,
|
||||
aborted: true,
|
||||
stopReason: "restart",
|
||||
},
|
||||
});
|
||||
|
||||
await waitForFast(() => {
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester("agent:main:main")
|
||||
.find((entry) => entry.runId === "run-restart-end");
|
||||
expect(run?.endedReason).toBe("subagent-killed");
|
||||
expect(run?.outcome?.status).toBe("error");
|
||||
expect(run?.cleanupCompletedAt).toBeTypeOf("number");
|
||||
expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("announces restart lifecycle error events as killed subagent failures", async () => {
|
||||
mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
|
||||
if (request.method === "agent.wait") {
|
||||
return { status: "pending" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
mod.registerSubagentRun({
|
||||
runId: "run-restart-error",
|
||||
childSessionKey: "agent:main:subagent:child",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
task: "restart error task",
|
||||
cleanup: "keep",
|
||||
expectsCompletionMessage: true,
|
||||
});
|
||||
|
||||
const lastOnAgentEventCall = mocks.onAgentEvent.mock.calls[
|
||||
mocks.onAgentEvent.mock.calls.length - 1
|
||||
] as unknown as
|
||||
| [(evt: { runId: string; stream: string; data: Record<string, unknown> }) => void]
|
||||
| undefined;
|
||||
const lifecycleHandler = lastOnAgentEventCall?.[0];
|
||||
lifecycleHandler?.({
|
||||
runId: "run-restart-error",
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "error",
|
||||
startedAt: 10,
|
||||
endedAt: 20,
|
||||
error: "ACP turn failed before completion",
|
||||
aborted: true,
|
||||
stopReason: "restart",
|
||||
},
|
||||
});
|
||||
|
||||
await waitForFast(() => {
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester("agent:main:main")
|
||||
.find((entry) => entry.runId === "run-restart-error");
|
||||
expect(run?.endedReason).toBe("subagent-killed");
|
||||
expect(run?.outcome?.status).toBe("error");
|
||||
expect(run?.cleanupCompletedAt).toBeTypeOf("number");
|
||||
expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("resumes ended cleanup when lifecycle killed completion rejects before cleanup", async () => {
|
||||
mocks.callGateway.mockImplementation(async (request: { method?: string }) => {
|
||||
if (request.method === "agent.wait") {
|
||||
|
||||
@@ -1,105 +1,75 @@
|
||||
// Tests CLI dispatch arguments and runtime selection for agent runner turns.
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { EmbeddedAgentRunResult } from "../../agents/embedded-agent-runner/types.js";
|
||||
import { createAgentRunRestartAbortError } from "../../agents/run-termination.js";
|
||||
|
||||
const cliDispatchMocks = vi.hoisted(() => ({
|
||||
emitAgentEvent: vi.fn(),
|
||||
runCliAgent: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/cli-runner.js", () => ({
|
||||
runCliAgent: (...args: unknown[]) => cliDispatchMocks.runCliAgent(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../../infra/agent-events.js", () => ({
|
||||
emitAgentEvent: (...args: unknown[]) => cliDispatchMocks.emitAgentEvent(...args),
|
||||
onAgentEvent: vi.fn(() => () => undefined),
|
||||
withAgentRunLifecycleGeneration: (_generation: string, run: () => unknown) => run(),
|
||||
}));
|
||||
|
||||
import {
|
||||
createCliToolSummaryTracker,
|
||||
keepCliSessionBindingOnlyWhenReused,
|
||||
runCliAgentWithLifecycle,
|
||||
} from "./agent-runner-cli-dispatch.js";
|
||||
|
||||
const cliDispatchMocks = vi.hoisted(() => ({
|
||||
emitAgentEvent: vi.fn(),
|
||||
onAgentEvent: vi.fn(() => () => undefined),
|
||||
runCliAgent: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/cli-runner.js", () => ({
|
||||
runCliAgent: cliDispatchMocks.runCliAgent,
|
||||
}));
|
||||
|
||||
vi.mock("../../infra/agent-events.js", () => ({
|
||||
emitAgentEvent: cliDispatchMocks.emitAgentEvent,
|
||||
onAgentEvent: cliDispatchMocks.onAgentEvent,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
cliDispatchMocks.onAgentEvent.mockReturnValue(() => undefined);
|
||||
});
|
||||
|
||||
describe("runCliAgentWithLifecycle", () => {
|
||||
it("keeps the captured lifecycle generation on start and terminal events", async () => {
|
||||
cliDispatchMocks.emitAgentEvent.mockClear();
|
||||
cliDispatchMocks.runCliAgent.mockResolvedValueOnce({
|
||||
it("propagates yielded CLI result state on lifecycle end events", async () => {
|
||||
cliDispatchMocks.runCliAgent.mockResolvedValue({
|
||||
payloads: [],
|
||||
meta: { durationMs: 1 },
|
||||
meta: {
|
||||
durationMs: 12,
|
||||
yielded: true,
|
||||
livenessState: "paused",
|
||||
stopReason: "end_turn",
|
||||
},
|
||||
} satisfies EmbeddedAgentRunResult);
|
||||
|
||||
await runCliAgentWithLifecycle({
|
||||
runId: "run-before-restart",
|
||||
lifecycleGeneration: "pre-restart-generation",
|
||||
runId: "run-yielded-cli",
|
||||
provider: "claude-cli",
|
||||
startedAt: 1_000,
|
||||
runParams: {
|
||||
sessionId: "session-1",
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
prompt: "hello",
|
||||
sessionId: "session-yielded-cli",
|
||||
sessionFile: "/tmp/session-yielded-cli.jsonl",
|
||||
workspaceDir: "/tmp",
|
||||
prompt: "yield now",
|
||||
provider: "claude-cli",
|
||||
model: "claude",
|
||||
thinkLevel: "off",
|
||||
timeoutMs: 1_000,
|
||||
runId: "run-before-restart",
|
||||
runId: "run-yielded-cli",
|
||||
},
|
||||
});
|
||||
|
||||
const lifecycleEvents = cliDispatchMocks.emitAgentEvent.mock.calls
|
||||
.map(([event]) => event as { stream?: string; lifecycleGeneration?: string })
|
||||
.filter((event) => event.stream === "lifecycle");
|
||||
expect(lifecycleEvents).toHaveLength(2);
|
||||
expect(
|
||||
lifecycleEvents.every((event) => event.lifecycleGeneration === "pre-restart-generation"),
|
||||
).toBe(true);
|
||||
});
|
||||
const lifecycleEndEvent = cliDispatchMocks.emitAgentEvent.mock.calls
|
||||
.map(([event]) => event as { runId: string; stream: string; data: Record<string, unknown> })
|
||||
.find(
|
||||
(event) =>
|
||||
event.runId === "run-yielded-cli" &&
|
||||
event.stream === "lifecycle" &&
|
||||
event.data.phase === "end",
|
||||
);
|
||||
|
||||
it("preserves restart ownership when the CLI resolves after cancellation", async () => {
|
||||
cliDispatchMocks.emitAgentEvent.mockClear();
|
||||
const controller = new AbortController();
|
||||
cliDispatchMocks.runCliAgent.mockImplementationOnce(async () => {
|
||||
controller.abort(createAgentRunRestartAbortError());
|
||||
return {
|
||||
payloads: [{ text: "stale result" }],
|
||||
meta: { durationMs: 1 },
|
||||
} satisfies EmbeddedAgentRunResult;
|
||||
expect(lifecycleEndEvent?.data).toMatchObject({
|
||||
phase: "end",
|
||||
startedAt: 1_000,
|
||||
yielded: true,
|
||||
livenessState: "paused",
|
||||
stopReason: "end_turn",
|
||||
});
|
||||
|
||||
await expect(
|
||||
runCliAgentWithLifecycle({
|
||||
runId: "run-restart",
|
||||
provider: "claude-cli",
|
||||
runParams: {
|
||||
sessionId: "session-1",
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
prompt: "hello",
|
||||
provider: "claude-cli",
|
||||
model: "claude",
|
||||
thinkLevel: "off",
|
||||
timeoutMs: 1_000,
|
||||
runId: "run-restart",
|
||||
abortSignal: controller.signal,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow("agent run aborted for restart");
|
||||
|
||||
const terminal = cliDispatchMocks.emitAgentEvent.mock.calls
|
||||
.map(([event]) => event as { stream?: string; data?: Record<string, unknown> })
|
||||
.find((event) => event.stream === "lifecycle" && event.data?.phase === "error");
|
||||
expect(terminal?.data).toMatchObject({
|
||||
aborted: true,
|
||||
stopReason: "restart",
|
||||
});
|
||||
expect(
|
||||
cliDispatchMocks.emitAgentEvent.mock.calls.some(
|
||||
([event]) => (event as { stream?: string }).stream === "assistant",
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,18 +10,9 @@ import { clearCliSession } from "../../agents/cli-session.js";
|
||||
import { extractToolResultText } from "../../agents/embedded-agent-subscribe.tools.js";
|
||||
import { inferToolMetaFromArgs } from "../../agents/embedded-agent-utils.js";
|
||||
import type { EmbeddedAgentRunResult } from "../../agents/embedded-agent.js";
|
||||
import {
|
||||
isAgentRunRestartAbortReason,
|
||||
resolveAgentRunAbortLifecycleFields,
|
||||
} from "../../agents/run-termination.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { updateSessionEntry } from "../../config/sessions/session-accessor.js";
|
||||
import { updateSessionStore, type SessionEntry } from "../../config/sessions.js";
|
||||
import type { AgentEventPayload } from "../../infra/agent-events.js";
|
||||
import {
|
||||
emitAgentEvent,
|
||||
onAgentEvent,
|
||||
withAgentRunLifecycleGeneration,
|
||||
} from "../../infra/agent-events.js";
|
||||
import { emitAgentEvent, onAgentEvent } from "../../infra/agent-events.js";
|
||||
import { formatToolAggregate } from "../tool-meta.js";
|
||||
|
||||
function isClaudeCliProvider(provider: string): boolean {
|
||||
@@ -190,13 +181,9 @@ export async function clearDroppedCliSessionBinding(params: {
|
||||
if (!params.storePath || !params.sessionKey) {
|
||||
return;
|
||||
}
|
||||
await updateSessionEntry(
|
||||
{ storePath: params.storePath, sessionKey: params.sessionKey },
|
||||
(entry) => {
|
||||
clearEntry(entry);
|
||||
return entry;
|
||||
},
|
||||
);
|
||||
await updateSessionStore(params.storePath, (store) => {
|
||||
clearEntry(store[params.sessionKey!]);
|
||||
});
|
||||
}
|
||||
|
||||
function createToolEventBridge(params: {
|
||||
@@ -301,9 +288,8 @@ function createCommentaryEventBridge(params: {
|
||||
});
|
||||
}
|
||||
|
||||
type RunCliAgentWithLifecycleParams = {
|
||||
export async function runCliAgentWithLifecycle(params: {
|
||||
runId: string;
|
||||
lifecycleGeneration?: string;
|
||||
provider: string;
|
||||
runParams: RunCliAgentParams;
|
||||
startedAt?: number;
|
||||
@@ -317,22 +303,7 @@ type RunCliAgentWithLifecycleParams = {
|
||||
onCommentaryText?: (payload: { text: string; itemId?: string }) => Promise<void>;
|
||||
onErrorBeforeLifecycle?: (err: unknown) => Promise<void>;
|
||||
transformResult?: (result: EmbeddedAgentRunResult) => EmbeddedAgentRunResult;
|
||||
};
|
||||
|
||||
export function runCliAgentWithLifecycle(
|
||||
params: RunCliAgentWithLifecycleParams,
|
||||
): Promise<EmbeddedAgentRunResult> {
|
||||
if (!params.lifecycleGeneration) {
|
||||
return runCliAgentWithLifecycleInternal(params);
|
||||
}
|
||||
return withAgentRunLifecycleGeneration(params.lifecycleGeneration, () =>
|
||||
runCliAgentWithLifecycleInternal(params),
|
||||
);
|
||||
}
|
||||
|
||||
async function runCliAgentWithLifecycleInternal(
|
||||
params: RunCliAgentWithLifecycleParams,
|
||||
): Promise<EmbeddedAgentRunResult> {
|
||||
}): Promise<EmbeddedAgentRunResult> {
|
||||
const startedAt = params.startedAt ?? Date.now();
|
||||
const emitLifecycleStart = params.emitLifecycleStart ?? true;
|
||||
const emitLifecycleTerminal = params.emitLifecycleTerminal ?? true;
|
||||
@@ -340,9 +311,6 @@ async function runCliAgentWithLifecycleInternal(
|
||||
if (emitLifecycleStart) {
|
||||
emitAgentEvent({
|
||||
runId: params.runId,
|
||||
...(params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {}),
|
||||
...(params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {}),
|
||||
...(params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {}),
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "start",
|
||||
@@ -381,10 +349,6 @@ async function runCliAgentWithLifecycleInternal(
|
||||
...params.runParams,
|
||||
emitCommentaryText: Boolean(params.onCommentaryText),
|
||||
});
|
||||
const restartAbortReason = params.runParams.abortSignal?.reason;
|
||||
if (isAgentRunRestartAbortReason(restartAbortReason)) {
|
||||
throw restartAbortReason;
|
||||
}
|
||||
const result = params.transformResult?.(rawResult) ?? rawResult;
|
||||
await stopAgentEventBridges(bridges);
|
||||
|
||||
@@ -398,17 +362,22 @@ async function runCliAgentWithLifecycleInternal(
|
||||
}
|
||||
|
||||
if (emitLifecycleTerminal) {
|
||||
const yieldedLifecycleData =
|
||||
result.meta.yielded === true
|
||||
? {
|
||||
yielded: true,
|
||||
livenessState: result.meta.livenessState,
|
||||
stopReason: result.meta.stopReason,
|
||||
}
|
||||
: {};
|
||||
emitAgentEvent({
|
||||
runId: params.runId,
|
||||
...(params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {}),
|
||||
...(params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {}),
|
||||
...(params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {}),
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "end",
|
||||
startedAt,
|
||||
endedAt: Date.now(),
|
||||
...resolveAgentRunAbortLifecycleFields(params.runParams.abortSignal),
|
||||
...yieldedLifecycleData,
|
||||
},
|
||||
});
|
||||
lifecycleTerminalEmitted = true;
|
||||
@@ -420,16 +389,12 @@ async function runCliAgentWithLifecycleInternal(
|
||||
if (emitLifecycleTerminal) {
|
||||
emitAgentEvent({
|
||||
runId: params.runId,
|
||||
...(params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {}),
|
||||
...(params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {}),
|
||||
...(params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {}),
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "error",
|
||||
startedAt,
|
||||
endedAt: Date.now(),
|
||||
error: String(err),
|
||||
...resolveAgentRunAbortLifecycleFields(params.runParams.abortSignal),
|
||||
},
|
||||
});
|
||||
lifecycleTerminalEmitted = true;
|
||||
@@ -442,16 +407,12 @@ async function runCliAgentWithLifecycleInternal(
|
||||
if (emitLifecycleTerminal && !lifecycleTerminalEmitted) {
|
||||
emitAgentEvent({
|
||||
runId: params.runId,
|
||||
...(params.runParams.sessionKey ? { sessionKey: params.runParams.sessionKey } : {}),
|
||||
...(params.runParams.sessionId ? { sessionId: params.runParams.sessionId } : {}),
|
||||
...(params.lifecycleGeneration ? { lifecycleGeneration: params.lifecycleGeneration } : {}),
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "error",
|
||||
startedAt,
|
||||
endedAt: Date.now(),
|
||||
error: "CLI run completed without lifecycle terminal event",
|
||||
...resolveAgentRunAbortLifecycleFields(params.runParams.abortSignal),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user