openclaw / src /agents /command /lifecycle.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
e249c6d verified
Raw
History Blame Contribute Delete
7.98 kB
import { classifyGatewayStaleInstall } from "../../gateway/stale-install.js";
import { emitAgentEvent } from "../../infra/agent-events.js";
import { formatErrorMessageForDisplay } from "../../infra/error-diagnostics.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { normalizeAgentRunTerminalDeliverySnapshot } from "../agent-run-terminal-delivery.js";
import type { AgentRunTerminalOutcome } from "../agent-run-terminal-outcome.js";
import { normalizeAgentRunTerminalReceipt } from "../agent-run-terminal-receipt.js";
import type { EmbeddedAgentRunEntryTerminal } from "../embedded-agent-runner/run-entry.js";
import { getFailoverErrorCode } from "../failover/error.js";
import { renderFailoverCodeUserCopy } from "../failover/user-copy.js";
import {
AGENT_RUN_SUPERSEDED_STOP_REASON,
resolveAgentRunAbortLifecycleFields,
resolveAgentRunErrorLifecycleFields,
} from "../run-termination.js";
import type { AgentAttemptLifecycleState } from "./attempt-callbacks.js";
import type { AgentAttemptResult } from "./runtime-loaders.js";
const log = createSubsystemLogger("agents/agent-command");
const formatLifecycleError = (error: unknown): string => {
const staleInstall = classifyGatewayStaleInstall(error);
return staleInstall
? staleInstall.error.message
: formatErrorMessageForDisplay(error, renderFailoverCodeUserCopy(getFailoverErrorCode(error)));
};
function resolveTerminalLogLevel(
outcome: AgentRunTerminalOutcome,
): "info" | "warn" | "error" | undefined {
if (!outcome.stopReason || outcome.stopReason === "end_turn") {
return undefined;
}
if (outcome.reason === "completed") {
return "info";
}
return outcome.status === "timeout" ? "warn" : "error";
}
export function applyAgentRunAbortMetadata<T extends { meta: object }>(
result: T,
signal: AbortSignal | undefined,
): T {
const abortFields = resolveAgentRunAbortLifecycleFields(signal);
if (abortFields.aborted !== true) {
return result;
}
return {
...result,
meta: {
...result.meta,
...abortFields,
},
};
}
export function createAgentCommandLifecycle(params: {
runId: string;
lifecycleGeneration: () => string;
startedAt: number;
abortSignal?: AbortSignal;
state: AgentAttemptLifecycleState;
}) {
let lifecycleFinishingEmitted = false;
const resolveResultError = (runResult: AgentAttemptResult, includeErrorPayload: boolean) =>
params.state.lifecycleError ??
(includeErrorPayload
? runResult.payloads?.find(
(payload) => payload.isError === true && typeof payload.text === "string",
)?.text
: undefined) ??
(runResult.meta.error ? runResult.meta.error.message.trim() || "Agent run failed" : undefined);
const resolveTerminalError = (
runResult: AgentAttemptResult,
fallbackExhausted: boolean,
terminal: EmbeddedAgentRunEntryTerminal,
) =>
params.state.lifecycleError ??
(terminal.outcome.status === "timeout"
? terminal.outcome.error
: resolveResultError(runResult, fallbackExhausted)) ??
(fallbackExhausted ? "All model fallback candidates failed" : "Agent run failed");
const emitTerminalPhase = (
phase: "finishing" | "end" | "error",
terminal: EmbeddedAgentRunEntryTerminal,
error = terminal.outcome.status === "timeout" ? terminal.outcome.error : undefined,
) => {
const { aborted, yielded, replayInvalid, terminalReply } = terminal.metadata;
const terminalDelivery = normalizeAgentRunTerminalDeliverySnapshot(
terminal.metadata.terminalDelivery,
);
const terminalReceipt = normalizeAgentRunTerminalReceipt(terminal.metadata.terminalReceipt);
const { stopReason, livenessState, timeoutPhase, providerStarted } = terminal.outcome;
const abortFields = resolveAgentRunAbortLifecycleFields(params.abortSignal);
emitAgentEvent({
runId: params.runId,
lifecycleGeneration: params.lifecycleGeneration(),
stream: "lifecycle",
data: {
phase,
startedAt: params.startedAt,
endedAt: Date.now(),
aborted: typeof aborted === "boolean" ? aborted : false,
stopReason,
...(yielded === true ? { yielded } : {}),
...(replayInvalid === true ? { replayInvalid } : {}),
...(livenessState ? { livenessState } : {}),
...(timeoutPhase ? { timeoutPhase } : {}),
...(providerStarted !== undefined ? { providerStarted } : {}),
...(error ? { error: formatErrorMessage(error) } : {}),
...(error && params.state.lifecycleErrorObservation
? { errorObservation: params.state.lifecycleErrorObservation }
: {}),
// Finishing is an attempt fence, not the outer execution's final publication.
...(phase !== "finishing" ? { executionSettled: true } : {}),
...(terminalDelivery ? { terminalDelivery } : {}),
...(terminalReceipt ? { terminalReceipt } : {}),
...(terminalReply ? { terminalReply } : {}),
...(stopReason === AGENT_RUN_SUPERSEDED_STOP_REASON
? { aborted: true, stopReason }
: abortFields),
},
});
};
return {
emitBasicError(error: unknown, extraData?: Record<string, unknown>) {
if (params.state.lifecycleEnded) {
return;
}
params.state.lifecycleEnded = true;
emitAgentEvent({
runId: params.runId,
lifecycleGeneration: params.lifecycleGeneration(),
stream: "lifecycle",
data: {
phase: "error",
startedAt: params.startedAt,
endedAt: Date.now(),
error: formatLifecycleError(error),
...(params.state.lifecycleErrorObservation
? { errorObservation: params.state.lifecycleErrorObservation }
: {}),
...extraData,
executionSettled: true,
},
});
},
emitFinishing(terminal: EmbeddedAgentRunEntryTerminal) {
if (
params.state.lifecycleEnded ||
params.state.lifecycleFinishing ||
lifecycleFinishingEmitted
) {
return;
}
lifecycleFinishingEmitted = true;
params.state.lifecycleFinishing = true;
emitTerminalPhase("finishing", terminal);
},
emitEnd(terminal: EmbeddedAgentRunEntryTerminal) {
if (params.state.lifecycleEnded) {
return;
}
params.state.lifecycleEnded = true;
const stopReason = terminal.outcome.stopReason;
const logLevel = resolveTerminalLogLevel(terminal.outcome);
if (logLevel) {
log[logLevel](`[agent] run ${params.runId} ended with stopReason=${stopReason}`);
}
emitTerminalPhase("end", terminal);
},
resolveResultError,
resolveTerminalError,
emitResultError(
runResult: AgentAttemptResult,
fallbackExhausted: boolean,
terminal: EmbeddedAgentRunEntryTerminal,
) {
if (params.state.lifecycleEnded) {
return;
}
params.state.lifecycleEnded = true;
const error = resolveTerminalError(runResult, fallbackExhausted, terminal);
emitTerminalPhase("error", terminal, error);
},
emitPostTurnError(error: unknown, terminal: EmbeddedAgentRunEntryTerminal) {
if (params.state.lifecycleEnded) {
return;
}
params.state.lifecycleEnded = true;
const terminalDelivery = normalizeAgentRunTerminalDeliverySnapshot(
terminal.metadata.terminalDelivery,
);
emitAgentEvent({
runId: params.runId,
lifecycleGeneration: params.lifecycleGeneration(),
stream: "lifecycle",
data: {
phase: "error",
startedAt: params.startedAt,
endedAt: Date.now(),
error: formatLifecycleError(error),
...(terminalDelivery ? { terminalDelivery } : {}),
...resolveAgentRunErrorLifecycleFields(error, params.abortSignal),
executionSettled: true,
},
});
},
};
}