File size: 2,345 Bytes
e249c6d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | /**
* Lifecycle callback state helpers for a single agent attempt.
*/
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
import type { AgentMessage } from "../runtime/index.js";
/** Mutable lifecycle flags observed while a single agent attempt runs. */
export type AgentAttemptLifecycleState = {
currentTurnUserMessagePersisted: boolean;
lifecycleError?: string;
lifecycleErrorObservation?: Record<string, unknown>;
lifecycleFinishing: boolean;
lifecycleEnded: boolean;
};
/** Event shape emitted by runtimes during an agent attempt. */
type AgentAttemptLifecycleEvent = {
stream: string;
data?: Record<string, unknown>;
sessionKey?: string;
};
/** Creates callbacks that update lifecycle flags for persistence decisions. */
export function createAgentAttemptLifecycleCallbacks(
state: AgentAttemptLifecycleState,
onRuntimeTurnStarted?: () => void | Promise<void>,
): {
onUserMessagePersisted: (message: Extract<AgentMessage, { role: "user" }>) => void;
onAgentEvent: (evt: AgentAttemptLifecycleEvent) => void | Promise<void>;
} {
return {
onUserMessagePersisted: () => {
state.currentTurnUserMessagePersisted = true;
},
onAgentEvent: (evt) => {
if (evt.stream !== "lifecycle" || typeof evt.data?.phase !== "string") {
return;
}
if (evt.data.phase === "start") {
// A same-candidate retry replaces deferred terminal state from the
// preceding attempt; retaining it would abort a recovered run.
state.lifecycleError = undefined;
state.lifecycleErrorObservation = undefined;
state.lifecycleFinishing = false;
state.lifecycleEnded = false;
return onRuntimeTurnStarted?.();
}
if (typeof evt.data.error === "string" && evt.data.error.trim()) {
state.lifecycleError = evt.data.error;
state.lifecycleErrorObservation = asOptionalRecord(evt.data.errorObservation);
}
// Finishing means output ended but transcript/session persistence may still
// need to run; end/error means the runtime lifecycle is complete.
if (evt.data.phase === "finishing") {
state.lifecycleFinishing = true;
return;
}
if (evt.data.phase === "end" || evt.data.phase === "error") {
state.lifecycleEnded = true;
}
},
};
}
|