Files
openclaw/src/sessions/user-turn-transcript.types.ts
Peter Steinberger d8b723fb00 fix: channel turns recover safely after gateway restart (#108283)
* fix(gateway): recover channel turns safely after restart

* refactor(gateway): keep recovery identity internal

* refactor(gateway): keep recovery metadata private

* fix(gateway): preserve recovery action authority

* fix(gateway): close recovery lifecycle gaps

* fix(gateway): fail closed on missing delivery receipts

* fix(gateway): fail closed on unmirrorable replies

* fix(gateway): persist terminal delivery intent

* fix(gateway): align recovery lifecycle types

* fix(gateway): require exact terminal thread receipt

* fix(gateway): close recovered terminal tool calls

* test(gateway): type terminal tool correlation

* fix(gateway): satisfy restart recovery CI contracts

* fix(gateway): require terminal tool correlation

* fix(agents): scope recovery receipts to source turns

* fix(gateway): fail closed without restart correlation

* fix(gateway): dedupe terminal source redelivery

* fix(agents): keep recovery tool results causal

* test(auto-reply): clean recovery fixture directories

* fix(gateway): allow live terminal sends without recovery claims

* fix(agents): reconcile delivered sends through restart aborts

* fix(agents): keep recovery continuations in source turns

* fix(agents): keep source-less sends non-terminal

* fix(auto-reply): dedupe active source redelivery

* fix(agents): harden recovery receipt contracts

* fix(agents): admit terminal recovery successors

* docs(recovery): document canonical receipt ownership

* fix(agents): fail closed on stale turn authority

* test(auto-reply): assert rebound session state

* fix(plugins): keep restart recovery hook list internal
2026-07-15 23:01:26 -07:00

155 lines
5.2 KiB
TypeScript

// User-turn transcript type contracts shared by runtime and queue option types.
import type { AgentMessage } from "../../packages/agent-core/src/types.js";
import type {
SessionTranscriptTurnExpectedState,
SessionTranscriptTurnLifecyclePatch,
} from "../config/sessions/session-transcript-turn-lifecycle.types.js";
import type { InputProvenance } from "./input-provenance.js";
type UserTurnSessionEntry = {
sessionId: string;
updatedAt: number;
sessionFile?: string;
threadId?: string | number;
} & Record<string, unknown>;
export type PersistedUserTurnMediaInput = {
path?: string | null;
url?: string | null;
contentType?: string | null;
kind?: string | null;
};
export type PersistedUserTurnMessage = Extract<AgentMessage, { role: "user" }>;
export type UserTurnInput = {
text?: string | null;
media?: readonly PersistedUserTurnMediaInput[] | null;
timestamp?: number;
idempotencyKey?: string;
senderIsOwner?: boolean;
provenance?: InputProvenance;
mediaOnlyText?: string;
/** Durable participant attribution. Callers must opt in at the product boundary. */
sender?: { id?: string | null; name?: string | null; username?: string | null } | null;
};
export type UserTurnTranscriptUpdateMode = "inline" | "none";
export type UserTurnMessagePersistenceParams = {
input?: UserTurnInput;
message?: PersistedUserTurnMessage;
sessionId?: string;
agentId?: string;
sessionKey?: string;
cwd?: string;
config?: unknown;
updateMode?: UserTurnTranscriptUpdateMode;
beforeMessageWrite?: UserTurnBeforeMessageWrite;
};
type UserTurnBeforeMessageWrite = (params: {
message: PersistedUserTurnMessage;
agentId?: string;
sessionKey?: string;
}) => AgentMessage | null;
type UserTurnTranscriptPersistenceTarget = {
sessionId: string;
expectedSessionId?: string;
sessionKey: string;
sessionEntry: UserTurnSessionEntry | undefined;
sessionStore?: Record<string, UserTurnSessionEntry>;
storePath?: string;
agentId: string;
threadId?: string | number;
cwd?: string;
config?: unknown;
beforeMessageWrite?: UserTurnBeforeMessageWrite;
};
export type UserTurnTranscriptTarget = UserTurnTranscriptPersistenceTarget;
export type UserTurnTranscriptPersistResult = {
/** True only when this call inserted the transcript message. */
appended?: boolean;
sessionFile: string;
sessionEntry: UserTurnSessionEntry | undefined;
messageId: string;
message: PersistedUserTurnMessage;
};
export type UserTurnTranscriptTargetResolver =
| UserTurnTranscriptTarget
| (() => UserTurnTranscriptTarget | undefined | Promise<UserTurnTranscriptTarget | undefined>);
export type PersistUserTurnTranscriptParams = {
input?: UserTurnInput;
message?: PersistedUserTurnMessage;
sessionId: string;
expectedSessionId?: string;
sessionKey: string;
sessionEntry: UserTurnSessionEntry | undefined;
sessionStore?: Record<string, UserTurnSessionEntry>;
storePath?: string;
agentId: string;
threadId?: string | number;
cwd?: string;
config?: unknown;
updateMode?: UserTurnTranscriptUpdateMode;
beforeMessageWrite?: UserTurnBeforeMessageWrite;
expectedSessionState?: SessionTranscriptTurnExpectedState;
sessionLifecyclePatch?: SessionTranscriptTurnLifecyclePatch;
};
type UserTurnInputResolver = () => UserTurnInput | undefined | Promise<UserTurnInput | undefined>;
export type CreateUserTurnTranscriptRecorderParams = {
input?: UserTurnInput;
message?: PersistedUserTurnMessage;
resolveInput?: UserTurnInputResolver;
target: UserTurnTranscriptTargetResolver;
updateMode?: UserTurnTranscriptUpdateMode;
beforeMessageWrite?: UserTurnBeforeMessageWrite;
errorContext?: string;
onPersistenceError?: (error: unknown) => void;
onMessagePersisted?: (message: PersistedUserTurnMessage) => void | Promise<void>;
expectedSessionState?: SessionTranscriptTurnExpectedState;
sessionLifecyclePatch?: SessionTranscriptTurnLifecyclePatch;
};
export type UserTurnTranscriptRecorder = {
readonly message: PersistedUserTurnMessage | undefined;
resolveMessage: () => Promise<PersistedUserTurnMessage | undefined>;
getPersistedMessage?: () => PersistedUserTurnMessage | undefined;
markSentToProvider?: () => void;
markRuntimePersistencePending: (pending: Promise<void>) => void;
markRuntimePersisted: (message?: PersistedUserTurnMessage) => void;
markBlocked: () => void;
hasPersisted: () => boolean;
isBlocked: () => boolean;
hasRuntimePersistencePending: () => boolean;
waitForRuntimePersistence: () => Promise<void>;
persistApproved: (params?: {
target?: UserTurnTranscriptTargetResolver;
updateMode?: UserTurnTranscriptUpdateMode;
cwd?: string;
expectedSessionId?: string;
expectedSessionState?: SessionTranscriptTurnExpectedState;
sessionLifecyclePatch?: SessionTranscriptTurnLifecyclePatch;
}) => Promise<UserTurnTranscriptPersistResult | undefined>;
persistBlocked: (
message: PersistedUserTurnMessage,
params?: {
target?: UserTurnTranscriptTargetResolver;
updateMode?: UserTurnTranscriptUpdateMode;
cwd?: string;
},
) => Promise<UserTurnTranscriptPersistResult | undefined>;
persistFallback: (params?: {
target?: UserTurnTranscriptTargetResolver;
updateMode?: UserTurnTranscriptUpdateMode;
cwd?: string;
}) => Promise<UserTurnTranscriptPersistResult | undefined>;
};