From bef753b2785f8af598f2e9516896974091445164 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 11 Aug 2026 03:38:54 -0700 Subject: [PATCH] refactor(agents): split reply-run registry into concept modules (#121995) --- config/max-lines-baseline.txt | 1 - docs/.generated/plugin-sdk-api-baseline.jsonl | 130 +- .../reply/reply-run-registry.contracts.ts | 289 +++ .../reply-run-registry.message-injection.ts | 223 ++ .../reply/reply-run-registry.operation.ts | 680 ++++++ .../reply/reply-run-registry.registry.ts | 499 ++++ .../reply/reply-run-registry.state.ts | 444 ++++ src/auto-reply/reply/reply-run-registry.ts | 2072 +---------------- 8 files changed, 2261 insertions(+), 2077 deletions(-) create mode 100644 src/auto-reply/reply/reply-run-registry.contracts.ts create mode 100644 src/auto-reply/reply/reply-run-registry.message-injection.ts create mode 100644 src/auto-reply/reply/reply-run-registry.operation.ts create mode 100644 src/auto-reply/reply/reply-run-registry.registry.ts create mode 100644 src/auto-reply/reply/reply-run-registry.state.ts diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt index 44b1ca4852c4..c1061cb60f6e 100644 --- a/config/max-lines-baseline.txt +++ b/config/max-lines-baseline.txt @@ -506,7 +506,6 @@ src/auto-reply/reply/model-selection.test.ts src/auto-reply/reply/queue.collect.test.ts src/auto-reply/reply/queue/drain.ts src/auto-reply/reply/reply-run-registry.test.ts -src/auto-reply/reply/reply-run-registry.ts src/auto-reply/reply/reply-turn-admission.test.ts src/auto-reply/reply/reply-utils.test.ts src/auto-reply/reply/session.test.ts diff --git a/docs/.generated/plugin-sdk-api-baseline.jsonl b/docs/.generated/plugin-sdk-api-baseline.jsonl index 7cc52af3cb5d..9d59013628d6 100644 --- a/docs/.generated/plugin-sdk-api-baseline.jsonl +++ b/docs/.generated/plugin-sdk-api-baseline.jsonl @@ -53,17 +53,17 @@ {"closureHash":"ff3c4616cd6212a6d698831a8b287ad87e3968c8663f4090d095bb30ec40fc1c","declaration":"export function abortAndDrainAgentHarnessRun(params: { sessionId: string; sessionKey?: string; settleMs?: number; forceClear?: boolean; reason?: string; }): Promise;","entrypoint":"agent-harness","exportName":"abortAndDrainAgentHarnessRun","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} {"closureHash":"58989e3fa91eecf2e05202e36548e6e64a3b9219e83154bd99866c851604e34e","declaration":"export function createAgentToolResultMiddlewareRunner(ctx: AgentToolResultMiddlewareContext, handlers?: AgentToolResultMiddleware[]): { applyToolResultMiddleware(event: AgentToolResultMiddlewareEvent): Promise; };","entrypoint":"agent-harness","exportName":"createAgentToolResultMiddlewareRunner","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} {"closureHash":"b8f6355d7ad1700aceecddaa6e9ccf43091a792d5b481a1ee3f55294a881a67d","declaration":"export function createCodexAppServerToolResultExtensionRunner(ctx: CodexAppServerExtensionContext, factories?: CodexAppServerExtensionFactory[]): { applyToolResultExtensions(event: CodexAppServerToolResultEvent): Promise>; };","entrypoint":"agent-harness","exportName":"createCodexAppServerToolResultExtensionRunner","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} -{"closureHash":"17759f4882831e0d89124d26439ef9d9fe89ff20b7f46b18f7f9da376b0ab7e3","declaration":"export function createOpenClawCodingTools(options?: OpenClawCodingToolsOptions): AnyAgentTool[];","entrypoint":"agent-harness","exportName":"createOpenClawCodingTools","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} +{"closureHash":"3bb548412e30493320303848ecf1757667cd3c0ce829471a5e1e426dbedbb20b","declaration":"export function createOpenClawCodingTools(options?: OpenClawCodingToolsOptions): AnyAgentTool[];","entrypoint":"agent-harness","exportName":"createOpenClawCodingTools","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} {"closureHash":"13649ee853485319e7449fda25c106b3f3f53d3090e53cdbb28e220a8529eb80","declaration":"export function disposeRegisteredAgentHarnesses(): Promise;","entrypoint":"agent-harness","exportName":"disposeRegisteredAgentHarnesses","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} {"closureHash":"f255a91162ece4c239bb8fa745efa14a1054bc024a291e9954f75f239b2b87c3","declaration":"export function resolveActiveEmbeddedRunSessionId(sessionKey: string): string | undefined;","entrypoint":"agent-harness","exportName":"resolveActiveEmbeddedRunSessionId","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} {"closureHash":"27d9f61df9ccf68615cb2002a6da891e36ab0c07623bc37c4d07aa980b446881","declaration":"export function resolveWebSearchToolPolicy(params: WebSearchToolPolicyParams): WebSearchToolPolicyResolution;","entrypoint":"agent-harness","exportName":"resolveWebSearchToolPolicy","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"function","recordType":"export"} -{"closureHash":"fd9e44e0e924c462451e966215da05d67067abfb489a1e1f64cac2b7279c2f65","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"agent-harness","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} -{"closureHash":"49056f1f65feb4085e19b1685d2cd42dfc22c222d53dd32531dbbfe559c9787e","declaration":"export type AgentHarnessV2 = AgentHarnessV2;","entrypoint":"agent-harness","exportName":"AgentHarnessV2","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} +{"closureHash":"7bc7817206b083bb475cc0f3862c9b552f0053de3f325f0b846a284e0c1c73f5","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"agent-harness","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} +{"closureHash":"b9cc9cb5302734ebcde16890889d21001f595151205b3105e7c6da221c415734","declaration":"export type AgentHarnessV2 = AgentHarnessV2;","entrypoint":"agent-harness","exportName":"AgentHarnessV2","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} {"closureHash":"4ad0f830bf11a2db48faefb1439b7fbb08d9f22d22083f5e301327e1696f2310","declaration":"export type AgentToolResultMiddleware = AgentToolResultMiddleware;","entrypoint":"agent-harness","exportName":"AgentToolResultMiddleware","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} {"closureHash":"008b6c78d5e44a9d1df1d7e32e6015382ee9ba1cb9b10bd4c2b467619d1c89d4","declaration":"export type AgentToolResultMiddlewareEvent = AgentToolResultMiddlewareEvent;","entrypoint":"agent-harness","exportName":"AgentToolResultMiddlewareEvent","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} {"closureHash":"7414a2a79c2d78c1d52538250c827305bba1a2bb25010448837c8524a9f95b3c","declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"agent-harness","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} -{"closureHash":"81f55150b97acd8afecf5636ae9479ef5047591f30fdd5b10ffc4cc67a000e82","declaration":"export type EmbeddedRunAttemptParams = EmbeddedRunAttemptParams;","entrypoint":"agent-harness","exportName":"EmbeddedRunAttemptParams","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} -{"closureHash":"2edd02e8f04850f8233a4ad2ec742a029d394fa04a970138c50438ba8cc0af79","declaration":"export type EmbeddedRunAttemptParamsV2 = EmbeddedRunAttemptParamsV2;","entrypoint":"agent-harness","exportName":"EmbeddedRunAttemptParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} +{"closureHash":"9b2159287cc6f60f75bf0b740c9135e4379c56dee8fd95987618f29d5e06f1c5","declaration":"export type EmbeddedRunAttemptParams = EmbeddedRunAttemptParams;","entrypoint":"agent-harness","exportName":"EmbeddedRunAttemptParams","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} +{"closureHash":"eab91b424611ec95d7039ae98650c677c8a52db27220a96d65bb3261ca0a4854","declaration":"export type EmbeddedRunAttemptParamsV2 = EmbeddedRunAttemptParamsV2;","entrypoint":"agent-harness","exportName":"EmbeddedRunAttemptParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} {"closureHash":"25c6985c3bef2c6efc6a96e70af6d66fe073be0b0d311681cfc0dfc186528d9a","declaration":"export type OpenClawAgentToolResult = OpenClawAgentToolResult;","entrypoint":"agent-harness","exportName":"OpenClawAgentToolResult","importSpecifier":"openclaw/plugin-sdk/agent-harness","kind":"type","recordType":"export"} {"category":"runtime","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","recordType":"module"} {"closureHash":"fab99dfcb01d475a1bfdbe7a8fac9be10a93dac5f12ac78c2dea673c8ab86abf","declaration":"export function abortAgentHarnessRun(sessionId: string): boolean;\nexport function abortAgentHarnessRun(sessionId: undefined, opts: { mode: \"all\" | \"compacting\"; reason?: \"restart\"; }): boolean;","entrypoint":"agent-harness-runtime","exportName":"abortAgentHarnessRun","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} @@ -82,8 +82,8 @@ {"closureHash":"f8ad3d762c9753fdc36b2b9af0980167fdc54090c7bcca2f3dbe7c8951e86821","declaration":"export function buildAgentRuntimePlan(params: BuildAgentRuntimePlanParams): AgentRuntimePlan;","entrypoint":"agent-harness-runtime","exportName":"buildAgentRuntimePlan","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"528d11abf061b0b303722737de5a6406e7e55bffa38c45144c77d896ae1b3b0a","declaration":"export function buildBootstrapContextForFiles(bootstrapFiles: WorkspaceBootstrapFile[], params: { config?: OpenClawConfig; agentId?: string | null; warn?: (message: string) => void; }): EmbeddedContextFile[];","entrypoint":"agent-harness-runtime","exportName":"buildBootstrapContextForFiles","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"44d5a4667d2f3dc3a28e5c9d7b46765b1fb3c62820288e40251f5174911c8a6a","declaration":"export function buildEmbeddedAttemptToolRunContext(params: { trigger?: EmbeddedRunTrigger; jobId?: string; memoryFlushWritePath?: string; toolsAllow?: string[]; conversationToolPolicy?: GroupToolPolicyConfig; trace?: DiagnosticTraceContext; }): { trigger?: EmbeddedRunTrigger; jobId?: string; memoryFlushWritePath?: string; runtimeToolAllowlist?: string[]; conversationToolPolicy?: GroupToolPolicyConfig; trace?: DiagnosticTraceContext; };","entrypoint":"agent-harness-runtime","exportName":"buildEmbeddedAttemptToolRunContext","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} -{"closureHash":"178b01c54bc6e78a54806159aff4d552ea4f28ead4c1324c6b677bdc950453f5","declaration":"export function buildHarnessContextEngineRuntimeContext(params: Parameters[0]): ContextEngineRuntimeContext;","entrypoint":"agent-harness-runtime","exportName":"buildHarnessContextEngineRuntimeContext","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} -{"closureHash":"5828086ce3caef3f6cf9324cef3ec99d0004ae964a52b98e72522df412d25693","declaration":"export function buildHarnessContextEngineRuntimeContextFromUsage(params: Parameters[0]): ContextEngineRuntimeContext;","entrypoint":"agent-harness-runtime","exportName":"buildHarnessContextEngineRuntimeContextFromUsage","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} +{"closureHash":"6822e66c6dd662f975f5647fb130c1829cd68059b72b82a9e13cbea84ad09be4","declaration":"export function buildHarnessContextEngineRuntimeContext(params: Parameters[0]): ContextEngineRuntimeContext;","entrypoint":"agent-harness-runtime","exportName":"buildHarnessContextEngineRuntimeContext","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} +{"closureHash":"9b10459a83024bab6bc2487e96a58b21b5e07e9afc12483fb1a55da9919fbdce","declaration":"export function buildHarnessContextEngineRuntimeContextFromUsage(params: Parameters[0]): ContextEngineRuntimeContext;","entrypoint":"agent-harness-runtime","exportName":"buildHarnessContextEngineRuntimeContextFromUsage","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"33a7cd8d63d4ef045ee3a42f806026a8b621e7dd32d8d6fbf4e7b44452d5e0ce","declaration":"export function buildNativeHookRelayCommand(params: { provider: NativeHookRelayProvider; relayId: string; generation?: string; event: NativeHookRelayEvent; preToolUseUnavailable?: \"noop\"; timeoutMs?: number; executable?: string; nice?: number | false; nodeExecutable?: string; }): string;","entrypoint":"agent-harness-runtime","exportName":"buildNativeHookRelayCommand","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"3846647c3c2fb0242f70591cf95c2e02f2a5a3c44447efb4cf7a31b2dc2122fd","declaration":"export function buildSkillWorkshopPromptSection(): string[];","entrypoint":"agent-harness-runtime","exportName":"buildSkillWorkshopPromptSection","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"32e977ece65144a83ae1b0a3c8d1b9b00a2a2506ad9ef1db3b2b9770ce762dbe","declaration":"export function buildWatchedSessionsHarnessContext(params: { config?: OpenClawConfig; sessionKey?: string; sandboxed?: boolean; toolNames: Iterable; capabilityToolNames?: Iterable; }): string | undefined;","entrypoint":"agent-harness-runtime","exportName":"buildWatchedSessionsHarnessContext","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} @@ -100,7 +100,7 @@ {"closureHash":"51f76e9e37b65b358f0313e10152d950b003bc3e50f4bb26fe29a988237772e2","declaration":"export function consumePreExecutionBlockedToolCall(toolCallId: string, runId?: string): boolean;","entrypoint":"agent-harness-runtime","exportName":"consumePreExecutionBlockedToolCall","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"58989e3fa91eecf2e05202e36548e6e64a3b9219e83154bd99866c851604e34e","declaration":"export function createAgentToolResultMiddlewareRunner(ctx: AgentToolResultMiddlewareContext, handlers?: AgentToolResultMiddleware[]): { applyToolResultMiddleware(event: AgentToolResultMiddlewareEvent): Promise; };","entrypoint":"agent-harness-runtime","exportName":"createAgentToolResultMiddlewareRunner","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"b8f6355d7ad1700aceecddaa6e9ccf43091a792d5b481a1ee3f55294a881a67d","declaration":"export function createCodexAppServerToolResultExtensionRunner(ctx: CodexAppServerExtensionContext, factories?: CodexAppServerExtensionFactory[]): { applyToolResultExtensions(event: CodexAppServerToolResultEvent): Promise>; };","entrypoint":"agent-harness-runtime","exportName":"createCodexAppServerToolResultExtensionRunner","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} -{"closureHash":"a6559603b97fb7dd3123e8c496eb50820a9f1e984113691813d6ab3a728d3f57","declaration":"export function deliverAgentHarnessUserInputPrompt(params: PromptDeliveryParams, questions: readonly AgentHarnessUserInputQuestion[], options?: AgentHarnessUserInputPromptOptions): Promise;","entrypoint":"agent-harness-runtime","exportName":"deliverAgentHarnessUserInputPrompt","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} +{"closureHash":"5b6d3a50c9bbc9962e93e618d01b478b9bd385947b3a30401605952f397a0505","declaration":"export function deliverAgentHarnessUserInputPrompt(params: PromptDeliveryParams, questions: readonly AgentHarnessUserInputQuestion[], options?: AgentHarnessUserInputPromptOptions): Promise;","entrypoint":"agent-harness-runtime","exportName":"deliverAgentHarnessUserInputPrompt","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"6f436485257f69fa07a18203b91d4341690522f12aff4f25805c7a1e21326ee3","declaration":"export function detectAndLoadAgentHarnessPromptImages(params: { prompt: string; workspaceDir: string; model: { input?: string[]; }; existingImages?: ImageContent[]; imageOrder?: PromptImageOrderEntry[]; media?: MediaFact[]; config?: OpenClawConfig; workspaceOnly?: boolean; localRoots?: readonly string[]; sandbox?: { root: string; bridge: SandboxFsBridge; }; }): Promise<{ images: ImageContent[]; detectedRefs: Array<{ raw: string; resolved: string; type: \"path\" | \"media-uri\"; }>; loadedCount: number; skippedCount: number; }>;","entrypoint":"agent-harness-runtime","exportName":"detectAndLoadAgentHarnessPromptImages","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"13649ee853485319e7449fda25c106b3f3f53d3090e53cdbb28e220a8529eb80","declaration":"export function disposeRegisteredAgentHarnesses(): Promise;","entrypoint":"agent-harness-runtime","exportName":"disposeRegisteredAgentHarnesses","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"c38cd390190bb07ddcd247420d6e0b489a0229bd402ddca0440417217a57251a","declaration":"export function emitAgentEvent(event: Omit): void;","entrypoint":"agent-harness-runtime","exportName":"emitAgentEvent","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} @@ -162,7 +162,7 @@ {"closureHash":"40154c3b7b2a7179df33f4a3fbef5e56078be2759d5aab29f39eb0f3c92fc631","declaration":"export function projectRuntimeToolInputSchema(schema: unknown, path?: string): RuntimeToolInputSchemaProjection;","entrypoint":"agent-harness-runtime","exportName":"projectRuntimeToolInputSchema","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"e42e3dc63e8545bb9a6056336fbd6c1dc8f356dce4f0f1c6d9765254c974390e","declaration":"export function projectSettledTurnFinalizationAttemptResult(result: AgentHarnessAttemptResult): AgentHarnessSettledTurnFinalizationResult;","entrypoint":"agent-harness-runtime","exportName":"projectSettledTurnFinalizationAttemptResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"32ab53f0ce98f72c43eb20bbcd3261b504ed4039c48533234c89331c9bcde7f1","declaration":"export function queueAgentHarnessMessage(sessionId: string, text: string, options?: EmbeddedAgentQueueMessageOptions): boolean;","entrypoint":"agent-harness-runtime","exportName":"queueAgentHarnessMessage","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} -{"closureHash":"e7885fe5b27910d3cdef6a3d963c76df4a4e2fa5992074437b53470201754722","declaration":"export function registerNativeHookRelay(params: RegisterNativeHookRelayParams): ActiveNativeHookRelayRegistrationHandle;","entrypoint":"agent-harness-runtime","exportName":"registerNativeHookRelay","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} +{"closureHash":"d42ea0403bf897f85908921c1d27e32ccd696f106829c98537a3f314d627088b","declaration":"export function registerNativeHookRelay(params: RegisterNativeHookRelayParams): ActiveNativeHookRelayRegistrationHandle;","entrypoint":"agent-harness-runtime","exportName":"registerNativeHookRelay","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"b7cc6c35506dc6955e0359aa5c53c15a4d0db23639da8c1717f8fefda953c119","declaration":"export function requestDeferredPluginToolApproval(params: { deferredApproval: DeferredPluginToolApproval; signal?: AbortSignal; }): Promise;","entrypoint":"agent-harness-runtime","exportName":"requestDeferredPluginToolApproval","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"a2306cba08cb05ff8430c8362c0cc3c821706da5a4e65a06849ad0d0fb007ae3","declaration":"export function resetAgentEventsForTest(options?: { preserveListeners?: boolean; }): void;","entrypoint":"agent-harness-runtime","exportName":"resetAgentEventsForTest","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"f255a91162ece4c239bb8fa745efa14a1054bc024a291e9954f75f239b2b87c3","declaration":"export function resolveActiveEmbeddedRunSessionId(sessionKey: string): string | undefined;","entrypoint":"agent-harness-runtime","exportName":"resolveActiveEmbeddedRunSessionId","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} @@ -197,7 +197,7 @@ {"closureHash":"da8b724c5858691b8a1c71c63294a397631c031270db92fdff9ef4493efa2492","declaration":"export function runAgentHarnessBeforeAgentFinalizeHook(params: { event: PluginHookBeforeAgentFinalizeEvent; ctx: AgentHarnessHookContext; hookRunner?: AgentHarnessHookRunner; }): Promise;","entrypoint":"agent-harness-runtime","exportName":"runAgentHarnessBeforeAgentFinalizeHook","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"8fec73ee8c7c9be3335bd6dc1bac9278f0be5beef22a5975eb06e56f504f4bd6","declaration":"export function runAgentHarnessBeforeCompactionHook(params: { sessionFile: string; messages?: AgentMessage[]; ctx: AgentHarnessHookContext; }): Promise;","entrypoint":"agent-harness-runtime","exportName":"runAgentHarnessBeforeCompactionHook","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"021b81820bef2e8d54c50a5fc159d6665b23b50a1f5c40dae76a4833839d89c9","declaration":"export function runAgentHarnessBeforeMessageWriteHook(params: { message: AgentMessage; agentId?: string; sessionKey?: string; }): AgentMessage | null;","entrypoint":"agent-harness-runtime","exportName":"runAgentHarnessBeforeMessageWriteHook","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} -{"closureHash":"283f0ae9e6e9eae68f9eb60371a5a82c084a7cd3ebb6f331dabcf3441fd72e9a","declaration":"export function runAgentHarnessGatewayQuestion(params: RunAgentHarnessGatewayQuestionParams): Promise;","entrypoint":"agent-harness-runtime","exportName":"runAgentHarnessGatewayQuestion","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} +{"closureHash":"d0defb9e0d6f721b5050f5a10068ba6f1c8c8118b1195df50d6457697814dbc4","declaration":"export function runAgentHarnessGatewayQuestion(params: RunAgentHarnessGatewayQuestionParams): Promise;","entrypoint":"agent-harness-runtime","exportName":"runAgentHarnessGatewayQuestion","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"89aa32c12a44b446777bc4cfbb106a048dd9cbc668260525798a895a433256f4","declaration":"export function runAgentHarnessLlmInputHook(params: { event: PluginHookLlmInputEvent; ctx: AgentHarnessHookContext; hookRunner?: AgentHarnessHookRunner; }): void;","entrypoint":"agent-harness-runtime","exportName":"runAgentHarnessLlmInputHook","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"ba63f9cdd34249fba984af87a039494b75b3f93c0c70b925e884f3f98935b984","declaration":"export function runAgentHarnessLlmOutputHook(params: { event: PluginHookLlmOutputEvent; ctx: AgentHarnessHookContext; hookRunner?: AgentHarnessHookRunner; }): void;","entrypoint":"agent-harness-runtime","exportName":"runAgentHarnessLlmOutputHook","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} {"closureHash":"c4c5131ab7065decd8d8e935ac3779e3baa2c52fbbbd95a6ccb624377db09ca1","declaration":"export function runBeforeToolCallHook(args: { toolName: string; params: unknown; toolKind?: PluginHookToolKind; toolInputKind?: PluginHookToolInputKind; toolCallId?: string; ctx?: HookContext; signal?: AbortSignal; approvalMode?: \"request\" | \"report\" | \"deny\" | \"defer\"; }): Promise;","entrypoint":"agent-harness-runtime","exportName":"runBeforeToolCallHook","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"function","recordType":"export"} @@ -219,13 +219,13 @@ {"closureHash":"0701a22057edc3b733da7e05948db94aa98b337b224a5718efdd85212a8602bf","declaration":"export const TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT: string;","entrypoint":"agent-harness-runtime","exportName":"TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"const","recordType":"export"} {"closureHash":"b5845c99df6c2a834a16678c320e0111bad96a3a7ac245d379aa5e6d2555d29b","declaration":"export const agentHarnessAttemptTerminal: { merge: (current: AgentRunAttemptTerminal, incoming: AgentRunAttemptTerminal) => AgentRunAttemptTerminal; normalize: (input: { aborted?: boolean; externalAbort?: boolean; idleTimedOut?: boolean; promptError?: unknown; promptErrorSource?: AgentRunAttemptFailureSource | null; timedOut?: boolean; timedOutByRunBudget?: boolean; timedOutDuringCompaction?: boolean; timedOutDuringToolExecution?: boolean; }) => AgentRunAttemptTerminal; project: (terminal: AgentRunAttemptTerminal) => { aborted: boolean; cleanupYieldAborted: boolean; externalAbort: boolean; failed: boolean; idleTimedOut: boolean; interrupted: boolean; promptError: unknown; promptErrorSource: AgentRunAttemptFailureSource | null; timedOut: boolean; timedOutByRunBudget: boolean; timedOutDuringCompaction: boolean; timedOutDuringToolExecution: boolean; }; setFailure: (terminal: AgentRunAttemptTerminal, failure: { source: AgentRunAttemptFailureSource; error: unknown; } | null) => AgentRunAttemptTerminal;};","entrypoint":"agent-harness-runtime","exportName":"agentHarnessAttemptTerminal","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"const","recordType":"export"} {"closureHash":"22bcf962aa76c76074c4ed4ad7d6558edbc3588e239263e09af74c1000bbe012","declaration":"export const embeddedAgentLog: SubsystemLogger;","entrypoint":"agent-harness-runtime","exportName":"embeddedAgentLog","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"const","recordType":"export"} -{"closureHash":"273b9824b5b4825e086a3191eadc2aea318b32e73bdffe70434f24211eec2911","declaration":"export const nativeHookRelayTesting: { readonly clearNativeHookRelaysForTests: () => void; readonly getNativeHookRelayInvocationsForTests: () => NativeHookRelayInvocation[]; readonly getNativeHookRelayRegistrationForTests: (relayId: string) => NativeHookRelayRegistration | undefined; readonly getNativeHookRelayBridgeDirForTests: () => string; readonly getNativeHookRelayBridgeRegistryPathForTests: (relayId: string) => string; readonly getNativeHookRelayBridgeRecordForTests: (relayId: string) => Record | undefined; readonly isNativeHookRelayBridgeLookupRetryableForTests: (error: unknown, elapsedMs?: number) => boolean; readonly formatPermissionApprovalDescriptionForTests: (request: NativeHookRelayPermissionApprovalRequest) => string; readonly permissionRequestContentFingerprintForTests: (request: NativeHookRelayPermissionApprovalRequest) => string; readonly permissionRequestToolInputKeyFingerprintForTests: (toolInput: Record) => string; readonly setNativeHookRelayPermissionApprovalRequesterForTests: (requester: NativeHookRelayPermissionApprovalRequester) => void; readonly setNativeHookRelayDeferredToolApprovalRequesterForTests: (requester: NativeHookRelayDeferredToolApprovalRequester) => void;};","entrypoint":"agent-harness-runtime","exportName":"nativeHookRelayTesting","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"const","recordType":"export"} +{"closureHash":"851c93d293ad03a2bc331890ed788f264195d98cf904117bcc8302e5dbe15049","declaration":"export const nativeHookRelayTesting: { readonly clearNativeHookRelaysForTests: () => void; readonly getNativeHookRelayInvocationsForTests: () => NativeHookRelayInvocation[]; readonly getNativeHookRelayRegistrationForTests: (relayId: string) => NativeHookRelayRegistration | undefined; readonly getNativeHookRelayBridgeDirForTests: () => string; readonly getNativeHookRelayBridgeRegistryPathForTests: (relayId: string) => string; readonly getNativeHookRelayBridgeRecordForTests: (relayId: string) => Record | undefined; readonly isNativeHookRelayBridgeLookupRetryableForTests: (error: unknown, elapsedMs?: number) => boolean; readonly formatPermissionApprovalDescriptionForTests: (request: NativeHookRelayPermissionApprovalRequest) => string; readonly permissionRequestContentFingerprintForTests: (request: NativeHookRelayPermissionApprovalRequest) => string; readonly permissionRequestToolInputKeyFingerprintForTests: (toolInput: Record) => string; readonly setNativeHookRelayPermissionApprovalRequesterForTests: (requester: NativeHookRelayPermissionApprovalRequester) => void; readonly setNativeHookRelayDeferredToolApprovalRequesterForTests: (requester: NativeHookRelayDeferredToolApprovalRequester) => void;};","entrypoint":"agent-harness-runtime","exportName":"nativeHookRelayTesting","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"const","recordType":"export"} {"closureHash":"f237bbc11e1d389679cd4e2ad7aa9055951b3029348e3374b81f769786a51d55","declaration":"export type AbortAndDrainAgentHarnessRunResult = AbortAndDrainEmbeddedAgentRunResult;","entrypoint":"agent-harness-runtime","exportName":"AbortAndDrainAgentHarnessRunResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"6f802f46717d28d260bbcb2cf4790dfed4e9ab28c4fe822d259228189cca4665","declaration":"export type AgentApprovalEventData = AgentApprovalEventData;","entrypoint":"agent-harness-runtime","exportName":"AgentApprovalEventData","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"12aac3a57ce200b0f082002be237625729f84ed7c337f1bac90bcd0688a53319","declaration":"export type AgentEventPayload = AgentEventPayload;","entrypoint":"agent-harness-runtime","exportName":"AgentEventPayload","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"fd9e44e0e924c462451e966215da05d67067abfb489a1e1f64cac2b7279c2f65","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"agent-harness-runtime","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"fd9e44e0e924c462451e966215da05d67067abfb489a1e1f64cac2b7279c2f65","declaration":"export type AgentHarnessAttemptParams = AgentHarnessAttemptParams;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessAttemptParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"77ba2f5b565c17687517aa0187ba27e72d682ea205bff114be307fde8632fd2a","declaration":"export type AgentHarnessAttemptParamsV2 = AgentHarnessAttemptParamsV2;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessAttemptParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"7bc7817206b083bb475cc0f3862c9b552f0053de3f325f0b846a284e0c1c73f5","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"agent-harness-runtime","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"7bc7817206b083bb475cc0f3862c9b552f0053de3f325f0b846a284e0c1c73f5","declaration":"export type AgentHarnessAttemptParams = AgentHarnessAttemptParams;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessAttemptParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"fd2630e3707c1916e1bb49e6f23e301e91cc74cbf1852a4b7277a221896a7a1f","declaration":"export type AgentHarnessAttemptParamsV2 = AgentHarnessAttemptParamsV2;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessAttemptParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"2dc2a62deea63b316142cc28d40afb88fb0d1e5ce3e048f141420726cda38248","declaration":"export type AgentHarnessAttemptResult = AgentHarnessAttemptResult;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessAttemptResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"c7e642234081160830fd9e18a301900f8ee1ea8c88382e5a286f0102de65b714","declaration":"export type AgentHarnessAuthBindingFingerprintParams = AgentHarnessAuthBindingFingerprintParams;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessAuthBindingFingerprintParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"06b71bd1e13e23c83573c7c50a0ea47e2634060ae895d66171d80b466d64b315","declaration":"export type AgentHarnessCompactParams = CompactEmbeddedAgentSessionParams;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessCompactParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} @@ -239,8 +239,8 @@ {"closureHash":"95741b872a194687696eba4a96d6e887ec6e81922695fa0282cb2f0a46ff9f97","declaration":"export type AgentHarnessSessionForkParams = AgentHarnessSessionForkParams;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSessionForkParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"f3c46fef2a6c3393b0b6ad09915abcd6f57bb58ddddde1d60ebd113c7a1c5fa6","declaration":"export type AgentHarnessSessionForkResult = AgentHarnessSessionForkResult;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSessionForkResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"4b5a59730309e446f59a8ec1c554c3193aca1959916594cc657347098bd67a35","declaration":"export type AgentHarnessSettledTurnFinalizationResult = AgentHarnessSettledTurnFinalizationResult;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSettledTurnFinalizationResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"e6d05e621798576718ee20dd5e8046d152b6a8a1fea14d01d77ea434d43128fa","declaration":"export type AgentHarnessSideQuestionParams = AgentHarnessSideQuestionParams;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSideQuestionParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"c4a5fc3666b0d54431dd30a363627ed623fa61cb669902c3fea17caab0f19e5e","declaration":"export type AgentHarnessSideQuestionParamsV2 = AgentHarnessSideQuestionParamsV2;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSideQuestionParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"4f2c5ece73bcf5045953948e04e3f67f344337eca5390023bea43a38223be911","declaration":"export type AgentHarnessSideQuestionParams = AgentHarnessSideQuestionParams;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSideQuestionParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"b691f8c7d4d0972c992b2c8af6232b099a1edccb0ca3646a90a9b3e2e9c2028e","declaration":"export type AgentHarnessSideQuestionParamsV2 = AgentHarnessSideQuestionParamsV2;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSideQuestionParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"e84173055503c40dd0ef1cb67c6d333e9257fcf995252b27c1d58f5c904e8de9","declaration":"export type AgentHarnessSideQuestionResult = AgentHarnessSideQuestionResult;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSideQuestionResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"50214d08dac98e3c1031ac7bc733b85cb2247d2eef3b6b2045758ae403af7647","declaration":"export type AgentHarnessSupport = AgentHarnessSupport;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSupport","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"50ce26ea22bc225e6064d2e5f37356b6f4ce4a57f8b9fa42baa4c421da48aad5","declaration":"export type AgentHarnessSupportContext = AgentHarnessSupportContext;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessSupportContext","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} @@ -250,7 +250,7 @@ {"closureHash":"20a8a7c9d5ebf425c30d04eea3856c6c889a210f1fa53c99299df24882afbf92","declaration":"export type AgentHarnessUserInputOption = AgentHarnessUserInputOption;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessUserInputOption","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"6224f51b4189f10a642ad7a8f50a902ec9c6a6a4f8dac0c0de5f7bc0271a27e5","declaration":"export type AgentHarnessUserInputPromptOptions = AgentHarnessUserInputPromptOptions;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessUserInputPromptOptions","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"d8f1d0d118e1536d1f442d11aeae5334a1ed7949a6c61824f547e12931278e1d","declaration":"export type AgentHarnessUserInputQuestion = AgentHarnessUserInputQuestion;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessUserInputQuestion","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"49056f1f65feb4085e19b1685d2cd42dfc22c222d53dd32531dbbfe559c9787e","declaration":"export type AgentHarnessV2 = AgentHarnessV2;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"b9cc9cb5302734ebcde16890889d21001f595151205b3105e7c6da221c415734","declaration":"export type AgentHarnessV2 = AgentHarnessV2;","entrypoint":"agent-harness-runtime","exportName":"AgentHarnessV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"6f2af683e6a31cde5a473c77e4f03b8f0ec0351a43ea6c0af979d5b394a33f95","declaration":"export type AgentMessage = AgentMessage;","entrypoint":"agent-harness-runtime","exportName":"AgentMessage","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"4ad0f830bf11a2db48faefb1439b7fbb08d9f22d22083f5e301327e1696f2310","declaration":"export type AgentToolResultMiddleware = AgentToolResultMiddleware;","entrypoint":"agent-harness-runtime","exportName":"AgentToolResultMiddleware","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"185abd2870d4df65e9f94623061a30167d17ef1279a395201fe429398bf23500","declaration":"export type AgentToolResultMiddlewareContext = AgentToolResultMiddlewareContext;","entrypoint":"agent-harness-runtime","exportName":"AgentToolResultMiddlewareContext","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} @@ -276,8 +276,8 @@ {"closureHash":"f30b66fd82f4cd61c60880c42a3b8f152ca64a2216036c99ab4129648ed329e7","declaration":"export type EmbeddedAgentCompactResult = EmbeddedAgentCompactResult;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedAgentCompactResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"1bbefad0193436f7340f9d0dec50be5d482973325c3c65170b1053dd05566f0a","declaration":"export type EmbeddedContextFile = EmbeddedContextFile;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedContextFile","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"f30b66fd82f4cd61c60880c42a3b8f152ca64a2216036c99ab4129648ed329e7","declaration":"export type EmbeddedPiCompactResult = EmbeddedAgentCompactResult;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedPiCompactResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"81f55150b97acd8afecf5636ae9479ef5047591f30fdd5b10ffc4cc67a000e82","declaration":"export type EmbeddedRunAttemptParams = EmbeddedRunAttemptParams;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedRunAttemptParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"2edd02e8f04850f8233a4ad2ec742a029d394fa04a970138c50438ba8cc0af79","declaration":"export type EmbeddedRunAttemptParamsV2 = EmbeddedRunAttemptParamsV2;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedRunAttemptParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"9b2159287cc6f60f75bf0b740c9135e4379c56dee8fd95987618f29d5e06f1c5","declaration":"export type EmbeddedRunAttemptParams = EmbeddedRunAttemptParams;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedRunAttemptParams","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"eab91b424611ec95d7039ae98650c677c8a52db27220a96d65bb3261ca0a4854","declaration":"export type EmbeddedRunAttemptParamsV2 = EmbeddedRunAttemptParamsV2;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedRunAttemptParamsV2","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"a6cd2eebe0d06e6578ec8438a207c5acda10ddf01041102d84e4c576a0b6a42f","declaration":"export type EmbeddedRunAttemptResult = EmbeddedRunAttemptResult;","entrypoint":"agent-harness-runtime","exportName":"EmbeddedRunAttemptResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"f6cb2d6411db22cb04220777720d97bcca977351de1cb344ce2ff6ab126a3a08","declaration":"export type ExecApprovalDecision = ExecApprovalDecision;","entrypoint":"agent-harness-runtime","exportName":"ExecApprovalDecision","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"7acb0dffc7bcda6c727e4afc49c955fa8e14f4f8b7b45d197ed794dfa0d73b8e","declaration":"export type ExecAutoReviewDecision = ExecAutoReviewDecision;","entrypoint":"agent-harness-runtime","exportName":"ExecAutoReviewDecision","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} @@ -293,7 +293,7 @@ {"closureHash":"d8a720bf250da8daf4dc6a52087aa8728ec33b650b3ad3906fcb40e184de66a0","declaration":"export type NativeHookRelayEvent = \"pre_tool_use\" | \"post_tool_use\" | \"permission_request\" | \"before_agent_finalize\";","entrypoint":"agent-harness-runtime","exportName":"NativeHookRelayEvent","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"cb72ca18e056e52e779d0bf5b67606e1dc160f225225b55937304541105027f4","declaration":"export type NativeHookRelayProcessResponse = NativeHookRelayProcessResponse;","entrypoint":"agent-harness-runtime","exportName":"NativeHookRelayProcessResponse","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"6ee390ae4784b987e22255c16884fb4171033bbfaefaef655e3dd2a2513814a5","declaration":"export type NativeHookRelayProvider = \"codex\";","entrypoint":"agent-harness-runtime","exportName":"NativeHookRelayProvider","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} -{"closureHash":"e6e52976c419b80b3dbff16e3cf4cbdd7fce4a0af717057740729bbcf5a9ec08","declaration":"export type NativeHookRelayRegistrationHandle = NativeHookRelayRegistrationHandle;","entrypoint":"agent-harness-runtime","exportName":"NativeHookRelayRegistrationHandle","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} +{"closureHash":"666d43d04fc27bb4d9b873b64a7d5ed3f44c40af1f2f47fc652130b73491947c","declaration":"export type NativeHookRelayRegistrationHandle = NativeHookRelayRegistrationHandle;","entrypoint":"agent-harness-runtime","exportName":"NativeHookRelayRegistrationHandle","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"90fea3155b5d32c22da90460fdaea94e4fd6895d1ca18613d5036ba9835153f9","declaration":"export type NodeListNode = NodeListNode;","entrypoint":"agent-harness-runtime","exportName":"NodeListNode","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"cefe1325c866f0cf7edbec5dcca2fe420237eedc0da172e54094e365894d205d","declaration":"export type NormalizedUsage = NormalizedUsage;","entrypoint":"agent-harness-runtime","exportName":"NormalizedUsage","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} {"closureHash":"25c6985c3bef2c6efc6a96e70af6d66fe073be0b0d311681cfc0dfc186528d9a","declaration":"export type OpenClawAgentToolResult = OpenClawAgentToolResult;","entrypoint":"agent-harness-runtime","exportName":"OpenClawAgentToolResult","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime","kind":"type","recordType":"export"} @@ -762,7 +762,7 @@ {"closureHash":"9fc7e45343d7bfbc6ed56d05c9f824e2f9cf6dafc3ca4b25437d1f65c2200727","declaration":"export function clearAccountEntryFields(params: { accounts?: Record; accountId: string; fields: string[]; isValueSet?: (value: unknown) => boolean; markClearedOnFieldPresence?: boolean; }): { nextAccounts?: Record; changed: boolean; cleared: boolean; };","entrypoint":"channel-core","exportName":"clearAccountEntryFields","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} {"closureHash":"f4e084b64b5376268885a73fe507aa5d72bee17a88ec2d7ae1d19b36ea5f1ccb","declaration":"export function createChannelConfigUiHints(params: { channelLabel: string; dmPolicy?: { channelKey: string; includeLegacyNestedPolicy?: boolean; legacyNestedPolicyOrder?: \"before\" | \"after\"; }; configWrites?: boolean; mentionPatterns?: { targetDescription: string; policyTargetDescription?: string; policyNote?: string; denyNote?: string; }; nativeCommands?: boolean; implicitMentions?: boolean; progress?: { includeCommentary?: boolean; commentaryOrder?: \"before-command\" | \"after-command\"; labels?: \"openclaw\"; titleWording?: boolean; }; streaming?: Partial>; retry?: boolean; }): HintMap;","entrypoint":"channel-core","exportName":"createChannelConfigUiHints","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} {"closureHash":"9e93bcdfc6299e65ef2a3d267047a9e8c856f0b2f8e9b79d570fb2a4cd238c74","declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions; pairing?: ChannelPairingAdapter | ChatChannelPairingOptions; threading?: ChannelThreadingAdapter | ChatChannelThreadingOptions; outbound?: ChannelOutboundAdapter | ChatChannelAttachedOutboundOptions; }): ChannelPlugin;","entrypoint":"channel-core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} -{"closureHash":"e5a6ebba23854bb417f7cbc3bcaf490fbb3c71a58766bede7632b632590bc556","declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerCliMetadata, registerFull, registerCapabilities, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"channel-core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} +{"closureHash":"1a51a3485567b0779d46606fde9857807055194003f4d1e6c5f1f3cc8286fe88","declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerCliMetadata, registerFull, registerCapabilities, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"channel-core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} {"closureHash":"0e0a9f6b22c433bdb1165e7707b1f9a1ddac1a544a8add00b503048cf157921f","declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"channel-core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} {"closureHash":"8895f7af11b994dcd383ebc45d8cfb3c3b1e6d88a64a001b04f604a83f77e517","declaration":"export function parseOptionalDelimitedEntries(value?: string): string[] | undefined;","entrypoint":"channel-core","exportName":"parseOptionalDelimitedEntries","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} {"closureHash":"3f97534b10d08eda6a556044eaba623b3744b200a2033877e48d2f2ca924d298","declaration":"export function recoverCurrentThreadSessionId(params: { route: ChannelOutboundSessionRoute; currentSessionKey?: string | null; canRecover?: (context: ThreadAwareOutboundSessionRouteRecoveryContext) => boolean; }): string | undefined;","entrypoint":"channel-core","exportName":"recoverCurrentThreadSessionId","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"function","recordType":"export"} @@ -774,24 +774,24 @@ {"closureHash":"8126b651a679d5435eaa5d346916792c3600e5f0f75d8492b077dd361854f7dd","declaration":"export type ChannelOutboundSessionRouteParams = { cfg: OpenClawConfig; agentId: string; accountId?: string | null; target: string; currentSessionKey?: string; resolvedTarget?: { to: string; kind: ChannelDirectoryEntryKind | \"channel\"; display?: string; source: \"normalized\" | \"directory\"; }; replyToId?: string | null; threadId?: string | number | null;};","entrypoint":"channel-core","exportName":"ChannelOutboundSessionRouteParams","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"type","recordType":"export"} {"closureHash":"d02e5c7a995847180696d2a05d8ea092b364ef32b36753e4091fc97ffdaf44ab","declaration":"export type ChannelPlugin = ChannelPlugin;","entrypoint":"channel-core","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"type","recordType":"export"} {"closureHash":"8054ffbe71b91afced900ef8a06f5e368ac10c4f99ffc1c6f538a802856f61c6","declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"channel-core","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"type","recordType":"export"} -{"closureHash":"040ee86402db43973dab92c67edd53fa7183ff96d7e4d485f7a262eadfc25178","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"channel-core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"type","recordType":"export"} +{"closureHash":"1b8024c377c0a8dbead0571adb50ce9b8a5eef2d5cb16b23035579caee71137a","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"channel-core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"type","recordType":"export"} {"closureHash":"aac070e9a0df4c3621b12f27426c2caccf3a83458ac98261726005c8ffb311ac","declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"channel-core","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"type","recordType":"export"} {"closureHash":"b6afda24ea57273cc4c9ae35a23b327e85e1b996dafb2ffdce5e339333de0312","declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"channel-core","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/channel-core","kind":"type","recordType":"export"} {"category":"channel","entrypoint":"channel-dm-policy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy","recordType":"module"} {"closureHash":"821fe6ccd0d9323322bf70c4d1ca5697071a0e32032f44c1e14157162c2c729d","declaration":"export function createChannelDmPolicy(params: CreateChannelDmPolicyParams): ChannelSetupDmPolicy & { promptAllowFrom: NonNullable; };","entrypoint":"channel-dm-policy","exportName":"createChannelDmPolicy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy","kind":"function","recordType":"export"} {"category":null,"entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","recordType":"module"} -{"closureHash":"58f87c4e81cab32e632786f72e1caf889d24c3b53a7187fda1188e6e2e3c934c","declaration":"export function defineBundledChannelEntry({ id, name, description, importMetaUrl, plugin, outbound, secrets, configSchema, runtime, accountInspect, features, registerCliMetadata, registerFull, registerCapabilities, }: DefineBundledChannelEntryOptions): BundledChannelEntryContract;","entrypoint":"channel-entry-contract","exportName":"defineBundledChannelEntry","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"function","recordType":"export"} -{"closureHash":"a41a19783cf0ed165e69dd976b67fe251d5bcded045013f1a0caa51c14aecca4","declaration":"export function defineBundledChannelSetupEntry({ importMetaUrl, plugin, secrets, runtime, legacyStateMigrations, legacySessionSurface, registerSetupRuntime, features, }: DefineBundledChannelSetupEntryOptions): BundledChannelSetupEntryContract;","entrypoint":"channel-entry-contract","exportName":"defineBundledChannelSetupEntry","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"function","recordType":"export"} +{"closureHash":"180da9ad9847928baccbd2a6ab6a0f276264ddc65bf47f2969e90b7c6989df98","declaration":"export function defineBundledChannelEntry({ id, name, description, importMetaUrl, plugin, outbound, secrets, configSchema, runtime, accountInspect, features, registerCliMetadata, registerFull, registerCapabilities, }: DefineBundledChannelEntryOptions): BundledChannelEntryContract;","entrypoint":"channel-entry-contract","exportName":"defineBundledChannelEntry","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"function","recordType":"export"} +{"closureHash":"69a58b370846a1d81731163c65c1f09c1008634c518156b7e2466110b49cb12b","declaration":"export function defineBundledChannelSetupEntry({ importMetaUrl, plugin, secrets, runtime, legacyStateMigrations, legacySessionSurface, registerSetupRuntime, features, }: DefineBundledChannelSetupEntryOptions): BundledChannelSetupEntryContract;","entrypoint":"channel-entry-contract","exportName":"defineBundledChannelSetupEntry","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"function","recordType":"export"} {"closureHash":"cd150287d7e286e7a0d7d6caeb05d0a1efa63b5f351f75f69ba932510a108afd","declaration":"export function loadBundledEntryExportSync(importMetaUrl: string, reference: BundledEntryModuleRef, options?: BundledEntryModuleLoadOptions): T;","entrypoint":"channel-entry-contract","exportName":"loadBundledEntryExportSync","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"function","recordType":"export"} {"closureHash":"7781a3d570d08929af5b3b6cffbb9ef4b9c8e8f1bed55234e0015a681868c812","declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"channel-entry-contract","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} -{"closureHash":"6ac69d76b9ad249ed738c9f69af92543dc772d1061007c65120f65b27bbddef9","declaration":"export type BundledChannelEntryContract = BundledChannelEntryContract;","entrypoint":"channel-entry-contract","exportName":"BundledChannelEntryContract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} +{"closureHash":"da19f59399a8f35e9fae01dbecfb47bfa21eb759d308692c36911671155df2ea","declaration":"export type BundledChannelEntryContract = BundledChannelEntryContract;","entrypoint":"channel-entry-contract","exportName":"BundledChannelEntryContract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"closureHash":"f4b82dfd995bd6f08b232984743389d1c854866cb845a8dbb3605acb28818688","declaration":"export type BundledChannelEntryFeatures = BundledChannelEntryFeatures;","entrypoint":"channel-entry-contract","exportName":"BundledChannelEntryFeatures","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"closureHash":"960693591b9bdc974bf3f0097076d90490078db3dceb35349a4f851736cde4fc","declaration":"export type BundledChannelLegacySessionSurface = BundledChannelLegacySessionSurface;","entrypoint":"channel-entry-contract","exportName":"BundledChannelLegacySessionSurface","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"closureHash":"edf79e6a736e5c4977e3b86ef21e7fdf0c0b2bc7deacd8edbee2ca76cb8cef43","declaration":"export type BundledChannelLegacyStateMigrationDetector = BundledChannelLegacyStateMigrationDetector;","entrypoint":"channel-entry-contract","exportName":"BundledChannelLegacyStateMigrationDetector","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} -{"closureHash":"c57ba2ad5b931165198d402a9afa6caebd97502136f4c329a77ce954ff60c43e","declaration":"export type BundledChannelSetupEntryContract = BundledChannelSetupEntryContract;","entrypoint":"channel-entry-contract","exportName":"BundledChannelSetupEntryContract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} +{"closureHash":"e570488099a83f3004306bdd7bd9330f91bd5667d8341187e628645c6e2eac58","declaration":"export type BundledChannelSetupEntryContract = BundledChannelSetupEntryContract;","entrypoint":"channel-entry-contract","exportName":"BundledChannelSetupEntryContract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"closureHash":"24679ecdf2ff34157b6345a37b58ec1363ec8cb1969b522599d0d92bb7a113b7","declaration":"export type BundledChannelSetupEntryFeatures = BundledChannelSetupEntryFeatures;","entrypoint":"channel-entry-contract","exportName":"BundledChannelSetupEntryFeatures","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"closureHash":"367219c821915e5be4d1ba3a86754475a5f8a4194512f0401de3be11cc649c60","declaration":"export type BundledEntryModuleLoadOptions = BundledEntryModuleLoadOptions;","entrypoint":"channel-entry-contract","exportName":"BundledEntryModuleLoadOptions","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} -{"closureHash":"903c1b42779157a2012e8678355a573ed76a1742589ee31c1fefe3d895c69195","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"channel-entry-contract","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} +{"closureHash":"135188028650bcd2e7c0f3d83ac05ac9f9101015f0fe0d07ac15e0f3719716c1","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"channel-entry-contract","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"closureHash":"42c4d242826c5c12b543ddb1a312de11ebfa0208fe77bc01ac36af6d2bf36f14","declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"channel-entry-contract","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"closureHash":"6f006c41d66e65636985a8ecf94d378e8f8778cf5200a6bda440265b52f6db2a","declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"channel-entry-contract","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract","kind":"type","recordType":"export"} {"category":null,"entrypoint":"channel-feedback","importSpecifier":"openclaw/plugin-sdk/channel-feedback","recordType":"module"} @@ -1103,7 +1103,7 @@ {"closureHash":"9555a73ba2717bba0bf94888017be4cecef2c51cc3887ed34f8d8ef3c79d6abf","declaration":"export const DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS: number;","entrypoint":"channel-message","exportName":"DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"const","recordType":"export"} {"closureHash":"3a2df3ab656149630c004d2d9f71b8ceda99aa81eb6b72b0cc7b3982ed0ce044","declaration":"export const DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS: 8;","entrypoint":"channel-message","exportName":"DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"const","recordType":"export"} {"closureHash":"38c735a14c65067aa7825390d1c5c8876c651d0603e84980dfaf947f5235747a","declaration":"export const INGRESS_CLAIM_PROCESS_ID: string;","entrypoint":"channel-message","exportName":"INGRESS_CLAIM_PROCESS_ID","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"const","recordType":"export"} -{"closureHash":"2a66734fbd24ec2cb0531134931d65abedbeea3520c65487eb56f5a9a5ba15c8","declaration":"export const deliverInboundReplyWithMessageSendContext: (params: DurableInboundReplyDeliveryParams) => Promise<{ status: \"not_applicable\"; reason: \"non_final\";} | { status: \"unsupported\"; reason: \"missing_channel\" | \"missing_target\" | \"missing_outbound_handler\" | \"capability_mismatch\"; capability?: DurableFinalDeliveryRequirement;} | { status: \"handled_visible\"; delivery: ChannelDeliveryResult;} | { status: \"handled_no_send\"; reason: \"no_visible_result\"; delivery: ChannelDeliveryResult;} | { status: \"failed\"; error: unknown; sentBeforeError?: true;}>;","entrypoint":"channel-message","exportName":"deliverInboundReplyWithMessageSendContext","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"const","recordType":"export"} +{"closureHash":"51420fdb3df6f8e935b7d58445af55207410ceafb82c15ce744d8ea5734bdf60","declaration":"export const deliverInboundReplyWithMessageSendContext: (params: DurableInboundReplyDeliveryParams) => Promise<{ status: \"not_applicable\"; reason: \"non_final\";} | { status: \"unsupported\"; reason: \"missing_channel\" | \"missing_target\" | \"missing_outbound_handler\" | \"capability_mismatch\"; capability?: DurableFinalDeliveryRequirement;} | { status: \"handled_visible\"; delivery: ChannelDeliveryResult;} | { status: \"handled_no_send\"; reason: \"no_visible_result\"; delivery: ChannelDeliveryResult;} | { status: \"failed\"; error: unknown; sentBeforeError?: true;}>;","entrypoint":"channel-message","exportName":"deliverInboundReplyWithMessageSendContext","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"const","recordType":"export"} {"closureHash":"2c52eff200a5b9bd2d93ee72209c2626bbe165f59b538591db661b94773023b0","declaration":"export type AgentPlanStep = AgentPlanStep;","entrypoint":"channel-message","exportName":"AgentPlanStep","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"type","recordType":"export"} {"closureHash":"1e8bcb2c8d8f5327f5b81b8c217f722f090dccba21ba3943324010f4f80a5a50","declaration":"export type AgentPlanStepStatus = AgentPlanStepStatus;","entrypoint":"channel-message","exportName":"AgentPlanStepStatus","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"type","recordType":"export"} {"closureHash":"b036fc26e5330dfd7da1af8839bb3c23e85283b9e16e252a659dd4bb0c35cd5e","declaration":"export type ChannelDeliveryStreamingConfig = ChannelDeliveryStreamingConfig;","entrypoint":"channel-message","exportName":"ChannelDeliveryStreamingConfig","importSpecifier":"openclaw/plugin-sdk/channel-message","kind":"type","recordType":"export"} @@ -1232,7 +1232,7 @@ {"closureHash":"9555a73ba2717bba0bf94888017be4cecef2c51cc3887ed34f8d8ef3c79d6abf","declaration":"export const DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS: number;","entrypoint":"channel-outbound","exportName":"DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"const","recordType":"export"} {"closureHash":"3a2df3ab656149630c004d2d9f71b8ceda99aa81eb6b72b0cc7b3982ed0ce044","declaration":"export const DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS: 8;","entrypoint":"channel-outbound","exportName":"DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"const","recordType":"export"} {"closureHash":"38c735a14c65067aa7825390d1c5c8876c651d0603e84980dfaf947f5235747a","declaration":"export const INGRESS_CLAIM_PROCESS_ID: string;","entrypoint":"channel-outbound","exportName":"INGRESS_CLAIM_PROCESS_ID","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"const","recordType":"export"} -{"closureHash":"2a66734fbd24ec2cb0531134931d65abedbeea3520c65487eb56f5a9a5ba15c8","declaration":"export const deliverInboundReplyWithMessageSendContext: (params: DurableInboundReplyDeliveryParams) => Promise<{ status: \"not_applicable\"; reason: \"non_final\";} | { status: \"unsupported\"; reason: \"missing_channel\" | \"missing_target\" | \"missing_outbound_handler\" | \"capability_mismatch\"; capability?: DurableFinalDeliveryRequirement;} | { status: \"handled_visible\"; delivery: ChannelDeliveryResult;} | { status: \"handled_no_send\"; reason: \"no_visible_result\"; delivery: ChannelDeliveryResult;} | { status: \"failed\"; error: unknown; sentBeforeError?: true;}>;","entrypoint":"channel-outbound","exportName":"deliverInboundReplyWithMessageSendContext","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"const","recordType":"export"} +{"closureHash":"51420fdb3df6f8e935b7d58445af55207410ceafb82c15ce744d8ea5734bdf60","declaration":"export const deliverInboundReplyWithMessageSendContext: (params: DurableInboundReplyDeliveryParams) => Promise<{ status: \"not_applicable\"; reason: \"non_final\";} | { status: \"unsupported\"; reason: \"missing_channel\" | \"missing_target\" | \"missing_outbound_handler\" | \"capability_mismatch\"; capability?: DurableFinalDeliveryRequirement;} | { status: \"handled_visible\"; delivery: ChannelDeliveryResult;} | { status: \"handled_no_send\"; reason: \"no_visible_result\"; delivery: ChannelDeliveryResult;} | { status: \"failed\"; error: unknown; sentBeforeError?: true;}>;","entrypoint":"channel-outbound","exportName":"deliverInboundReplyWithMessageSendContext","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"const","recordType":"export"} {"closureHash":"2c52eff200a5b9bd2d93ee72209c2626bbe165f59b538591db661b94773023b0","declaration":"export type AgentPlanStep = AgentPlanStep;","entrypoint":"channel-outbound","exportName":"AgentPlanStep","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"type","recordType":"export"} {"closureHash":"1e8bcb2c8d8f5327f5b81b8c217f722f090dccba21ba3943324010f4f80a5a50","declaration":"export type AgentPlanStepStatus = AgentPlanStepStatus;","entrypoint":"channel-outbound","exportName":"AgentPlanStepStatus","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"type","recordType":"export"} {"closureHash":"b036fc26e5330dfd7da1af8839bb3c23e85283b9e16e252a659dd4bb0c35cd5e","declaration":"export type ChannelDeliveryStreamingConfig = ChannelDeliveryStreamingConfig;","entrypoint":"channel-outbound","exportName":"ChannelDeliveryStreamingConfig","importSpecifier":"openclaw/plugin-sdk/channel-outbound","kind":"type","recordType":"export"} @@ -1298,7 +1298,7 @@ {"closureHash":"cd6b0d3f962c8cb40d405e92bb06bb8e9285b386200897f550d21036022262c1","declaration":"export const PAIRING_APPROVED_MESSAGE: \"✅ OpenClaw access approved. Send a message to start chatting.\";","entrypoint":"channel-plugin-common","exportName":"PAIRING_APPROVED_MESSAGE","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common","kind":"const","recordType":"export"} {"closureHash":"712435252d2fe5135542ca6b5a4375a6edea24e83829338011dc6ae7e9802fbb","declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"channel-plugin-common","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common","kind":"type","recordType":"export"} {"closureHash":"d02e5c7a995847180696d2a05d8ea092b364ef32b36753e4091fc97ffdaf44ab","declaration":"export type ChannelPlugin = ChannelPlugin;","entrypoint":"channel-plugin-common","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common","kind":"type","recordType":"export"} -{"closureHash":"040ee86402db43973dab92c67edd53fa7183ff96d7e4d485f7a262eadfc25178","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"channel-plugin-common","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common","kind":"type","recordType":"export"} +{"closureHash":"1b8024c377c0a8dbead0571adb50ce9b8a5eef2d5cb16b23035579caee71137a","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"channel-plugin-common","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common","kind":"type","recordType":"export"} {"closureHash":"b6afda24ea57273cc4c9ae35a23b327e85e1b996dafb2ffdce5e339333de0312","declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"channel-plugin-common","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common","kind":"type","recordType":"export"} {"category":null,"entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy","recordType":"module"} {"closureHash":"017ab29d16d0f3d02d60f3513e65a373868d26496bc93d020d8fc8c17fd875f4","declaration":"export function buildAccountScopedDmSecurityPolicy(params: { cfg: OpenClawConfig; channelKey: string; accountId?: string | null; fallbackAccountId?: string | null; policy?: string | null; allowFrom?: Array | null; defaultPolicy?: string; allowFromPathSuffix?: string; policyPathSuffix?: string; approveChannelId?: string; approveHint?: string; normalizeEntry?: (raw: string) => string; inheritSharedDefaultsFromDefaultAccount?: boolean; }): ChannelSecurityDmPolicy;","entrypoint":"channel-policy","exportName":"buildAccountScopedDmSecurityPolicy","importSpecifier":"openclaw/plugin-sdk/channel-policy","kind":"function","recordType":"export"} @@ -1897,8 +1897,8 @@ {"closureHash":"9e93bcdfc6299e65ef2a3d267047a9e8c856f0b2f8e9b79d570fb2a4cd238c74","declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions; pairing?: ChannelPairingAdapter | ChatChannelPairingOptions; threading?: ChannelThreadingAdapter | ChatChannelThreadingOptions; outbound?: ChannelOutboundAdapter | ChatChannelAttachedOutboundOptions; }): ChannelPlugin;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} {"closureHash":"785b0f750c5bd20f9f3ca081886a5af79ee0e4d2c38f78f769bf994b234f5076","declaration":"export function createDedupeCache(options: DedupeCacheOptions): DedupeCache;","entrypoint":"core","exportName":"createDedupeCache","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} {"closureHash":"e2df8d235401aebf58eb71652fbeaedb42ca00aaa5042a76909ff2c48ba6fd54","declaration":"export function createSubsystemLogger(subsystem: string): SubsystemLogger;","entrypoint":"core","exportName":"createSubsystemLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} -{"closureHash":"e5a6ebba23854bb417f7cbc3bcaf490fbb3c71a58766bede7632b632590bc556","declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerCliMetadata, registerFull, registerCapabilities, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} -{"closureHash":"56edcfdc47d0e6f005a60668136834383ce95fe466b88cbce4678fa0ebf35631","declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, reload, nodeHostCommands, securityAuditCollectors, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} +{"closureHash":"1a51a3485567b0779d46606fde9857807055194003f4d1e6c5f1f3cc8286fe88","declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerCliMetadata, registerFull, registerCapabilities, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} +{"closureHash":"f04c131ad85b7755df9998b3734af31eb0e39d74941d3cddbbc717a1836ad665","declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, reload, nodeHostCommands, securityAuditCollectors, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} {"closureHash":"0e0a9f6b22c433bdb1165e7707b1f9a1ddac1a544a8add00b503048cf157921f","declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} {"closureHash":"fed1ccba51158f3c9e0a46ec9a9d747944d984842b90bfe9f3616486fc5e42f4","declaration":"export function delegateCompactionToRuntime(params: Parameters[0]): Promise;","entrypoint":"core","exportName":"delegateCompactionToRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} {"closureHash":"2c282e0e308f9442bca7cdeb8e89a097531b1d54a692c9d86e5f39a8db1c2537","declaration":"export function deleteAccountFromConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; clearBaseFields?: string[]; }): OpenClawConfig;","entrypoint":"core","exportName":"deleteAccountFromConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} @@ -1945,7 +1945,7 @@ {"closureHash":"2e6ce1f4c1692270a00dc0cf08e2752a26739c3143f8091cb270063c6034605d","declaration":"export function tryReadSecretFileSync(filePath: string | undefined, label: string, options: CredentialFileReadOptions): string | undefined;\nexport function tryReadSecretFileSync(filePath: string | undefined, label: string, options?: FsSafeSecretFileReadOptions): string | undefined;\nexport function tryReadSecretFileSync(filePath: string, label: string, options: FsSafeSecretFileReadOptions | undefined, diagnostic: { configPath: string; }): ConfiguredCredentialResult;\nexport function tryReadSecretFileSync(filePath: string | undefined, label: string, options: FsSafeSecretFileReadOptions | undefined, diagnostic: { configPath: string; }): CredentialResult;","entrypoint":"core","exportName":"tryReadSecretFileSync","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export"} {"closureHash":"b40933a11984425d14a7a479bda0d2cd14cbfa9371164774f39bebbf12861df7","declaration":"export const DEFAULT_ACCOUNT_ID: \"default\";","entrypoint":"core","exportName":"DEFAULT_ACCOUNT_ID","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export"} {"closureHash":null,"declaration":"export const DEFAULT_SECRET_FILE_MAX_BYTES: number;","entrypoint":"core","exportName":"DEFAULT_SECRET_FILE_MAX_BYTES","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export"} -{"closureHash":"fd9e44e0e924c462451e966215da05d67067abfb489a1e1f64cac2b7279c2f65","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"core","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} +{"closureHash":"7bc7817206b083bb475cc0f3862c9b552f0053de3f325f0b846a284e0c1c73f5","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"core","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"d2ff4de9ee3fd111c2cc64a3ee7e557d9f161af1b6b437ee7886bcb16e426317","declaration":"export type AgentPromptGuidance = AgentPromptGuidance;","entrypoint":"core","exportName":"AgentPromptGuidance","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"2841127a90e04467adfea8126cbf03655f44ddb089ad9e0d48833c2beea39cbd","declaration":"export type AgentPromptGuidanceEntry = AgentPromptGuidanceEntry;","entrypoint":"core","exportName":"AgentPromptGuidanceEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"aa0412acefe62caff12e5271ead0545504874e824380dc21cfc0566388a05f33","declaration":"export type AgentPromptSurfaceKind = \"openclaw_main\" | \"pi_main\" | \"codex_app_server\" | \"cli_backend\" | \"acp_backend\" | \"subagent\";","entrypoint":"core","exportName":"AgentPromptSurfaceKind","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} @@ -1977,10 +1977,10 @@ {"closureHash":"54ea426dde51859b572b4c1079d2b70b412096f73da2b8c265f2749d9f01cbb5","declaration":"export type NormalizedLocation = NormalizedLocation;","entrypoint":"core","exportName":"NormalizedLocation","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"8054ffbe71b91afced900ef8a06f5e368ac10c4f99ffc1c6f538a802856f61c6","declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"core","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"5189698d102b8b064eefe1442b008312973c3994fd6b203d2205725d7ee9d118","declaration":"export type OpenClawPluginActiveModelContext = OpenClawPluginActiveModelContext;","entrypoint":"core","exportName":"OpenClawPluginActiveModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} -{"closureHash":"040ee86402db43973dab92c67edd53fa7183ff96d7e4d485f7a262eadfc25178","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} +{"closureHash":"1b8024c377c0a8dbead0571adb50ce9b8a5eef2d5cb16b23035579caee71137a","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"e89ab2a5316dba762220c00ee423b352ce66153297aaa1abd4299fe59d0eb9b4","declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"core","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"365beb5cee974639c2f35fc7a39088721ca8e9c618442830298b98033e9c768c","declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} -{"closureHash":"b5ed3632d2bb3365b86b4dfaad78017f027eb09a2eebc623361452918eb30b4e","declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"core","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} +{"closureHash":"d03dbf592e8e12766b5421c44cee51d58322518775574d50a36941ed9dabec05","declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"core","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"a7ed83f0924f818b23cecb5a1f3591ff28b9499ccac9667a2b8505c2bc608ea9","declaration":"export type OpenClawPluginGatewayEventScope = OpenClawPluginGatewayEventScope;","entrypoint":"core","exportName":"OpenClawPluginGatewayEventScope","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"f81f224d9b837e6353b7a546c5a76b124bcf568fd1f1fd0c6881fda30c53120a","declaration":"export type OpenClawPluginGatewayEvents = OpenClawPluginGatewayEvents;","entrypoint":"core","exportName":"OpenClawPluginGatewayEvents","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} {"closureHash":"ddc2738dd0ece94ddde9a7c655f92c15ec6416645ecf932d4aa404c7b7761159","declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"core","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export"} @@ -2215,7 +2215,7 @@ {"closureHash":"0db968dad99d84ecef5202167daadd092e3b4f456270283d3fd61585ac2bbd5a","declaration":"export type DiscordComponentSendResult = DiscordComponentSendResult;","entrypoint":"discord","exportName":"DiscordComponentSendResult","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} {"closureHash":"084b46d7dd510eb182a4705559251e824220b57dbfa6dc54392507b72ba1815e","declaration":"export type DiscordOutboundTargetResolution = DiscordOutboundTargetResolution;","entrypoint":"discord","exportName":"DiscordOutboundTargetResolution","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} {"closureHash":"8054ffbe71b91afced900ef8a06f5e368ac10c4f99ffc1c6f538a802856f61c6","declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"discord","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} -{"closureHash":"040ee86402db43973dab92c67edd53fa7183ff96d7e4d485f7a262eadfc25178","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"discord","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} +{"closureHash":"1b8024c377c0a8dbead0571adb50ce9b8a5eef2d5cb16b23035579caee71137a","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"discord","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} {"closureHash":"b6afda24ea57273cc4c9ae35a23b327e85e1b996dafb2ffdce5e339333de0312","declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"discord","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} {"closureHash":"6b9967e919c1cd420c8356b4f9d40dbc81932b4b15a733eecdcfb2ad84f8e5d8","declaration":"export type ResolvedDiscordAccount = ResolvedDiscordAccount;","entrypoint":"discord","exportName":"ResolvedDiscordAccount","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} {"closureHash":"6bed5ca896b7034117e4757e2c6b4111dd5293dbad9416e3828dd6afa4e858ce","declaration":"export type ThreadBindingRecord = ThreadBindingRecord;","entrypoint":"discord","exportName":"ThreadBindingRecord","importSpecifier":"openclaw/plugin-sdk/discord","kind":"type","recordType":"export"} @@ -2360,7 +2360,7 @@ {"closureHash":"98e0cc16352b7dfcceb4957f8cd2acfe3c2b3acb223c42a5cfb885e088fcb04c","declaration":"export function resolveInboundReplyDispatchCounts(result: ChannelTurnDispatchResultLike): Record;","entrypoint":"inbound-reply-dispatch","exportName":"resolveInboundReplyDispatchCounts","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"function","recordType":"export"} {"closureHash":"906285cbb342f769c9af45c6f0c0956db3ede3ebdc882322bcae94fb34f7470b","declaration":"export function runChannelInboundEvent(params: RunChannelTurnParams): Promise>;\nexport function runChannelInboundEvent(params: ChannelInboundEventRunnerParams): Promise>;","entrypoint":"inbound-reply-dispatch","exportName":"runChannelInboundEvent","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"function","recordType":"export"} {"closureHash":"2c68496a174872b31aaf1ac8fad2789deb79d490c5ffbcb157325f1c95d1b51f","declaration":"export function runPreparedInboundReply(params: PreparedChannelTurn): Promise>;","entrypoint":"inbound-reply-dispatch","exportName":"runPreparedInboundReply","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"function","recordType":"export"} -{"closureHash":"2a66734fbd24ec2cb0531134931d65abedbeea3520c65487eb56f5a9a5ba15c8","declaration":"export const deliverInboundReplyWithMessageSendContext: (params: DurableInboundReplyDeliveryParams) => Promise<{ status: \"not_applicable\"; reason: \"non_final\";} | { status: \"unsupported\"; reason: \"missing_channel\" | \"missing_target\" | \"missing_outbound_handler\" | \"capability_mismatch\"; capability?: DurableFinalDeliveryRequirement;} | { status: \"handled_visible\"; delivery: ChannelDeliveryResult;} | { status: \"handled_no_send\"; reason: \"no_visible_result\"; delivery: ChannelDeliveryResult;} | { status: \"failed\"; error: unknown; sentBeforeError?: true;}>;","entrypoint":"inbound-reply-dispatch","exportName":"deliverInboundReplyWithMessageSendContext","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"const","recordType":"export"} +{"closureHash":"51420fdb3df6f8e935b7d58445af55207410ceafb82c15ce744d8ea5734bdf60","declaration":"export const deliverInboundReplyWithMessageSendContext: (params: DurableInboundReplyDeliveryParams) => Promise<{ status: \"not_applicable\"; reason: \"non_final\";} | { status: \"unsupported\"; reason: \"missing_channel\" | \"missing_target\" | \"missing_outbound_handler\" | \"capability_mismatch\"; capability?: DurableFinalDeliveryRequirement;} | { status: \"handled_visible\"; delivery: ChannelDeliveryResult;} | { status: \"handled_no_send\"; reason: \"no_visible_result\"; delivery: ChannelDeliveryResult;} | { status: \"failed\"; error: unknown; sentBeforeError?: true;}>;","entrypoint":"inbound-reply-dispatch","exportName":"deliverInboundReplyWithMessageSendContext","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"const","recordType":"export"} {"closureHash":"add3eb2782f44af1661863d7f50fa714fd56e0f66aa3ef7b12c78680c690904a","declaration":"export type AssembledInboundReply = AssembledChannelTurn;","entrypoint":"inbound-reply-dispatch","exportName":"AssembledInboundReply","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"type","recordType":"export"} {"closureHash":"3de0d6d1bb8f9a83bd1c6e57aa8618e3fea71054236cd530c555e348bd0b7702","declaration":"export type ChannelBotLoopProtectionFacts = ChannelBotLoopProtectionFacts;","entrypoint":"inbound-reply-dispatch","exportName":"ChannelBotLoopProtectionFacts","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"type","recordType":"export"} {"closureHash":"f0da845bbdf49b30f6718a4b7ff012a0536deb98f65418583555dc90825797ca","declaration":"export type ChannelInboundDroppedHistoryOptions = ChannelTurnDroppedHistoryOptions;","entrypoint":"inbound-reply-dispatch","exportName":"ChannelInboundDroppedHistoryOptions","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch","kind":"type","recordType":"export"} @@ -3195,19 +3195,19 @@ {"closureHash":"b53dc03a1e264386e1323ea35581cd93833ce98530ac39da4002f566fde1ef7d","declaration":"export function callMeetingBrowserProxyOnNode(params: { runtime: PluginRuntime; adapter: NodeAdapter; nodeId: string; } & MeetingBrowserRequestParams): Promise;","entrypoint":"meeting-runtime","exportName":"callMeetingBrowserProxyOnNode","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"c5fcd1d157edcb4773235adfaf8051e3ec7ca428cb7916b6d55f2511affab56c","declaration":"export function convertMeetingBridgeAudioForStt(audio: Buffer, audioFormat: MeetingRealtimeAudioFormat): Buffer;","entrypoint":"meeting-runtime","exportName":"convertMeetingBridgeAudioForStt","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"aa9063c27c5ff714d3653571c4a2a71e9d2eea5609b161f03e4738e15411358b","declaration":"export function convertMeetingTtsAudioForBridge(audio: Buffer, sampleRate: number, audioFormat: MeetingRealtimeAudioFormat, outputFormat?: string, platformName?: string): Buffer;","entrypoint":"meeting-runtime","exportName":"convertMeetingTtsAudioForBridge","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} -{"closureHash":"48ef15d0478b92dd01264bb153e45f674a7e2087a48569a47e5bd3abfa0e6614","declaration":"export function createLocalMeetingRealtimeAudioTransport(params: { inputCommand: string[]; outputCommand: string[]; bargeInInputCommand?: string[]; bargeInRmsThreshold: number; bargeInPeakThreshold: number; bargeInCooldownMs: number; logger: RuntimeLogger; logScope: string; audioFormat?: MeetingRealtimeAudioFormat; spawn?: MeetingRealtimeAudioSpawn; }): MeetingRealtimeAudioTransport;","entrypoint":"meeting-runtime","exportName":"createLocalMeetingRealtimeAudioTransport","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} +{"closureHash":"5e9b88a3c2739386e3732d276d95ebd662a328baa5a038bf86243a079aed98ec","declaration":"export function createLocalMeetingRealtimeAudioTransport(params: { inputCommand: string[]; outputCommand: string[]; bargeInInputCommand?: string[]; bargeInRmsThreshold: number; bargeInPeakThreshold: number; bargeInCooldownMs: number; logger: RuntimeLogger; logScope: string; audioFormat?: MeetingRealtimeAudioFormat; spawn?: MeetingRealtimeAudioSpawn; }): MeetingRealtimeAudioTransport;","entrypoint":"meeting-runtime","exportName":"createLocalMeetingRealtimeAudioTransport","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"687918ad008a189bf7049c2f7addb6d932fcda4bf6ccccf80913734229c769ec","declaration":"export function createMeetingBrowserNodeCaller(params: { runtime: PluginRuntime; adapter: NodeAdapter; nodeId: string; }): MeetingBrowserRequestCaller;","entrypoint":"meeting-runtime","exportName":"createMeetingBrowserNodeCaller","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"f244ca8c47f556c9f388953cf9a7581c7d4d413af93e0dfa1462779e6a687a7c","declaration":"export function createMeetingBrowserNodeInvokePolicy(options: MeetingBrowserNodePolicyOptions): OpenClawPluginNodeInvokePolicy;","entrypoint":"meeting-runtime","exportName":"createMeetingBrowserNodeInvokePolicy","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"1aa4e3ff39a2cbe162421c62b1fa4a362973381d4d9ff15c671249b1ebd566bc","declaration":"export function createMeetingNodeHost(options: MeetingNodeHostOptions): { handleCommand(paramsJSON?: string | null): Promise; };","entrypoint":"meeting-runtime","exportName":"createMeetingNodeHost","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} -{"closureHash":"50d664bea3c18c28dda2928be888f61701c496415796ca735d118c5b64cf353f","declaration":"export function createMeetingRealtimeEngineBindings(params: { platform: MeetingPlatformRuntimeMetadata; config: { realtime: { agentId?: string; toolPolicy: RealtimeVoiceAgentConsultToolPolicy; }; }; fullConfig: OpenClawConfig; runtime: PluginRuntime; logger: RuntimeLogger; }): { platform: MeetingRuntimePlatform; consultAgent: (consult: MeetingAgentConsultParams) => Promise<{ text: string; }>; tools: RealtimeVoiceTool[]; handleToolCall: (call: MeetingRealtimeToolCallParams) => Promise; };","entrypoint":"meeting-runtime","exportName":"createMeetingRealtimeEngineBindings","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} +{"closureHash":"d98c78ef2b68fcd013999189ce6b527c905ca3c72d23f282fa4d068baf78e93b","declaration":"export function createMeetingRealtimeEngineBindings(params: { platform: MeetingPlatformRuntimeMetadata; config: { realtime: { agentId?: string; toolPolicy: RealtimeVoiceAgentConsultToolPolicy; }; }; fullConfig: OpenClawConfig; runtime: PluginRuntime; logger: RuntimeLogger; }): { platform: MeetingRuntimePlatform; consultAgent: (consult: MeetingAgentConsultParams) => Promise<{ text: string; }>; tools: RealtimeVoiceTool[]; handleToolCall: (call: MeetingRealtimeToolCallParams) => Promise; };","entrypoint":"meeting-runtime","exportName":"createMeetingRealtimeEngineBindings","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"9124db05923060c467edb149bd8abdc8be1d00d00f867cf84600972923a58489","declaration":"export function createMeetingSession(params: { platform: MeetingPlatformRuntimeMetadata; config: { realtime: { provider?: string; voiceProvider?: string; transcriptionProvider?: string; model?: string; toolPolicy: TToolPolicy; }; }; resolved: MeetingResolvedJoin; createdAt: string; }): MeetingSessionRecord;","entrypoint":"meeting-runtime","exportName":"createMeetingSession","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"fb276e07e395ab19177b3d7967fd0a8fcda79130883f15092a4a306b826481b9","declaration":"export function createMeetingSetupStatus(checks: MeetingSetupCheck[]): MeetingSetupStatus;","entrypoint":"meeting-runtime","exportName":"createMeetingSetupStatus","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"8502c6c66f3d3894a5e014c8387542de36cce5f9b2a5c7ff2fe5fc2498b79820","declaration":"export function createMeetingVoiceCallGateway(params: { config: MeetingVoiceCallConfig; runtime: PluginRuntime; surface: MeetingVoiceCallSurface; connectClient: (params: { config: MeetingVoiceCallConfig; surface: MeetingVoiceCallSurface; }) => Promise; }): MeetingVoiceCallGateway;","entrypoint":"meeting-runtime","exportName":"createMeetingVoiceCallGateway","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} -{"closureHash":"349e917858b2f151892adb66f32143d74734cf715511e8f7250d97e49ffdd24d","declaration":"export function createNodeMeetingRealtimeAudioTransport(params: { runtime: PluginRuntime; nodeId: string; bridgeId: string; logger: RuntimeLogger; commandName: string; logScope: string; logPrefix: string; audioFormat?: MeetingRealtimeAudioFormat; }): MeetingRealtimeAudioTransport;","entrypoint":"meeting-runtime","exportName":"createNodeMeetingRealtimeAudioTransport","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} +{"closureHash":"a6e61ac4ed0c572b67864fae20fce85f89d0446de4be9222dac12225ef796217","declaration":"export function createNodeMeetingRealtimeAudioTransport(params: { runtime: PluginRuntime; nodeId: string; bridgeId: string; logger: RuntimeLogger; commandName: string; logScope: string; logPrefix: string; audioFormat?: MeetingRealtimeAudioFormat; }): MeetingRealtimeAudioTransport;","entrypoint":"meeting-runtime","exportName":"createNodeMeetingRealtimeAudioTransport","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"c0987b15a391a92d53d3e4e12b61eb122f9547fcf4f45478cdd3c5a8973a3e52","declaration":"export function endMeetingVoiceCallGatewayCall(params: { gateway: MeetingVoiceCallGateway; callId: string; }): Promise;","entrypoint":"meeting-runtime","exportName":"endMeetingVoiceCallGatewayCall","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"b3ebc10fe7ab372e1317eaa8f78621cf277bba1e33fda57acf2254f6759d1e9d","declaration":"export function getMeetingVoiceCallGatewayCall(params: { gateway: MeetingVoiceCallGateway; callId: string; }): Promise;","entrypoint":"meeting-runtime","exportName":"getMeetingVoiceCallGatewayCall","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"aac088df8e80d9ce89c45feccdb04ace3ae4417b3fcdd0f058410ddf25c96b46","declaration":"export function isMeetingVoiceCallMissingError(error: unknown): boolean;","entrypoint":"meeting-runtime","exportName":"isMeetingVoiceCallMissingError","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} -{"closureHash":"20ffbb6b94e7bbf41c9206ccd70cbd4a7235cbe615cb0e0f67e4c1a29be158d0","declaration":"export function joinMeetingViaVoiceCallGateway(params: { config: MeetingVoiceCallConfig; gateway: MeetingVoiceCallGateway; surface: MeetingVoiceCallSurface; dialInNumber: string; dtmfSequence?: string; logger?: RuntimeLogger; message?: string; requesterSessionKey?: string; agentId?: string; sessionKey?: string; }): Promise;","entrypoint":"meeting-runtime","exportName":"joinMeetingViaVoiceCallGateway","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} +{"closureHash":"1eea6ba8ffb859648114c97a62b52f2772a87149685420fe3dfd83acc1ad5544","declaration":"export function joinMeetingViaVoiceCallGateway(params: { config: MeetingVoiceCallConfig; gateway: MeetingVoiceCallGateway; surface: MeetingVoiceCallSurface; dialInNumber: string; dtmfSequence?: string; logger?: RuntimeLogger; message?: string; requesterSessionKey?: string; agentId?: string; sessionKey?: string; }): Promise;","entrypoint":"meeting-runtime","exportName":"joinMeetingViaVoiceCallGateway","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"bac9621cd025754fbcd4c21409b06eb0503405db81f35cac993aaf0b881ab3e1","declaration":"export function leaveMeetingWithBrowser(params: { adapter: BrowserAdapter; callBrowser: MeetingBrowserRequestCaller; launch: boolean; meetingSessionId?: string; meetingUrl: string; tab: MeetingBrowserTab; timeoutMs: number; }): Promise<{ left: boolean; note: string; }>;","entrypoint":"meeting-runtime","exportName":"leaveMeetingWithBrowser","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"c7b1aeac134b2070d053adf33026d876998b852ccd65cd2c9667119b1a1abee2","declaration":"export function openMeetingWithBrowser, Mode extends string, Health extends MeetingBrowserHealth & { browserTitle?: string; browserUrl?: string; notes?: string[]; }, Transcript extends MeetingTranscriptSnapshot>(params: { adapter: BrowserAdapter; callBrowser: MeetingBrowserRequestCaller; config: MeetingBrowserControllerConfig; session: Session; }): Promise<{ launched: boolean; browser?: Health; tab?: MeetingBrowserTab; }>;","entrypoint":"meeting-runtime","exportName":"openMeetingWithBrowser","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"34858979bd248799c2215d9c7bdd5d4a6b27660c9173bfca2e50dd4373ac27db","declaration":"export function readMeetingBrowserTab(result: unknown): MeetingBrowserCandidateTab | undefined;","entrypoint":"meeting-runtime","exportName":"readMeetingBrowserTab","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} @@ -3218,8 +3218,8 @@ {"closureHash":"470378e619de753cc60fc9959fafc2d10adce6e3b38b3f25e53167a0e0a1f578","declaration":"export function resolveMeetingBrowserNodeInfo(params: { runtime: PluginRuntime; adapter: NodeAdapter; requestedNode?: string; }): Promise;","entrypoint":"meeting-runtime","exportName":"resolveMeetingBrowserNodeInfo","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"f21196c9ea8ab90248ffdcdef3e942f1247ee2b9064ddb9757fffe352362c779","declaration":"export function resolveMeetingRealtimeAudioFormat(audioFormat: MeetingRealtimeAudioFormat): RealtimeVoiceAudioFormat;","entrypoint":"meeting-runtime","exportName":"resolveMeetingRealtimeAudioFormat","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"521dc5c262524a709cdd27cdc88e95004073da8223537bcbc73d5d423172dc2b","declaration":"export function speakMeetingViaVoiceCallGateway(params: { gateway: MeetingVoiceCallGateway; callId: string; message: string; }): Promise;","entrypoint":"meeting-runtime","exportName":"speakMeetingViaVoiceCallGateway","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} -{"closureHash":"120efa63a13b055b3b73d6450b24a67b5cd6a574b9db9394ecc7dc13f4b17c4a","declaration":"export function startMeetingAgentRealtimeEngine(params: { config: MeetingRealtimeEngineConfig; fullConfig: OpenClawConfig; runtime: PluginRuntime; platform: MeetingRuntimePlatform; meetingSessionId: string; requesterSessionKey?: string; logPrefix?: \"node\"; transport: MeetingRealtimeAudioTransport; logger: RuntimeLogger; providers?: RealtimeTranscriptionProviderPlugin[]; consultAgent: (params: MeetingAgentConsultParams) => Promise<{ text: string; }>; }): Promise;","entrypoint":"meeting-runtime","exportName":"startMeetingAgentRealtimeEngine","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} -{"closureHash":"1d060366536b2cad37030b9fedef06065118648a6646b75a0848cc293c123e4e","declaration":"export function startMeetingRealtimeEngine(params: { config: MeetingRealtimeEngineConfig; fullConfig: OpenClawConfig; runtime: PluginRuntime; platform: MeetingRuntimePlatform; meetingSessionId: string; requesterSessionKey?: string; logPrefix?: \"node\"; talkSessionId?: string; talkContext?: { nodeId: string; bridgeId: string; }; transport: MeetingRealtimeAudioTransport; logger: RuntimeLogger; providers?: RealtimeVoiceProviderPlugin[]; consultAgent: (params: MeetingAgentConsultParams) => Promise<{ text: string; }>; tools: RealtimeVoiceTool[]; handleToolCall: (params: MeetingRealtimeToolCallParams) => Promise; }): Promise;","entrypoint":"meeting-runtime","exportName":"startMeetingRealtimeEngine","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} +{"closureHash":"af6e7a70d984540c4e8666df3913fec085f306a3e47e195c6a70cc463bb375bc","declaration":"export function startMeetingAgentRealtimeEngine(params: { config: MeetingRealtimeEngineConfig; fullConfig: OpenClawConfig; runtime: PluginRuntime; platform: MeetingRuntimePlatform; meetingSessionId: string; requesterSessionKey?: string; logPrefix?: \"node\"; transport: MeetingRealtimeAudioTransport; logger: RuntimeLogger; providers?: RealtimeTranscriptionProviderPlugin[]; consultAgent: (params: MeetingAgentConsultParams) => Promise<{ text: string; }>; }): Promise;","entrypoint":"meeting-runtime","exportName":"startMeetingAgentRealtimeEngine","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} +{"closureHash":"ff316165743a08095911e3b60201a1936eb0d9853d23596a7e3a99b11b911729","declaration":"export function startMeetingRealtimeEngine(params: { config: MeetingRealtimeEngineConfig; fullConfig: OpenClawConfig; runtime: PluginRuntime; platform: MeetingRuntimePlatform; meetingSessionId: string; requesterSessionKey?: string; logPrefix?: \"node\"; talkSessionId?: string; talkContext?: { nodeId: string; bridgeId: string; }; transport: MeetingRealtimeAudioTransport; logger: RuntimeLogger; providers?: RealtimeVoiceProviderPlugin[]; consultAgent: (params: MeetingAgentConsultParams) => Promise<{ text: string; }>; tools: RealtimeVoiceTool[]; handleToolCall: (params: MeetingRealtimeToolCallParams) => Promise; }): Promise;","entrypoint":"meeting-runtime","exportName":"startMeetingRealtimeEngine","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"function","recordType":"export"} {"closureHash":"663b9c77231b21af697efd5042e22d1c33e9ee972adeb312ad86d81a2b2e36f7","declaration":"export type MeetingAgentConsultParams = MeetingAgentConsultParams;","entrypoint":"meeting-runtime","exportName":"MeetingAgentConsultParams","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"adf0745c611bf737fc28978d766fc2a25b7104e71b6b2fed4e9b95e519c7b115","declaration":"export type MeetingBrowserCandidateTab = MeetingBrowserCandidateTab;","entrypoint":"meeting-runtime","exportName":"MeetingBrowserCandidateTab","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"59b1526172b7491b85f14f13c1ee26d8d6726f33093770748580c767b1689a17","declaration":"export type MeetingBrowserControllerConfig = MeetingBrowserControllerConfig;","entrypoint":"meeting-runtime","exportName":"MeetingBrowserControllerConfig","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} @@ -3251,7 +3251,7 @@ {"closureHash":"8244b408803f57cf2004157e96f2ce11bb036cdf864123b15ba2fac7f03b5d43","declaration":"export type MeetingSessionRuntimeHandles = MeetingSessionRuntimeHandles;","entrypoint":"meeting-runtime","exportName":"MeetingSessionRuntimeHandles","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"aa8054fc2c442891197e89bdad61e79999ea15d813c4b127fb6150607b9e67e7","declaration":"export type MeetingSessionRuntimeJoinContext, TTransport extends string, TMode extends string, THealth extends MeetingBrowserHealth, TTab extends MeetingBrowserTab> = MeetingSessionRuntimeJoinContext;","entrypoint":"meeting-runtime","exportName":"MeetingSessionRuntimeJoinContext","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"325241724edd38da439c5df03d225e2a7cb8e56d817e98ed7c37850656d3744b","declaration":"export type MeetingSessionRuntimeMessages = MeetingSessionRuntimeMessages;","entrypoint":"meeting-runtime","exportName":"MeetingSessionRuntimeMessages","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} -{"closureHash":"fc4a0423ef89e4e69bf2a005f462f371d0443643f67d43dc5241725392100d22","declaration":"export type MeetingSessionRuntimeOptions, TRequest, TTransport extends string, TMode extends string, THealth extends MeetingBrowserHealth, TTab extends MeetingBrowserTab, TManualReason extends string, TSpeechBlockedReason extends string> = MeetingSessionRuntimeOptions;","entrypoint":"meeting-runtime","exportName":"MeetingSessionRuntimeOptions","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} +{"closureHash":"cb3b2e4b8606d2d859842589c9fd25e9ebb6c45b24057130405c592ad6f3c97e","declaration":"export type MeetingSessionRuntimeOptions, TRequest, TTransport extends string, TMode extends string, THealth extends MeetingBrowserHealth, TTab extends MeetingBrowserTab, TManualReason extends string, TSpeechBlockedReason extends string> = MeetingSessionRuntimeOptions;","entrypoint":"meeting-runtime","exportName":"MeetingSessionRuntimeOptions","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"d3c730cba02d30512c4cdae6e2367c662982e2c25671833ecd8701283924c642","declaration":"export type MeetingSessionState = MeetingSessionState;","entrypoint":"meeting-runtime","exportName":"MeetingSessionState","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"2496931157bf64fdb16013172ab9c023e60c5f85b98f46a3ee9532ce58d636d6","declaration":"export type MeetingSetupCheck = MeetingSetupCheck;","entrypoint":"meeting-runtime","exportName":"MeetingSetupCheck","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"f0aed6f7955a43fc387d8bbc072cb6276b0f2d18e7f829441a2ad45974bb3831","declaration":"export type MeetingSetupStatus = MeetingSetupStatus;","entrypoint":"meeting-runtime","exportName":"MeetingSetupStatus","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} @@ -3265,9 +3265,9 @@ {"closureHash":"20c70a94483aeddda4d1500cfcc5e71fab844b474ba4061c55d6c8693d40821b","declaration":"export type MeetingVoiceCallJoinResult = MeetingVoiceCallJoinResult;","entrypoint":"meeting-runtime","exportName":"MeetingVoiceCallJoinResult","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"4fb2c5928aa6f809dd4c604d71c5c99cdfe3e98b6e9c1662601c9b4ad9738162","declaration":"export type MeetingVoiceCallStatusResult = MeetingVoiceCallStatusResult;","entrypoint":"meeting-runtime","exportName":"MeetingVoiceCallStatusResult","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} {"closureHash":"5ef812397b6409a4ca228943dd1080661365959477b42e705a9aaa219757d14f","declaration":"export type MeetingVoiceCallSurface = MeetingVoiceCallSurface;","entrypoint":"meeting-runtime","exportName":"MeetingVoiceCallSurface","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"type","recordType":"export"} -{"closureHash":"de684d52c9c6211ac3c0e3b0c125fa5dcd4ba7429592d16c7d46d997854d5e11","declaration":"export interface MeetingPlatformAdapter extends MeetingPlatformAdapterContract {\n}","entrypoint":"meeting-runtime","exportName":"MeetingPlatformAdapter","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"interface","recordType":"export"} +{"closureHash":"5041fbb4d91908d3ef575351f099a59c54c0b8d46bc020833eed1ad172dc6c52","declaration":"export interface MeetingPlatformAdapter extends MeetingPlatformAdapterContract {\n}","entrypoint":"meeting-runtime","exportName":"MeetingPlatformAdapter","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"interface","recordType":"export"} {"closureHash":"79884bd1df1ea0d07d5ceb4c62d93c1ee744b75d31c94c988845869c0f3cbd2d","declaration":"export interface MeetingRealtimeAudioTransport {\n onFatal(handler: () => void): void;\n startInput(onAudio: (audio: Buffer) => void): void;\n beginOutput?(): void;\n stop(): Promise;\n writeOutput(audio: Buffer): Promise;\n clearOutput(): Promise;\n dispose(): Promise;\n getHealth?(): MeetingRealtimeAudioTransportHealth;\n startBargeInMonitor?(onBargeIn: (audio: Buffer) => boolean): void;\n}","entrypoint":"meeting-runtime","exportName":"MeetingRealtimeAudioTransport","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"interface","recordType":"export"} -{"closureHash":"6be607fcf27e9528624263aeda4913a99a9c977620e1bac6d5730fb00f3f518c","declaration":"export class MeetingSessionRuntime, TRequest, TTransport extends string, TMode extends string, THealth extends MeetingBrowserHealth, TTab extends MeetingBrowserTab, TManualReason extends string, TSpeechBlockedReason extends string> {\n readonly #sessions: Map;\n readonly #sessionLeaves: Map>>;\n readonly #sessionCleanup: MeetingSessionCleanupTracker;\n readonly #meetingLock: MeetingSessionJoinLock;\n readonly #sessionStops: Map Promise>;\n readonly #sessionSpeakers: Map void>;\n readonly #sessionHealth: Map Partial>;\n readonly #durableTranscripts: MeetingSessionDurableTranscripts;\n readonly #transcriptStore: MeetingSessionTranscriptStore;\n constructor(private readonly options: MeetingSessionRuntimeOptions);\n list(): TSession[];\n getSession(sessionId: string): TSession | undefined;\n async status(sessionId?: string): Promise<{\n found: boolean;\n session?: TSession;\n sessions?: TSession[];\n }>;\n async transcript(sessionId: string, options: {\n sinceIndex?: number;\n }): Promise<{ found: boolean; sessionId?: string; startIndex?: number; nextIndex?: number; droppedLines?: number; evicted?: boolean; lines?: MeetingTranscriptLine[]; }>;\n async startTranscriptSource(request: TranscriptStartRequest): Promise;\n async stopTranscriptSource(request: TranscriptStopRequest): Promise;\n isReusableSession(session: TSession, resolved: MeetingResolvedJoin): boolean;\n async join(request: TRequest): Promise<{\n session: TSession;\n spoken?: boolean;\n }>;\n async leave(sessionId: string, options?: {\n keepBrowserTab?: boolean;\n }): Promise>;\n async speak(sessionId: string, instructions?: string): Promise<{\n found: boolean;\n spoken: boolean;\n session?: TSession;\n }>;\n async speakWhenReady(session: TSession, instructions: string): Promise;\n hasHealthHandle(sessionId: string): boolean;\n refreshHealth(sessionId?: string): void;\n async refreshBrowserHealth(session: TSession, options: {\n force?: boolean;\n readOnly?: boolean;\n }): Promise;\n async refreshCaptionHealth(session: TSession): Promise;\n refreshSpeechReadiness(session: TSession): {\n ready: boolean;\n reason?: TSpeechBlockedReason;\n message?: string;\n };\n markSessionEnded(session: TSession, reason: string): void;\n async #joinUnlocked(request: TRequest, resolved: MeetingResolvedJoin): Promise<{\n session: TSession;\n spoken?: boolean;\n }>;\n async #leaveUnlocked(sessionId: string, options?: {\n keepBrowserTab?: boolean;\n }): Promise>;\n async #leaveSession(session: TSession, options?: {\n keepBrowserTab?: boolean;\n }): Promise>;\n #meetingKey(transport: TTransport, url: string): string;\n #inheritBrowserTabOwnership(params: {\n session: TSession;\n transport: TTransport;\n nodeId?: string;\n meetingUrl: string;\n tab?: TTab;\n }): TTab | undefined;\n async #settleRetainedBrowserTabs(retained: Array<{\n session: TSession;\n tab: TTab;\n }>, adopted?: {\n transport: TTransport;\n nodeId?: string;\n tab: TTab;\n }): Promise;\n async #rollbackFailedJoinSession(session: TSession): Promise;\n async #settleRetainedBrowserTabsAfterFailure(retained: Array<{\n session: TSession;\n tab: TTab;\n }>): Promise;\n #attachRuntimeHandles(session: TSession, handles: MeetingSessionRuntimeHandles): void;\n #dropRuntimeHandles(sessionId: string): void;\n #isManagedBrowserSession(session: TSession): boolean;\n #evaluateSpeechReadiness(session: TSession): {\n ready: boolean;\n reason?: TSpeechBlockedReason;\n message?: string;\n };\n #noteSession(session: TSession, note: string): void;\n}","entrypoint":"meeting-runtime","exportName":"MeetingSessionRuntime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"class","recordType":"export"} +{"closureHash":"7b3c54f79cc3fa407f4becb39e6697f885ae3f0ea3f510095f4a03620b17b114","declaration":"export class MeetingSessionRuntime, TRequest, TTransport extends string, TMode extends string, THealth extends MeetingBrowserHealth, TTab extends MeetingBrowserTab, TManualReason extends string, TSpeechBlockedReason extends string> {\n readonly #sessions: Map;\n readonly #sessionLeaves: Map>>;\n readonly #sessionCleanup: MeetingSessionCleanupTracker;\n readonly #meetingLock: MeetingSessionJoinLock;\n readonly #sessionStops: Map Promise>;\n readonly #sessionSpeakers: Map void>;\n readonly #sessionHealth: Map Partial>;\n readonly #durableTranscripts: MeetingSessionDurableTranscripts;\n readonly #transcriptStore: MeetingSessionTranscriptStore;\n constructor(private readonly options: MeetingSessionRuntimeOptions);\n list(): TSession[];\n getSession(sessionId: string): TSession | undefined;\n async status(sessionId?: string): Promise<{\n found: boolean;\n session?: TSession;\n sessions?: TSession[];\n }>;\n async transcript(sessionId: string, options: {\n sinceIndex?: number;\n }): Promise<{ found: boolean; sessionId?: string; startIndex?: number; nextIndex?: number; droppedLines?: number; evicted?: boolean; lines?: MeetingTranscriptLine[]; }>;\n async startTranscriptSource(request: TranscriptStartRequest): Promise;\n async stopTranscriptSource(request: TranscriptStopRequest): Promise;\n isReusableSession(session: TSession, resolved: MeetingResolvedJoin): boolean;\n async join(request: TRequest): Promise<{\n session: TSession;\n spoken?: boolean;\n }>;\n async leave(sessionId: string, options?: {\n keepBrowserTab?: boolean;\n }): Promise>;\n async speak(sessionId: string, instructions?: string): Promise<{\n found: boolean;\n spoken: boolean;\n session?: TSession;\n }>;\n async speakWhenReady(session: TSession, instructions: string): Promise;\n hasHealthHandle(sessionId: string): boolean;\n refreshHealth(sessionId?: string): void;\n async refreshBrowserHealth(session: TSession, options: {\n force?: boolean;\n readOnly?: boolean;\n }): Promise;\n async refreshCaptionHealth(session: TSession): Promise;\n refreshSpeechReadiness(session: TSession): {\n ready: boolean;\n reason?: TSpeechBlockedReason;\n message?: string;\n };\n markSessionEnded(session: TSession, reason: string): void;\n async #joinUnlocked(request: TRequest, resolved: MeetingResolvedJoin): Promise<{\n session: TSession;\n spoken?: boolean;\n }>;\n async #leaveUnlocked(sessionId: string, options?: {\n keepBrowserTab?: boolean;\n }): Promise>;\n async #leaveSession(session: TSession, options?: {\n keepBrowserTab?: boolean;\n }): Promise>;\n #meetingKey(transport: TTransport, url: string): string;\n #inheritBrowserTabOwnership(params: {\n session: TSession;\n transport: TTransport;\n nodeId?: string;\n meetingUrl: string;\n tab?: TTab;\n }): TTab | undefined;\n async #settleRetainedBrowserTabs(retained: Array<{\n session: TSession;\n tab: TTab;\n }>, adopted?: {\n transport: TTransport;\n nodeId?: string;\n tab: TTab;\n }): Promise;\n async #rollbackFailedJoinSession(session: TSession): Promise;\n async #settleRetainedBrowserTabsAfterFailure(retained: Array<{\n session: TSession;\n tab: TTab;\n }>): Promise;\n #attachRuntimeHandles(session: TSession, handles: MeetingSessionRuntimeHandles): void;\n #dropRuntimeHandles(sessionId: string): void;\n #isManagedBrowserSession(session: TSession): boolean;\n #evaluateSpeechReadiness(session: TSession): {\n ready: boolean;\n reason?: TSpeechBlockedReason;\n message?: string;\n };\n #noteSession(session: TSession, note: string): void;\n}","entrypoint":"meeting-runtime","exportName":"MeetingSessionRuntime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime","kind":"class","recordType":"export"} {"category":null,"entrypoint":"memory-core-host-engine-foundation","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation","recordType":"module"} {"closureHash":"e2df8d235401aebf58eb71652fbeaedb42ca00aaa5042a76909ff2c48ba6fd54","declaration":"export function createSubsystemLogger(subsystem: string): SubsystemLogger;","entrypoint":"memory-core-host-engine-foundation","exportName":"createSubsystemLogger","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation","kind":"function","recordType":"export"} {"closureHash":null,"declaration":"export function isPathInside(root: string, target: string): boolean;","entrypoint":"memory-core-host-engine-foundation","exportName":"isPathInside","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation","kind":"function","recordType":"export"} @@ -3403,9 +3403,9 @@ {"category":"core","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","recordType":"module"} {"closureHash":"3d27406644141af2733cfca6e8c1240fc62327910109f585ce9073cafd7fd89c","declaration":"export function buildJsonPluginConfigSchema(schema: JsonSchemaObject, options?: BuildJsonPluginConfigSchemaOptions): OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"buildJsonPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export"} {"closureHash":"aff66c792d532642afb9c6e5a03c8d220f219250c3a8b49733f32058f22f98ca","declaration":"export function buildPluginConfigSchema(schema: ZodTypeAny, options?: BuildPluginConfigSchemaOptions): OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"buildPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export"} -{"closureHash":"56edcfdc47d0e6f005a60668136834383ce95fe466b88cbce4678fa0ebf35631","declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, reload, nodeHostCommands, securityAuditCollectors, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"plugin-entry","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export"} +{"closureHash":"f04c131ad85b7755df9998b3734af31eb0e39d74941d3cddbbc717a1836ad665","declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, reload, nodeHostCommands, securityAuditCollectors, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"plugin-entry","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export"} {"closureHash":"0ef1dfa3b5deeb456480e292c0e835c3cd6d13e7bb7059fc735546507da51594","declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export"} -{"closureHash":"fd9e44e0e924c462451e966215da05d67067abfb489a1e1f64cac2b7279c2f65","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"plugin-entry","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} +{"closureHash":"7bc7817206b083bb475cc0f3862c9b552f0053de3f325f0b846a284e0c1c73f5","declaration":"export type AgentHarness = AgentHarness;","entrypoint":"plugin-entry","exportName":"AgentHarness","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"d2ff4de9ee3fd111c2cc64a3ee7e557d9f161af1b6b437ee7886bcb16e426317","declaration":"export type AgentPromptGuidance = AgentPromptGuidance;","entrypoint":"plugin-entry","exportName":"AgentPromptGuidance","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"2841127a90e04467adfea8126cbf03655f44ddb089ad9e0d48833c2beea39cbd","declaration":"export type AgentPromptGuidanceEntry = AgentPromptGuidanceEntry;","entrypoint":"plugin-entry","exportName":"AgentPromptGuidanceEntry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"aa0412acefe62caff12e5271ead0545504874e824380dc21cfc0566388a05f33","declaration":"export type AgentPromptSurfaceKind = \"openclaw_main\" | \"pi_main\" | \"codex_app_server\" | \"cli_backend\" | \"acp_backend\" | \"subagent\";","entrypoint":"plugin-entry","exportName":"AgentPromptSurfaceKind","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} @@ -3421,10 +3421,10 @@ {"closureHash":"8054ffbe71b91afced900ef8a06f5e368ac10c4f99ffc1c6f538a802856f61c6","declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"plugin-entry","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"2f6e368fff73e942044cdf95c7c751b46c741e22ac0d2d4d97f45a3158f758ae","declaration":"export type OpenClawGatewayDiscoveryAdvertiseContext = OpenClawGatewayDiscoveryAdvertiseContext;","entrypoint":"plugin-entry","exportName":"OpenClawGatewayDiscoveryAdvertiseContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"b198058e0065e5c60b729a5bdf568f7e2436a006d8c4a7c63de764c7283f96ba","declaration":"export type OpenClawGatewayDiscoveryService = OpenClawGatewayDiscoveryService;","entrypoint":"plugin-entry","exportName":"OpenClawGatewayDiscoveryService","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} -{"closureHash":"040ee86402db43973dab92c67edd53fa7183ff96d7e4d485f7a262eadfc25178","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-entry","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} +{"closureHash":"1b8024c377c0a8dbead0571adb50ce9b8a5eef2d5cb16b23035579caee71137a","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-entry","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"e89ab2a5316dba762220c00ee423b352ce66153297aaa1abd4299fe59d0eb9b4","declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"365beb5cee974639c2f35fc7a39088721ca8e9c618442830298b98033e9c768c","declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} -{"closureHash":"b5ed3632d2bb3365b86b4dfaad78017f027eb09a2eebc623361452918eb30b4e","declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} +{"closureHash":"d03dbf592e8e12766b5421c44cee51d58322518775574d50a36941ed9dabec05","declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"a7ed83f0924f818b23cecb5a1f3591ff28b9499ccac9667a2b8505c2bc608ea9","declaration":"export type OpenClawPluginGatewayEventScope = OpenClawPluginGatewayEventScope;","entrypoint":"plugin-entry","exportName":"OpenClawPluginGatewayEventScope","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"f81f224d9b837e6353b7a546c5a76b124bcf568fd1f1fd0c6881fda30c53120a","declaration":"export type OpenClawPluginGatewayEvents = OpenClawPluginGatewayEvents;","entrypoint":"plugin-entry","exportName":"OpenClawPluginGatewayEvents","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} {"closureHash":"7470b211ac4d3449cd0fc353ee2eb48e9fbc2dbc9259d21c680d0203577aa94f","declaration":"export type OpenClawPluginHttpRouteHandler = OpenClawPluginHttpRouteHandler;","entrypoint":"plugin-entry","exportName":"OpenClawPluginHttpRouteHandler","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export"} @@ -3574,14 +3574,14 @@ {"closureHash":"7906fbdeb04e9b99f512e4bab7230d701be7eee8d92fdc6529b2627c44f45f94","declaration":"export function executePluginCommand(params: { command: RegisteredPluginCommand; args?: string; senderId?: string; channel: string; channelId?: PluginCommandContext[\"channelId\"]; isAuthorizedSender: boolean; senderIsOwner?: boolean; gatewayClientScopes?: PluginCommandContext[\"gatewayClientScopes\"]; agentId?: string; sessionKey?: PluginCommandContext[\"sessionKey\"]; sessionId?: PluginCommandContext[\"sessionId\"]; sessionTarget?: PluginCommandContext[\"sessionTarget\"]; sessionFile?: PluginCommandContext[\"sessionFile\"]; authProfileId?: string; commandBody: string; config: OpenClawConfig; from?: PluginCommandContext[\"from\"]; to?: PluginCommandContext[\"to\"]; originatingTo?: string; accountId?: PluginCommandContext[\"accountId\"]; messageThreadId?: PluginCommandContext[\"messageThreadId\"]; threadParentId?: PluginCommandContext[\"threadParentId\"]; diagnosticsSessions?: PluginCommandContext[\"diagnosticsSessions\"]; diagnosticsUploadApproved?: PluginCommandContext[\"diagnosticsUploadApproved\"]; diagnosticsPreviewOnly?: PluginCommandContext[\"diagnosticsPreviewOnly\"]; diagnosticsPrivateRouted?: PluginCommandContext[\"diagnosticsPrivateRouted\"]; }): Promise;","entrypoint":"plugin-runtime","exportName":"executePluginCommand","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"dfc8fc733f281bfbe0cc270964f0f657f17c2b97f21bc1b316aee6e9de8ff6ba","declaration":"export function getGlobalHookRunner(): HookRunner | null;","entrypoint":"plugin-runtime","exportName":"getGlobalHookRunner","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"db6583607237e4081814e2c5d4f8fb6f216ad3bbfe623bf16fc1a9c09c15f64b","declaration":"export function getPluginCommandSpecs(provider?: string, options?: PluginCommandSpecOptions): Array<{ name: string; description: string; descriptionLocalizations?: Record; acceptsArgs: boolean; }>;","entrypoint":"plugin-runtime","exportName":"getPluginCommandSpecs","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} -{"closureHash":"4c36134de1f295ec75d4067378059d155e1f8a249669ee7c8f3708c757efb4b5","declaration":"export function getPluginRuntimeGatewayRequestScope(): PluginRuntimeGatewayRequestScope | undefined;","entrypoint":"plugin-runtime","exportName":"getPluginRuntimeGatewayRequestScope","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} +{"closureHash":"f00da22c0f015e608127bf3a48925bde2ea8102b8df975cde326bb66992eb672","declaration":"export function getPluginRuntimeGatewayRequestScope(): PluginRuntimeGatewayRequestScope | undefined;","entrypoint":"plugin-runtime","exportName":"getPluginRuntimeGatewayRequestScope","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"cfe277ce6de0c6ed50f1f54b66c889f6c95e91b058e8bcdc27aa37317a93a447","declaration":"export function listRegisteredPluginAgentPromptGuidance(params?: { surface?: AgentPromptSurfaceKind; includeLegacyGlobalGuidance?: boolean; }): string[];","entrypoint":"plugin-runtime","exportName":"listRegisteredPluginAgentPromptGuidance","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"757a4525d8813e5cbb5308d7314751680f5f5ff0902ce994246393e8d771b7fb","declaration":"export function matchPluginCommand(commandBody: string, options?: { channel?: string; }): { command: RegisteredPluginCommand; args?: string; } | null;","entrypoint":"plugin-runtime","exportName":"matchPluginCommand","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"9f62be89b0b32cf405337215301e81177eddf12db5c13748331b616993116495","declaration":"export function registerPluginCommand(pluginId: string, command: OpenClawPluginCommandDefinition, opts?: { pluginName?: string; pluginRoot?: string; allowReservedCommandNames?: boolean; allowOwnerStatusExposure?: boolean; }): CommandRegistrationResult;","entrypoint":"plugin-runtime","exportName":"registerPluginCommand","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"16986e5d431f584c2d5053289eca3d2225bbe47a92cdfbbc2af91b264a165846","declaration":"export function registerPluginInteractiveHandler(pluginId: string, registration: PluginInteractiveHandlerRegistration, opts?: { pluginName?: string; pluginRoot?: string; }): InteractiveRegistrationResult;","entrypoint":"plugin-runtime","exportName":"registerPluginInteractiveHandler","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"f20c07a48e2c949fbc889e7a32760f4e52339d9f6d3eab49e00ff249266e2396","declaration":"export function startLazyPluginServiceModule(params: { skipEnvVar?: string; overrideEnvVar?: string; validateOverrideSpecifier?: (specifier: string) => string; loadDefaultModule: () => Promise; loadOverrideModule?: (specifier: string) => Promise; startExportNames: string[]; stopExportNames?: string[]; }): Promise;","entrypoint":"plugin-runtime","exportName":"startLazyPluginServiceModule","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"function","recordType":"export"} {"closureHash":"bad43ffe69d1d9d6c92c79df4c388b3dda685013a92392daef456f1622ade659","declaration":"export type LazyPluginServiceHandle = LazyPluginServiceHandle;","entrypoint":"plugin-runtime","exportName":"LazyPluginServiceHandle","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"type","recordType":"export"} -{"closureHash":"040ee86402db43973dab92c67edd53fa7183ff96d7e4d485f7a262eadfc25178","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-runtime","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"type","recordType":"export"} +{"closureHash":"1b8024c377c0a8dbead0571adb50ce9b8a5eef2d5cb16b23035579caee71137a","declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-runtime","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"type","recordType":"export"} {"closureHash":"365beb5cee974639c2f35fc7a39088721ca8e9c618442830298b98033e9c768c","declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"plugin-runtime","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"type","recordType":"export"} {"closureHash":"f06379ada10ce0eca4e66bdf978db2c0035e054a135dc044c6d831ac7797c08f","declaration":"export type PluginConversationBinding = PluginConversationBinding;","entrypoint":"plugin-runtime","exportName":"PluginConversationBinding","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"type","recordType":"export"} {"closureHash":"a1223d674b2b66918a576ae05b04318d7b8cbe9b1d3bc6dd7a08d1a4fa79b291","declaration":"export type PluginConversationBindingRequestParams = PluginConversationBindingRequestParams;","entrypoint":"plugin-runtime","exportName":"PluginConversationBindingRequestParams","importSpecifier":"openclaw/plugin-sdk/plugin-runtime","kind":"type","recordType":"export"} @@ -3671,10 +3671,10 @@ {"closureHash":"e66adcb55dec79be73a52bc76594b7e9210fd93c69a85b56098fbb58cecbb354","declaration":"export type WriteOAuthCredentialsOptions = WriteOAuthCredentialsOptions;","entrypoint":"provider-auth","exportName":"WriteOAuthCredentialsOptions","importSpecifier":"openclaw/plugin-sdk/provider-auth","kind":"type","recordType":"export"} {"category":null,"entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","recordType":"module"} {"closureHash":"4f764aa4d0f8d3ffd97330520c637263a51a4d34e7dab6f6f37004b1d4b1e013","declaration":"export function augmentModelCatalogWithProviderPlugins(params: { config?: OpenClawConfig; workspaceDir?: string; env?: NodeJS.ProcessEnv; metadataSnapshot?: PluginMetadataSnapshot; context: ProviderAugmentModelCatalogContext; }): Promise;","entrypoint":"provider-catalog-runtime","exportName":"augmentModelCatalogWithProviderPlugins","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} -{"closureHash":"caf16b7443f51a31807dd8110c6c07be121ca2b884c9ad551bdf30387c51c0c0","declaration":"export function isPluginProvidersLoadInFlight(params: Parameters[0]): boolean;","entrypoint":"provider-catalog-runtime","exportName":"isPluginProvidersLoadInFlight","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} -{"closureHash":"87d0eef36918b4a8b9e140c8c359ec1d0b5d49e30a8f2116bb81bd01e28cc505","declaration":"export function resolveCatalogHookProviderPluginIds(params: { config?: PluginLoadOptions[\"config\"]; workspaceDir?: string; env?: PluginLoadOptions[\"env\"]; metadataSnapshot?: ProviderManifestLoadParams[\"metadataSnapshot\"]; }): string[];","entrypoint":"provider-catalog-runtime","exportName":"resolveCatalogHookProviderPluginIds","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} -{"closureHash":"d10fc0524bbc152e2af375b411a30b966d93c2bb6ecd940ab3f78e8f3a88207a","declaration":"export function resolveOwningPluginIdsForProvider(params: { provider: string; config?: PluginLoadOptions[\"config\"]; workspaceDir?: string; env?: PluginLoadOptions[\"env\"]; manifestRegistry?: PluginManifestRegistry; metadataSnapshot?: Pick; }): string[] | undefined;","entrypoint":"provider-catalog-runtime","exportName":"resolveOwningPluginIdsForProvider","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} -{"closureHash":"2e06a4d545892a6723a1f0d7b0695ff89955475f243b8bd27de290fdc108221c","declaration":"export function resolvePluginProviders(params: { config?: PluginLoadOptions[\"config\"]; workspaceDir?: string; env?: PluginLoadOptions[\"env\"]; bundledProviderVitestCompat?: boolean; onlyPluginIds?: string[]; providerRefs?: readonly string[]; modelRefs?: readonly string[]; activate?: boolean; cache?: boolean; applyAutoEnable?: boolean; pluginSdkResolution?: PluginLoadOptions[\"pluginSdkResolution\"]; mode?: \"runtime\" | \"setup\"; includeUntrustedWorkspacePlugins?: boolean; pluginMetadataSnapshot?: PluginMetadataRegistryView; skipIfLoadInFlight?: boolean; }): ProviderPlugin[];","entrypoint":"provider-catalog-runtime","exportName":"resolvePluginProviders","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} +{"closureHash":"74d00a5272a2dc955618d7e45ade943b8b586555b3868eedacc70f13e60afc54","declaration":"export function isPluginProvidersLoadInFlight(params: Parameters[0]): boolean;","entrypoint":"provider-catalog-runtime","exportName":"isPluginProvidersLoadInFlight","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} +{"closureHash":"c54bc61160ae8c2f4e5803df2b7b4f44677de2074f8bd04471bb778dbe8054b5","declaration":"export function resolveCatalogHookProviderPluginIds(params: { config?: PluginLoadOptions[\"config\"]; workspaceDir?: string; env?: PluginLoadOptions[\"env\"]; metadataSnapshot?: ProviderManifestLoadParams[\"metadataSnapshot\"]; }): string[];","entrypoint":"provider-catalog-runtime","exportName":"resolveCatalogHookProviderPluginIds","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} +{"closureHash":"db5c15562c5c4b59df46512237963f914dfd6b3f3fd4c85deb4655dd6ebcf831","declaration":"export function resolveOwningPluginIdsForProvider(params: { provider: string; config?: PluginLoadOptions[\"config\"]; workspaceDir?: string; env?: PluginLoadOptions[\"env\"]; manifestRegistry?: PluginManifestRegistry; metadataSnapshot?: Pick; }): string[] | undefined;","entrypoint":"provider-catalog-runtime","exportName":"resolveOwningPluginIdsForProvider","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} +{"closureHash":"95ea5ad4efe8bb0024f8341b8b89c7b931c2961b7eb61ffff693899ecd13687a","declaration":"export function resolvePluginProviders(params: { config?: PluginLoadOptions[\"config\"]; workspaceDir?: string; env?: PluginLoadOptions[\"env\"]; bundledProviderVitestCompat?: boolean; onlyPluginIds?: string[]; providerRefs?: readonly string[]; modelRefs?: readonly string[]; activate?: boolean; cache?: boolean; applyAutoEnable?: boolean; pluginSdkResolution?: PluginLoadOptions[\"pluginSdkResolution\"]; mode?: \"runtime\" | \"setup\"; includeUntrustedWorkspacePlugins?: boolean; pluginMetadataSnapshot?: PluginMetadataRegistryView; skipIfLoadInFlight?: boolean; }): ProviderPlugin[];","entrypoint":"provider-catalog-runtime","exportName":"resolvePluginProviders","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime","kind":"function","recordType":"export"} {"category":null,"entrypoint":"proxy-capture","importSpecifier":"openclaw/plugin-sdk/proxy-capture","recordType":"module"} {"closureHash":"d0e4e026d736bc00b1758d1089c44c3c5f3a09c7254291584de8cb297365804c","declaration":"export function acquireDebugProxyCaptureStore(dbPath: string, blobDir: string): { store: LegacyDebugProxyCaptureStore; release: () => void; };\nexport function acquireDebugProxyCaptureStore(options?: DebugProxyCaptureStoreOptions): { store: SharedDebugProxyCaptureStore; release: () => void; };","entrypoint":"proxy-capture","exportName":"acquireDebugProxyCaptureStore","importSpecifier":"openclaw/plugin-sdk/proxy-capture","kind":"function","recordType":"export"} {"closureHash":"d41cbb9de51b7aee1c6cf8c713b0bc44f27ac926fbb6422c05726f196d11964e","declaration":"export function captureHttpExchange(params: { url: string; method: string; requestHeaders?: Headers | Record | undefined; requestBody?: BodyInit | Buffer | string | null; response: Response; transport?: \"http\" | \"sse\"; flowId?: string; meta?: Record; }, resolved?: DebugProxySettings, deps?: DebugProxyCaptureRuntimeDeps): void;","entrypoint":"proxy-capture","exportName":"captureHttpExchange","importSpecifier":"openclaw/plugin-sdk/proxy-capture","kind":"function","recordType":"export"} @@ -4639,16 +4639,16 @@ {"closureHash":"e365d7ad911453b5139ba700931b061e560cff80b50d4cbe5e9615b5d7e13a52","declaration":"export type ScopedExpiringIdCache = ScopedExpiringIdCache;","entrypoint":"text-runtime","exportName":"ScopedExpiringIdCache","importSpecifier":"openclaw/plugin-sdk/text-runtime","kind":"type","recordType":"export"} {"closureHash":"af5502c92594c2e9049a4b4994e17c00d3adee3f6d1e68edd818a539c74c8787","declaration":"export interface CodeRegion {\n start: number;\n end: number;\n}","entrypoint":"text-runtime","exportName":"CodeRegion","importSpecifier":"openclaw/plugin-sdk/text-runtime","kind":"interface","recordType":"export"} {"category":null,"entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin","recordType":"module"} -{"closureHash":"db8f6f333f65c3ea958320a3f7f7e5607c8abbe5b20013df93ea5bb5a17effc8","declaration":"export function defineToolPlugin(definition: DefineToolPluginOptions): DefinedToolPluginEntry;","entrypoint":"tool-plugin","exportName":"defineToolPlugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"function","recordType":"export"} +{"closureHash":"4b2cfc6e5cc9d316cc937b768691204298ca43e501be1c7608810502bc85c4f8","declaration":"export function defineToolPlugin(definition: DefineToolPluginOptions): DefinedToolPluginEntry;","entrypoint":"tool-plugin","exportName":"defineToolPlugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"function","recordType":"export"} {"closureHash":"36ef907ce20f885b5f3068cbf0475dc442a68da9652579bb5bf8fb83ad34c38b","declaration":"export function getToolPluginMetadata(entry: unknown): ToolPluginMetadata | undefined;","entrypoint":"tool-plugin","exportName":"getToolPluginMetadata","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"function","recordType":"export"} {"closureHash":"bdc58154e36b2625fb3daccca3eab248d981077d04d747f0ad507cb2ff565198","declaration":"export const toolPluginMetadataSymbol: typeof toolPluginMetadataSymbol;","entrypoint":"tool-plugin","exportName":"toolPluginMetadataSymbol","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"const","recordType":"export"} -{"closureHash":"02b1bb3eb13d8a20fafeae0e141a88972113899326f4b08df46d18c95cfa712c","declaration":"export type DefineToolPluginOptions = DefineToolPluginOptions;","entrypoint":"tool-plugin","exportName":"DefineToolPluginOptions","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} -{"closureHash":"5234acdfe278fa5d4ba786ceaa73021b3999c337ca364aa75238819998efe048","declaration":"export type DefinedToolPluginEntry = DefinedToolPluginEntry;","entrypoint":"tool-plugin","exportName":"DefinedToolPluginEntry","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} -{"closureHash":"26090a95a1bb76b7890203f5938f98091210e9de5937c20ea58b4ef28f79ca02","declaration":"export type ToolPluginExecutionContext = ToolPluginExecutionContext;","entrypoint":"tool-plugin","exportName":"ToolPluginExecutionContext","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} -{"closureHash":"345404e9f1aeb33e3c4206b45cadaefd705f46efb0a442f12d40fdb59fe92c79","declaration":"export type ToolPluginFactoryContext = ToolPluginFactoryContext;","entrypoint":"tool-plugin","exportName":"ToolPluginFactoryContext","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} +{"closureHash":"302acbf23f5e2810c0af9da0cc46f40c33e8b982d3c831686c975a0acc0de7da","declaration":"export type DefineToolPluginOptions = DefineToolPluginOptions;","entrypoint":"tool-plugin","exportName":"DefineToolPluginOptions","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} +{"closureHash":"8dff28374d7b667a6d96e6f378f676a6d2851e8592c9d0d7261b0ff3dca2b331","declaration":"export type DefinedToolPluginEntry = DefinedToolPluginEntry;","entrypoint":"tool-plugin","exportName":"DefinedToolPluginEntry","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} +{"closureHash":"75a71c1fc2813cdccf15077977931517b4e0adba786c411646a6a0f8eccc554e","declaration":"export type ToolPluginExecutionContext = ToolPluginExecutionContext;","entrypoint":"tool-plugin","exportName":"ToolPluginExecutionContext","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} +{"closureHash":"b02a711942a70fde6030a18a6b92c0a1f041775560a5b8946f6477adb7b8e784","declaration":"export type ToolPluginFactoryContext = ToolPluginFactoryContext;","entrypoint":"tool-plugin","exportName":"ToolPluginFactoryContext","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} {"closureHash":"f774bd092f51a31fbb34847213f9583234f585f66918d0af55e81bbbae40463b","declaration":"export type ToolPluginMetadata = ToolPluginMetadata;","entrypoint":"tool-plugin","exportName":"ToolPluginMetadata","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} {"closureHash":"414a89d0c80eaaf682dc25d306e7223c981c6fd25366adb973a106f4268a6a54","declaration":"export type ToolPluginStaticToolMetadata = ToolPluginStaticToolMetadata;","entrypoint":"tool-plugin","exportName":"ToolPluginStaticToolMetadata","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} -{"closureHash":"84f0cb532ea86262f07257704aeee8ee814dcef554d995a675af5b2ff3f1328c","declaration":"export type ToolPluginToolDefinition = ToolPluginToolDefinition;","entrypoint":"tool-plugin","exportName":"ToolPluginToolDefinition","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} +{"closureHash":"81b70cac5a9fb2a27b94adbc6bdb6fac435063be76c08106477b3b51c62ab4dd","declaration":"export type ToolPluginToolDefinition = ToolPluginToolDefinition;","entrypoint":"tool-plugin","exportName":"ToolPluginToolDefinition","importSpecifier":"openclaw/plugin-sdk/tool-plugin","kind":"type","recordType":"export"} {"category":null,"entrypoint":"tool-results","importSpecifier":"openclaw/plugin-sdk/tool-results","recordType":"module"} {"closureHash":"c5b0951c3629834dff78f92317cf1421a6bbc0faf9307be1cabb915055665280","declaration":"export function jsonResult(payload: TDetails): AgentToolResult;","entrypoint":"tool-results","exportName":"jsonResult","importSpecifier":"openclaw/plugin-sdk/tool-results","kind":"function","recordType":"export"} {"closureHash":"b9aefc637a694c74e028e92006004bef18185ed569af48ac43d36b646b0af2f3","declaration":"export function textResult(text: string, details: TDetails): AgentToolResult;","entrypoint":"tool-results","exportName":"textResult","importSpecifier":"openclaw/plugin-sdk/tool-results","kind":"function","recordType":"export"} @@ -4682,9 +4682,9 @@ {"closureHash":"9f9174f1023a8a451232f5d97d1e922b7faa88b76b58cffbfbe246a1488e0ddd","declaration":"export function readJsonWebhookBodyOrReject(params: { req: IncomingMessage; res: ServerResponse; maxBytes?: number; timeoutMs?: number; profile?: WebhookBodyReadProfile; emptyObjectOnEmpty?: boolean; invalidJsonMessage?: string; }): Promise<{ ok: true; value: unknown; } | { ok: false; }>;","entrypoint":"webhook-ingress","exportName":"readJsonWebhookBodyOrReject","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} {"closureHash":"1bbddd5d1c8297e315275db4d5e1c8748ff09c0be7c37b0cf456db4fc69f8909","declaration":"export function readRequestBodyWithLimit(req: IncomingMessage, options: ReadRequestBodyOptions): Promise;","entrypoint":"webhook-ingress","exportName":"readRequestBodyWithLimit","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} {"closureHash":"e8a1174cac83ce346a231fd25e82bb5f7618ffb57cdd9af6eaa208411f70646d","declaration":"export function readWebhookBodyOrReject(params: { req: IncomingMessage; res: ServerResponse; maxBytes?: number; timeoutMs?: number; profile?: WebhookBodyReadProfile; invalidBodyMessage?: string; }): Promise<{ ok: true; value: string; } | { ok: false; }>;","entrypoint":"webhook-ingress","exportName":"readWebhookBodyOrReject","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} -{"closureHash":"c9d579563fa9e8b37a5388e52c5a906869617c26c05adf81e0f7c0398f762153","declaration":"export function registerPluginHttpRoute(params: { path?: string | null; fallbackPath?: string | null; handler: PluginHttpRouteHandler; auth: PluginHttpRouteRegistration[\"auth\"]; match?: PluginHttpRouteRegistration[\"match\"]; gatewayRuntimeScopeSurface?: PluginHttpRouteRegistration[\"gatewayRuntimeScopeSurface\"]; replaceExisting?: boolean; reuseExistingSameOwner?: boolean; throwOnFailure?: boolean; pluginId?: string; source?: string; accountId?: string; log?: (message: string) => void; registry?: PluginRegistry; }): () => void;","entrypoint":"webhook-ingress","exportName":"registerPluginHttpRoute","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} +{"closureHash":"0fe225c147ce8441e5b57019a0560c98da2a032a18309fe67fce65abe624ecad","declaration":"export function registerPluginHttpRoute(params: { path?: string | null; fallbackPath?: string | null; handler: PluginHttpRouteHandler; auth: PluginHttpRouteRegistration[\"auth\"]; match?: PluginHttpRouteRegistration[\"match\"]; gatewayRuntimeScopeSurface?: PluginHttpRouteRegistration[\"gatewayRuntimeScopeSurface\"]; replaceExisting?: boolean; reuseExistingSameOwner?: boolean; throwOnFailure?: boolean; pluginId?: string; source?: string; accountId?: string; log?: (message: string) => void; registry?: PluginRegistry; }): () => void;","entrypoint":"webhook-ingress","exportName":"registerPluginHttpRoute","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} {"closureHash":"05835f2f66f906f736b82b332a84a9714bc3a6b211890a62d8fc4dbf9258a7d4","declaration":"export function registerWebhookTarget(targetsByPath: Map, target: T, opts?: RegisterWebhookTargetOptions): RegisteredWebhookTarget;","entrypoint":"webhook-ingress","exportName":"registerWebhookTarget","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} -{"closureHash":"0afacb2f52f1b20cbfbc73b079c02812952e924c7ec145bed27478171f90d318","declaration":"export function registerWebhookTargetWithPluginRoute(params: { targetsByPath: Map; target: T; route: RegisterWebhookPluginRouteOptions; onLastPathTargetRemoved?: RegisterWebhookTargetOptions[\"onLastPathTargetRemoved\"]; }): RegisteredWebhookTarget;","entrypoint":"webhook-ingress","exportName":"registerWebhookTargetWithPluginRoute","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} +{"closureHash":"bcf5a9653d0d3f370c1674c299b332139f9223f93c4a548abca50241e9e6cdc9","declaration":"export function registerWebhookTargetWithPluginRoute(params: { targetsByPath: Map; target: T; route: RegisterWebhookPluginRouteOptions; onLastPathTargetRemoved?: RegisterWebhookTargetOptions[\"onLastPathTargetRemoved\"]; }): RegisteredWebhookTarget;","entrypoint":"webhook-ingress","exportName":"registerWebhookTargetWithPluginRoute","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} {"closureHash":"ec2f47e9bd7a075a3dd4302ac9e4c0bfe20630445db6735cf803f2d354275841","declaration":"export function requestBodyErrorToText(code: RequestBodyLimitErrorCode): string;","entrypoint":"webhook-ingress","exportName":"requestBodyErrorToText","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} {"closureHash":"5e1f3b468d56eb0f82104184077fdf4b6bd0c1444aa10b71a65fcaa2ec0298a2","declaration":"export function resolveRequestClientIp(req?: IncomingMessage, trustedProxies?: string[], allowRealIpFallback?: boolean): string | undefined;","entrypoint":"webhook-ingress","exportName":"resolveRequestClientIp","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} {"closureHash":"56c3e9868dfa6ee2a7a128eb02039a16b52696759adadbd3fe0a3bd7a6a07eeb","declaration":"export function resolveSingleWebhookTarget(targets: readonly T[], isMatch: (target: T) => boolean): WebhookTargetMatchResult;","entrypoint":"webhook-ingress","exportName":"resolveSingleWebhookTarget","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export"} @@ -4702,7 +4702,7 @@ {"closureHash":"efb74e84339c15d6f607b38d1d477b0504d47d04ae78e77083a1893c756a0b54","declaration":"export const WEBHOOK_RATE_LIMIT_DEFAULTS: Readonly<{ windowMs: 60000; maxRequests: 120; maxTrackedKeys: 4096;}>;","entrypoint":"webhook-ingress","exportName":"WEBHOOK_RATE_LIMIT_DEFAULTS","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"const","recordType":"export"} {"closureHash":"13b28ec9acae43c3bdcd683b9831154f88118dee8142e42b7eebfd59f08c602c","declaration":"export type BoundedCounter = BoundedCounter;","entrypoint":"webhook-ingress","exportName":"BoundedCounter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export"} {"closureHash":"9e737ae8c17038ef7becbd2bf1b756cfeaed571e2ded368c0fe3a446ebd1f666","declaration":"export type FixedWindowRateLimiter = FixedWindowRateLimiter;","entrypoint":"webhook-ingress","exportName":"FixedWindowRateLimiter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export"} -{"closureHash":"70269def5d23a381da6b57861cdc83ff7ce3c136e9e0c076e268a81b5f022f68","declaration":"export type RegisterWebhookPluginRouteOptions = RegisterWebhookPluginRouteOptions;","entrypoint":"webhook-ingress","exportName":"RegisterWebhookPluginRouteOptions","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export"} +{"closureHash":"de549f49c759d8e58bde941c0a037fe5ac52fe4c228f007b51d9d6831a00fdf2","declaration":"export type RegisterWebhookPluginRouteOptions = RegisterWebhookPluginRouteOptions;","entrypoint":"webhook-ingress","exportName":"RegisterWebhookPluginRouteOptions","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export"} {"closureHash":"b3e98619854462acfac6538ac9a2d0cefb88704a0eac712563f0996b07364152","declaration":"export type RegisterWebhookTargetOptions = RegisterWebhookTargetOptions;","entrypoint":"webhook-ingress","exportName":"RegisterWebhookTargetOptions","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export"} {"closureHash":"964c2bfd8f268c411ee7b982c9de02c207f4bec402dd62c9f998a59752bb9d1f","declaration":"export type RegisteredWebhookTarget = RegisteredWebhookTarget;","entrypoint":"webhook-ingress","exportName":"RegisteredWebhookTarget","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export"} {"closureHash":"aa552e5bdaefc9eb94d3b5a5cdf8c413fad555d9f452c6a2a6af5a3ce695c9d1","declaration":"export type WebhookAnomalyTracker = WebhookAnomalyTracker;","entrypoint":"webhook-ingress","exportName":"WebhookAnomalyTracker","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export"} diff --git a/src/auto-reply/reply/reply-run-registry.contracts.ts b/src/auto-reply/reply/reply-run-registry.contracts.ts new file mode 100644 index 000000000000..81c190eb9692 --- /dev/null +++ b/src/auto-reply/reply/reply-run-registry.contracts.ts @@ -0,0 +1,289 @@ +import type { ImageContent } from "../../llm/types.js"; +import type { MediaFact } from "../../media/media-facts.js"; +import type { PromptImageOrderEntry } from "../../media/prompt-image-order.js"; +import type { UserTurnTranscriptRecorder } from "../../sessions/user-turn-transcript.types.js"; +import type { + SourceReplyDeliveryMode, + TaskSuggestionDeliveryMode, +} from "../get-reply-options.types.js"; +import type { ReplyFollowupAdmissionBarrierTimeoutPolicy } from "./reply-dispatcher.types.js"; +import * as replyRunSettle from "./reply-run-finalization-lease.js"; + +type ReplyRunKey = string; + +type ReplyBackendKind = "embedded" | "cli"; + +type ReplyBackendCancelReason = "user_abort" | "restart" | "superseded"; + +export type ReplyBackendQueueMessageOptions = { + steeringMode?: "all"; + /** True when this queue item came from the channel's current user turn. */ + isInboundUserMessage?: boolean; + debounceMs?: number; + /** Ordered current-turn images to inject with the steering text. */ + images?: ImageContent[]; + imageOrder?: PromptImageOrderEntry[]; + /** Ordered facts represented by attachment text in this steering prompt. */ + media?: MediaFact[]; + deliveryTimeoutMs?: number; + waitForTranscriptCommit?: boolean; + /** Stable source identity for exact queued-message commit/cancellation matching. */ + queueIdentity?: string; + abortSignal?: AbortSignal; + /** Releases arrival ordering once the runtime has actually accepted this queue item. */ + onQueueAccepted?: (accepted: boolean) => void; + sourceReplyDeliveryMode?: SourceReplyDeliveryMode; + taskSuggestionDeliveryMode?: TaskSuggestionDeliveryMode; + /** Prepared channel turn to merge only at transcript persistence. */ + userTurnTranscriptRecorder?: UserTurnTranscriptRecorder; +}; + +export type ReplyBackendQueueMessageResult = { + /** Acceptance was irreversible, but the harness could not prove transcript commitment. */ + transcriptCommit: "unconfirmed"; + errorMessage: string; +}; + +export type ReplyBackendMessageInjection = { + /** Runtime-owned admission state; independent from token streaming. */ + isAvailable(): boolean; + queueMessage( + text: string, + options?: ReplyBackendQueueMessageOptions, + ): Promise; +}; + +export type ReplyBackendHandle = { + readonly kind: ReplyBackendKind; + readonly runId?: string; + readonly sourceReplyDeliveryMode?: SourceReplyDeliveryMode; + readonly taskSuggestionDeliveryMode?: TaskSuggestionDeliveryMode; + /** True only when queueMessage preserves images supplied in its options. */ + readonly supportsQueueMessageImages?: boolean; + cancel(reason?: ReplyBackendCancelReason): void; + readonly messageInjection?: ReplyBackendMessageInjection; + /** @deprecated Compatibility for shipped embedded handles. Use messageInjection. */ + isStreaming?: () => boolean; + isStopped?: () => boolean; + isAbortable?: () => boolean; + /** @deprecated Compatibility for shipped embedded handles. Use messageInjection. */ + queueMessage?: ( + text: string, + options?: ReplyBackendQueueMessageOptions, + ) => Promise; + /** + * Compatibility-only hook so legacy "abort compacting runs" paths can still + * find embedded runs that are compacting during the main run phase. + */ + isCompacting?: () => boolean; +}; + +export const replyMessageInjectionTargetOperation = Symbol("replyMessageInjectionTargetOperation"); +export type ReplyMessageInjectionTarget = { + readonly [replyMessageInjectionTargetOperation]: ReplyOperation; + /** Legacy targets stay leaf-bound even when their backend exposes a run id. */ + readonly identity: "leaf" | "run"; + readonly runId?: string; + readonly originatingLeafEntryId: string | null | undefined; +}; + +type ReplyMessageInjectionRejectionReason = + | "no_active_run" + | "not_running" + | "stale_run" + | "leaf_mismatch" + | "run_mismatch" + | "injection_unavailable" + | ReplyBackendQueueMessageMismatch + | "runtime_rejected"; + +export type ReplyMessageInjectionOutcome = + | { status: "accepted"; result?: ReplyBackendQueueMessageResult } + | { status: "rejected"; reason: ReplyMessageInjectionRejectionReason; errorMessage?: string }; + +export type ReplyMessageInjectionAttempt = { + /** Native run identity captured with the opaque operation target. */ + targetRunId: string | undefined; + /** Leaf-bound compatibility must reject before ACK instead of falling through. */ + rejectBeforeAck?: true; + /** Settles once the runtime accepts or rejects ownership of this exact message. */ + acceptance: Promise; + /** Settles after the backend confirms or rejects this exact injection. */ + outcome: Promise; +}; + +type ReplyBackendQueueMessageMismatch = + | "image_input_unsupported" + | "source_reply_delivery_mode_mismatch" + | "task_suggestion_delivery_mode_mismatch"; + +/** Prevents steering a turn into a run that cannot preserve its model-facing input. */ + +export type ReplyOperationPhase = + | "queued" + | "waiting_for_deferred_maintenance" + | "waiting_for_global_lane" + | "preflight_compacting" + | "memory_flushing" + | "running" + | "completed" + | "failed" + | "aborted"; + +type ReplyOperationFailureCode = + | "gateway_draining" + | "command_lane_cleared" + | "aborted_by_user" + | "session_corruption_reset" + | "run_stalled" + | "run_failed"; + +type ReplyOperationAbortCode = "aborted_by_user" | "aborted_for_restart"; + +type ReplyOperationResult = + | { kind: "completed" } + | { kind: "failed"; code: ReplyOperationFailureCode; cause?: unknown } + | { kind: "aborted"; code: ReplyOperationAbortCode }; + +export type ReplyOperation = { + readonly key: ReplyRunKey; + readonly sessionId: string; + /** Gateway lifecycle that admitted this process-local owner. */ + readonly lifecycleGeneration?: string; + readonly routeThreadId?: string | number; + /** Transcript branch leaf from which this operation was admitted. */ + readonly originatingLeafEntryId?: string | null; + readonly abortSignal: AbortSignal; + readonly resetTriggered: boolean; + /** + * True when this operation was admitted to recover a terminal session (a + * leftover failed/timeout/killed run). Concurrent visible turns reading the + * same terminal store snapshot must NOT force-clear such an operation: it is a + * sibling recovery already in flight, not the proven stale leftover. + */ + readonly terminalRecovery: boolean; + /** + * Sticky fact for audio accepted into this operation after its originating turn. + * Final delivery reads it because the original dispatch context cannot change. + */ + readonly acceptedSteeredInboundAudio: boolean; + readonly phase: ReplyOperationPhase; + readonly result: ReplyOperationResult | null; + /** Set when a stale-watchdog expiry forced this operation's run_stalled result. */ + readonly staleExpiryReason?: ReplyOperationStaleReason; + readonly startedAtMs: number; + readonly lastActivityAtMs: number; + /** True when this operation has owned the supplied session ID. */ + hasOwnedSessionId(sessionId: string): boolean; + recordActivity(): void; + setPhase( + next: + | "queued" + | "waiting_for_deferred_maintenance" + | "waiting_for_global_lane" + | "preflight_compacting" + | "memory_flushing" + | "running", + ): void; + /** Mark this operation as waiting on prior same-session maintenance. */ + markWaitingForDeferredMaintenance(): void; + /** Return a maintenance-waiting operation to queued if the run has not started. */ + markDeferredMaintenanceWaitEnded(): void; + /** Mark this operation as waiting for process-global run capacity. */ + markWaitingForGlobalLane(): void; + /** Return a global-lane-waiting operation to queued once capacity is granted. */ + markGlobalLaneWaitEnded(): void; + /** Mark this operation as an in-flight terminal-session recovery. */ + markTerminalRecovery(): void; + markAcceptedSteeredInboundAudio(): void; + updateSessionId(nextSessionId: string): void; + /** + * Move this queued operation to another session key's run slot. Native command + * turns admit under the slash SOURCE key; when the command continues into a full + * agent turn it must own the TARGET session's slot so concurrent target inbounds + * queue/steer instead of double-admitting. Throws ReplyRunAlreadyActiveError when + * the target slot is owned. + */ + updateSessionKey(nextSessionKey: string): void; + attachBackend(handle: ReplyBackendHandle): void; + detachBackend(handle: ReplyBackendHandle): void; + /** Reject later aborts after the backend has committed its terminal outcome. */ + freezeAbort(): void; + /** + * Keep a failed operation active until complete() releases the session lane. + * Dispatch uses this while a user-visible failure payload still needs delivery. + */ + retainFailureUntilComplete(): void; + /** Settles after the lifecycle owner's final delivery/persistence barrier. */ + readonly ownerSettlement?: Promise; + complete(): void; + /** + * Complete the operation, clear active-run state, then run follow-up work. + * Use when the follow-up can create another ReplyOperation for this session. + */ + completeThen(afterClear: () => void): void; + /** + * Clear active-run state immediately, but delay registered after-clear work + * until delivery or another external barrier settles. + */ + completeWithAfterClearBarrier( + barrier: PromiseLike, + timeout?: number | ReplyFollowupAdmissionBarrierTimeoutPolicy, + ): void; + fail(code: Exclude, cause?: unknown): void; + abortByUser(): boolean; + abortForRestart(): boolean; +}; + +export type ReplyRunRegistry = { + begin(params: { + sessionKey: string; + sessionId: string; + resetTriggered: boolean; + routeThreadId?: string | number; + originatingLeafEntryId?: string | null; + upstreamAbortSignal?: AbortSignal; + }): ReplyOperation; + get(sessionKey: string): ReplyOperation | undefined; + isActive(sessionKey: string): boolean; + resolveMessageInjectionTarget(params: { + sessionKey: string; + originatingLeafEntryId: string | null | undefined; + expectedRunId?: string; + }): ReplyMessageInjectionTarget | undefined; + abort(sessionKey: string): boolean; + waitForIdle( + sessionKey: string, + timeoutMs?: number | null, + opts?: { signal?: AbortSignal }, + ): Promise; + resolveSessionId(sessionKey: string): string | undefined; +}; + +export const REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS = 15_000; +// Terminal results must release the lane even if the owner never resumes. +// Without this, abort/failure can leave the session wedged until process restart. +export const REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS = 60_000; + +type ReplyOperationStaleReason = replyRunSettle.ReplyOperationStaleReason; + +export class ReplyRunAlreadyActiveError extends Error { + constructor(sessionKey: string) { + super(`Reply run already active for ${sessionKey}`); + this.name = "ReplyRunAlreadyActiveError"; + } +} + +export class ReplyRunFollowupAdmissionBlockedError extends Error { + constructor(sessionKey: string) { + super(`Reply follow-up admission is blocked for ${sessionKey}`); + this.name = "ReplyRunFollowupAdmissionBlockedError"; + } +} + +export class ReplyRunSuccessorAdmissionBlockedError extends Error { + constructor(sessionKey: string) { + super(`Reply successor admission is blocked for ${sessionKey}`); + this.name = "ReplyRunSuccessorAdmissionBlockedError"; + } +} diff --git a/src/auto-reply/reply/reply-run-registry.message-injection.ts b/src/auto-reply/reply/reply-run-registry.message-injection.ts new file mode 100644 index 000000000000..fb6d8633d791 --- /dev/null +++ b/src/auto-reply/reply/reply-run-registry.message-injection.ts @@ -0,0 +1,223 @@ +import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { createDeferred } from "../../shared/deferred.js"; +import { + replyMessageInjectionTargetOperation, + type ReplyBackendHandle, + type ReplyBackendMessageInjection, + type ReplyBackendQueueMessageOptions, + type ReplyBackendQueueMessageResult, + type ReplyMessageInjectionAttempt, + type ReplyMessageInjectionOutcome, + type ReplyMessageInjectionTarget, + type ReplyOperation, +} from "./reply-run-registry.contracts.js"; +import { + getAttachedBackend, + isReplyRunEvidenceStale, + replyRunState, +} from "./reply-run-registry.state.js"; + +type ReplyBackendQueueMessageMismatch = + | "image_input_unsupported" + | "source_reply_delivery_mode_mismatch" + | "task_suggestion_delivery_mode_mismatch"; + +type ReplyMessageInjectionRejectionReason = + | "no_active_run" + | "not_running" + | "stale_run" + | "leaf_mismatch" + | "run_mismatch" + | "injection_unavailable" + | ReplyBackendQueueMessageMismatch + | "runtime_rejected"; + +export function resolveReplyBackendQueueMessageMismatch( + backend: Pick< + ReplyBackendHandle, + "sourceReplyDeliveryMode" | "supportsQueueMessageImages" | "taskSuggestionDeliveryMode" + >, + options?: ReplyBackendQueueMessageOptions, +): ReplyBackendQueueMessageMismatch | undefined { + if (options?.images?.length && backend.supportsQueueMessageImages !== true) { + return "image_input_unsupported"; + } + if ( + options?.sourceReplyDeliveryMode === "message_tool_only" && + backend.sourceReplyDeliveryMode !== "message_tool_only" + ) { + return "source_reply_delivery_mode_mismatch"; + } + // User turns carry this own property even when disabled; internal wakeups + // omit it so they inherit the active run's already-negotiated tool surface. + if ( + options !== undefined && + Object.hasOwn(options, "taskSuggestionDeliveryMode") && + options?.taskSuggestionDeliveryMode !== backend.taskSuggestionDeliveryMode + ) { + return "task_suggestion_delivery_mode_mismatch"; + } + return undefined; +} + +function resolveReplyBackendMessageInjection( + backend: ReplyBackendHandle, +): ReplyBackendMessageInjection | undefined { + if (backend.messageInjection) { + return backend.messageInjection; + } + if (!backend.queueMessage) { + return undefined; + } + return { + isAvailable: () => { + if (backend.isStopped) { + return !backend.isStopped(); + } + // Legacy handles already expose the only capability that matters here: + // queueMessage. Let the runtime accept or reject instead of guessing from + // unrelated token-stream state. + return true; + }, + queueMessage: (text, options) => + options ? backend.queueMessage!(text, options) : backend.queueMessage!(text), + }; +} + +export function resolveReplyMessageInjectionRejection(params: { + operation: ReplyOperation | undefined; + originatingLeafEntryId: string | null | undefined; + expectedRunId?: string; + options?: ReplyBackendQueueMessageOptions; +}): + | { reason: ReplyMessageInjectionRejectionReason; errorMessage?: string } + | { backend: ReplyBackendHandle; injection: ReplyBackendMessageInjection } { + const { operation } = params; + if (!operation || replyRunState.activeRunsByKey.get(operation.key) !== operation) { + return { reason: "no_active_run" }; + } + if (operation.result || operation.phase !== "running") { + return { reason: "not_running" }; + } + const expectedRunId = normalizeOptionalString(params.expectedRunId); + // Exact run identity supersedes the operation's immutable origin leaf. The + // same run advances its transcript leaf during ordinary tool/output progress. + if (!expectedRunId && operation.originatingLeafEntryId !== params.originatingLeafEntryId) { + return { reason: "leaf_mismatch" }; + } + if (isReplyRunEvidenceStale(operation)) { + return { reason: "stale_run" }; + } + const backend = getAttachedBackend(operation); + const injection = backend ? resolveReplyBackendMessageInjection(backend) : undefined; + if (!backend || !injection) { + return { reason: "injection_unavailable" }; + } + if (expectedRunId && normalizeOptionalString(backend.runId) !== expectedRunId) { + return { reason: "run_mismatch" }; + } + try { + if (!injection.isAvailable()) { + return { reason: "injection_unavailable" }; + } + } catch (error) { + return { reason: "injection_unavailable", errorMessage: String(error) }; + } + const mismatch = resolveReplyBackendQueueMessageMismatch(backend, params.options); + return mismatch ? { reason: mismatch } : { backend, injection }; +} + +export function beginReplyMessageInjectionTarget( + target: ReplyMessageInjectionTarget, + text: string, + options?: ReplyBackendQueueMessageOptions, +): ReplyMessageInjectionAttempt { + const resolved = resolveReplyMessageInjectionRejection({ + operation: target[replyMessageInjectionTargetOperation], + originatingLeafEntryId: target.originatingLeafEntryId, + expectedRunId: target.identity === "run" ? target.runId : undefined, + options, + }); + if (!("injection" in resolved)) { + const immediateRejection = { status: "rejected" as const, ...resolved }; + return { + targetRunId: target.runId, + ...(target.identity === "leaf" ? { rejectBeforeAck: true as const } : {}), + acceptance: Promise.resolve(false), + outcome: Promise.resolve(immediateRejection), + }; + } + // Injection is user input, not run evidence: stamping activity here would let + // sub-10-minute user messages re-arm a wedged run's staleness window forever. + // Invoke before the first await. The capability owns the final synchronous + // admission check, matching Codex's active-turn lock boundary. + const acceptance = createDeferred(); + let acceptanceSettled = false; + const settleAcceptance = (accepted: boolean) => { + if (acceptanceSettled) { + return; + } + acceptanceSettled = true; + acceptance.resolve(accepted); + }; + const callerOnQueueAccepted = options?.onQueueAccepted; + const queueOptions: ReplyBackendQueueMessageOptions = { + ...options, + onQueueAccepted: (accepted) => { + settleAcceptance(accepted); + callerOnQueueAccepted?.(accepted); + }, + }; + let queued: Promise; + try { + queued = resolved.injection.queueMessage(text, queueOptions); + } catch (error) { + settleAcceptance(false); + const immediateRejection = { + status: "rejected" as const, + reason: "runtime_rejected" as const, + errorMessage: String(error), + }; + return { + targetRunId: target.runId, + acceptance: acceptance.promise, + outcome: Promise.resolve(immediateRejection), + }; + } + const outcome = queued.then( + (result): ReplyMessageInjectionOutcome => { + settleAcceptance(true); + return result ? { status: "accepted", result } : { status: "accepted" }; + }, + (error: unknown): ReplyMessageInjectionOutcome => { + settleAcceptance(false); + return { + status: "rejected", + reason: "runtime_rejected", + errorMessage: String(error), + }; + }, + ); + return { + targetRunId: target.runId, + acceptance: acceptance.promise, + outcome, + }; +} + +/** Abort only the operation captured by this target; never a same-key successor. */ +export function abortReplyMessageInjectionTarget(target: ReplyMessageInjectionTarget): boolean { + return target[replyMessageInjectionTargetOperation].abortByUser(); +} + +/** Record accepted input on the exact operation without rediscovering its session slot. */ +export function recordAcceptedReplyMessageInjectionTarget( + target: ReplyMessageInjectionTarget, + options?: { inboundAudio?: boolean }, +): void { + const operation = target[replyMessageInjectionTargetOperation]; + operation.recordActivity(); + if (options?.inboundAudio === true) { + operation.markAcceptedSteeredInboundAudio(); + } +} diff --git a/src/auto-reply/reply/reply-run-registry.operation.ts b/src/auto-reply/reply/reply-run-registry.operation.ts new file mode 100644 index 000000000000..de7cd833f9d1 --- /dev/null +++ b/src/auto-reply/reply/reply-run-registry.operation.ts @@ -0,0 +1,680 @@ +import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { + createAgentRunRestartAbortError, + isAgentRunRestartAbortReason, +} from "../../agents/run-termination.js"; +import { createAbortError } from "../../infra/abort-signal.js"; +import { getAgentEventLifecycleGeneration } from "../../infra/agent-events.js"; +import { diagnosticLogger as diag } from "../../logging/diagnostic-runtime.js"; +import { createDeferred } from "../../shared/deferred.js"; +import type { ReplyFollowupAdmissionBarrierTimeoutPolicy } from "./reply-dispatcher.types.js"; +import * as replyRunSettle from "./reply-run-finalization-lease.js"; +import { + REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS, + ReplyRunAlreadyActiveError, + ReplyRunFollowupAdmissionBlockedError, + ReplyRunSuccessorAdmissionBlockedError, + type ReplyOperation, + type ReplyOperationPhase, +} from "./reply-run-registry.contracts.js"; +import { + abortFrozenOperations, + attachedBackendByOperation, + clearReplyRunState, + createUserAbortError, + evictReplyOperationByOperation, + expireReplyOperationByOperation, + flushReplyOperationAfterClear, + getAttachedBackend, + isReplyOperationAbortable, + isReplyOperationPreBackendPhase, + markReplyRunDiagnosticProgress, + notifyReplyRunEnded, + operationsByUpstreamAbortSignal, + registerFollowupAdmissionBarrier, + registerWaitSessionId, + replyRunState, + retainStateUntilCompleteOperations, + type ReplyRunAdmissionBarrier, + runAfterReplyOperationClear, + startReplyOperationSuccessorBarriers, + type ReplyOperationStaleExpiryOptions, + updateFollowupAdmissionSessionId, + updateSuccessorAdmissionSessionId, + waitForReplyBarrierSettlement, +} from "./reply-run-registry.state.js"; + +type ReplyBackendCancelReason = "user_abort" | "restart" | "superseded"; +type ReplyOperationAbortCode = "aborted_by_user" | "aborted_for_restart"; +type ReplyOperationResult = NonNullable; +type ReplyOperationStaleReason = replyRunSettle.ReplyOperationStaleReason; + +export function createReplyOperation(params: { + sessionKey: string; + sessionId: string; + resetTriggered: boolean; + routeThreadId?: string | number; + originatingLeafEntryId?: string | null; + upstreamAbortSignal?: AbortSignal; + respectFollowupAdmissionBarrier?: boolean; +}): ReplyOperation { + const sessionKey = normalizeOptionalString(params.sessionKey); + const sessionId = normalizeOptionalString(params.sessionId); + if (!sessionKey) { + throw new Error("Reply operations require a canonical sessionKey"); + } + if (!sessionId) { + throw new Error("Reply operations require a sessionId"); + } + if ( + params.respectFollowupAdmissionBarrier && + replyRunState.followupAdmissionBarriersByKey.has(sessionKey) + ) { + throw new ReplyRunFollowupAdmissionBlockedError(sessionKey); + } + if (replyRunState.activeRunsByKey.has(sessionKey)) { + throw new ReplyRunAlreadyActiveError(sessionKey); + } + if (replyRunState.successorAdmissionBarriersByKey.has(sessionKey)) { + throw new ReplyRunSuccessorAdmissionBlockedError(sessionKey); + } + + const controller = new AbortController(); + // Mutable so updateSessionKey can move the run slot (command-turn continuation + // adoption); every closure below must read this, never params.sessionKey. + let currentSessionKey = sessionKey; + let currentSessionId = sessionId; + let phase: ReplyOperationPhase = "queued"; + let phaseBeforeGlobalLaneWait: "queued" | "running" | undefined; + let staleExpiryReason: ReplyOperationStaleReason | undefined; + let result: ReplyOperationResult | null = null; + let stateCleared = false; + let clearBarrierSettlement: Promise | undefined; + let pendingClearBarrier: ReplyRunAdmissionBarrier | undefined; + let retainFailureUntilComplete = false; + let terminalRecovery = false; + let acceptedSteeredInboundAudio = false; + const ownerSettlement = createDeferred(); + let ownerSettled = false; + const settleOwner = () => { + if (ownerSettled) { + return; + } + ownerSettled = true; + ownerSettlement.resolve(undefined); + }; + const startedAtMs = Date.now(); + const lifecycleGeneration = getAgentEventLifecycleGeneration(); + let lastActivityAtMs = startedAtMs; + const upstreamAbortSignal = params.upstreamAbortSignal; + let upstreamAbortHandler: (() => void) | undefined; + const detachUpstreamAbort = () => { + if (!upstreamAbortHandler) { + return; + } + upstreamAbortSignal?.removeEventListener("abort", upstreamAbortHandler); + upstreamAbortHandler = undefined; + }; + const ownedSessionIds = new Set([sessionId]); + const recordActivity = () => { + lastActivityAtMs = Date.now(); + }; + const setResult = (next: ReplyOperationResult) => { + result = next; + recordActivity(); + }; + + const clearState = ( + afterClearBarrier?: PromiseLike, + followupAdmissionBarrierTimeout?: number | ReplyFollowupAdmissionBarrierTimeoutPolicy, + ) => { + if (stateCleared) { + return; + } + stateCleared = true; + terminalSettleTimer.clear(); + finalizationLease.clear(); + expireReplyOperationByOperation.delete(operation); + evictReplyOperationByOperation.delete(operation); + detachUpstreamAbort(); + const registeredBarrier = afterClearBarrier + ? registerFollowupAdmissionBarrier( + currentSessionKey, + currentSessionId, + afterClearBarrier, + followupAdmissionBarrierTimeout, + ) + : pendingClearBarrier; + pendingClearBarrier = undefined; + updateFollowupAdmissionSessionId(currentSessionKey, currentSessionId); + // Recovery-owner handoff must begin before the old slot wakes a successor; + // otherwise that successor can snapshot durable state the handoff then mutates. + startReplyOperationSuccessorBarriers(operation); + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "reply_operation:ended", + }); + clearReplyRunState({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + operation, + }); + if (!registeredBarrier) { + flushReplyOperationAfterClear(operation, currentSessionId); + return; + } + void registeredBarrier.settled.then(() => + flushReplyOperationAfterClear(operation, registeredBarrier.sessionId), + ); + clearBarrierSettlement = registeredBarrier.settled; + }; + + const abortInternally = (reason?: unknown) => { + if (!controller.signal.aborted) { + controller.abort(reason); + } + }; + + const scheduleTerminalSettle = () => { + if (stateCleared) { + return; + } + terminalSettleTimer.scheduleOnce(REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS); + }; + + const abortWithReason = ( + reason: ReplyBackendCancelReason, + abortReason: unknown, + opts?: { abortedCode?: ReplyOperationAbortCode }, + ) => { + if (opts?.abortedCode && !result) { + setResult({ kind: "aborted", code: opts.abortedCode }); + detachUpstreamAbort(); + } + phase = "aborted"; + abortInternally(abortReason); + getAttachedBackend(operation)?.cancel(reason); + }; + + const operation: ReplyOperation = { + get key() { + return currentSessionKey; + }, + get sessionId() { + return currentSessionId; + }, + lifecycleGeneration, + get routeThreadId() { + return params.routeThreadId; + }, + get originatingLeafEntryId() { + return params.originatingLeafEntryId; + }, + get abortSignal() { + return controller.signal; + }, + get resetTriggered() { + return params.resetTriggered; + }, + get terminalRecovery() { + return terminalRecovery; + }, + get acceptedSteeredInboundAudio() { + return acceptedSteeredInboundAudio; + }, + get phase() { + return phase; + }, + get result() { + return result; + }, + get staleExpiryReason() { + return staleExpiryReason; + }, + get startedAtMs() { + return startedAtMs; + }, + get lastActivityAtMs() { + return lastActivityAtMs; + }, + hasOwnedSessionId(candidateSessionId) { + const normalizedSessionId = normalizeOptionalString(candidateSessionId); + return normalizedSessionId ? ownedSessionIds.has(normalizedSessionId) : false; + }, + recordActivity() { + finalizationLease.recordActivity(); + }, + setPhase(next) { + if (result) { + return; + } + recordActivity(); + phase = next; + }, + markWaitingForDeferredMaintenance() { + if (result || phase !== "queued") { + return; + } + phase = "waiting_for_deferred_maintenance"; + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "deferred_maintenance:waiting", + }); + }, + markDeferredMaintenanceWaitEnded() { + if (result || phase !== "waiting_for_deferred_maintenance") { + return; + } + phase = "queued"; + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "deferred_maintenance:wait_ended", + }); + }, + markWaitingForGlobalLane() { + if (result || (phase !== "queued" && phase !== "running")) { + return; + } + // Queued-on-lane is healthy waiting, not a wedged run. Removing this phase + // lets stale recovery silently drop replies while global capacity is busy. + phaseBeforeGlobalLaneWait = phase; + phase = "waiting_for_global_lane"; + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "global_lane:waiting", + }); + }, + markGlobalLaneWaitEnded() { + if (result || phase !== "waiting_for_global_lane") { + return; + } + phase = phaseBeforeGlobalLaneWait ?? "queued"; + phaseBeforeGlobalLaneWait = undefined; + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "global_lane:wait_ended", + }); + }, + markTerminalRecovery() { + terminalRecovery = true; + }, + markAcceptedSteeredInboundAudio() { + acceptedSteeredInboundAudio = true; + }, + updateSessionId(nextSessionId) { + if (result) { + return; + } + const normalizedNextSessionId = normalizeOptionalString(nextSessionId); + if (!normalizedNextSessionId || normalizedNextSessionId === currentSessionId) { + return; + } + recordActivity(); + if ( + replyRunState.activeKeysBySessionId.has(normalizedNextSessionId) && + replyRunState.activeKeysBySessionId.get(normalizedNextSessionId) !== currentSessionKey + ) { + throw new Error( + `Cannot rebind reply operation ${currentSessionKey} to active session ${normalizedNextSessionId}`, + ); + } + replyRunState.activeKeysBySessionId.delete(currentSessionId); + registerWaitSessionId(currentSessionKey, currentSessionId); + currentSessionId = normalizedNextSessionId; + ownedSessionIds.add(currentSessionId); + updateFollowupAdmissionSessionId(currentSessionKey, currentSessionId); + updateSuccessorAdmissionSessionId(operation, currentSessionId); + replyRunState.activeSessionIdsByKey.set(currentSessionKey, currentSessionId); + replyRunState.activeKeysBySessionId.set(currentSessionId, currentSessionKey); + registerWaitSessionId(currentSessionKey, currentSessionId); + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "reply_operation:session_updated", + }); + }, + updateSessionKey(nextSessionKey) { + const normalizedNextKey = normalizeOptionalString(nextSessionKey); + if (!normalizedNextKey) { + throw new Error("Reply operations require a canonical sessionKey"); + } + if (normalizedNextKey === currentSessionKey) { + return; + } + // Only a queued reservation may move slots: once the run started (or the + // operation settled), abort/steer/wait paths already resolved this key. + if (result || stateCleared || phase !== "queued") { + throw new Error(`Cannot rekey reply operation ${currentSessionKey} in phase ${phase}`); + } + if (replyRunState.activeRunsByKey.has(normalizedNextKey)) { + throw new ReplyRunAlreadyActiveError(normalizedNextKey); + } + if (replyRunState.successorAdmissionBarriersByKey.has(normalizedNextKey)) { + throw new ReplyRunSuccessorAdmissionBlockedError(normalizedNextKey); + } + recordActivity(); + const previousKey = currentSessionKey; + replyRunState.activeRunsByKey.delete(previousKey); + replyRunState.activeSessionIdsByKey.delete(previousKey); + currentSessionKey = normalizedNextKey; + replyRunState.activeRunsByKey.set(currentSessionKey, operation); + replyRunState.activeSessionIdsByKey.set(currentSessionKey, currentSessionId); + replyRunState.activeKeysBySessionId.set(currentSessionId, currentSessionKey); + // Wait/abort lookups resolve keys via owned session IDs; move them so + // waitForReplyRunEndBySessionId keeps finding this operation. + for (const ownedSessionId of ownedSessionIds) { + if (replyRunState.waitKeysBySessionId.get(ownedSessionId) === previousKey) { + replyRunState.waitKeysBySessionId.set(ownedSessionId, currentSessionKey); + } + } + // The previous key's slot is idle now; wake turns waiting on it. + notifyReplyRunEnded(previousKey); + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "reply_operation:session_key_adopted", + }); + }, + attachBackend(handle) { + if (result) { + handle.cancel( + result.kind === "aborted" + ? result.code === "aborted_for_restart" + ? "restart" + : "user_abort" + : "superseded", + ); + return; + } + recordActivity(); + attachedBackendByOperation.set(operation, handle); + if (controller.signal.aborted) { + handle.cancel("superseded"); + } + }, + detachBackend(handle) { + if (getAttachedBackend(operation) === handle) { + attachedBackendByOperation.delete(operation); + } + }, + freezeAbort() { + abortFrozenOperations.add(operation); + detachUpstreamAbort(); + finalizationLease.begin(); + }, + retainFailureUntilComplete() { + retainFailureUntilComplete = true; + }, + ownerSettlement: ownerSettlement.promise, + complete() { + if (!result) { + setResult({ kind: "completed" }); + phase = "completed"; + } + clearState(); + settleOwner(); + }, + completeThen(afterClear) { + runAfterReplyOperationClear(operation, afterClear); + operation.complete(); + }, + completeWithAfterClearBarrier(barrier, timeoutMs) { + if (!result) { + setResult({ kind: "completed" }); + phase = "completed"; + } + const wasAlreadyCleared = stateCleared; + const ownerCompletionSettlement = pendingClearBarrier + ? waitForReplyBarrierSettlement(barrier, timeoutMs) + : undefined; + clearState(barrier, timeoutMs); + // This barrier owns dispatch delivery and terminal persistence. Stale + // expiry may have already cleared the slot, but recovery must still wait + // for that old owner's durable work before admitting a queued turn. + const completionSettlement = wasAlreadyCleared + ? waitForReplyBarrierSettlement(barrier, timeoutMs) + : (ownerCompletionSettlement ?? clearBarrierSettlement); + if (completionSettlement) { + void completionSettlement.then(settleOwner); + } else { + settleOwner(); + } + }, + fail(code, cause) { + abortFrozenOperations.add(operation); + detachUpstreamAbort(); + finalizationLease.clear(); + if (!result) { + setResult({ kind: "failed", code, cause }); + phase = "failed"; + } + if (!retainFailureUntilComplete && !retainStateUntilCompleteOperations.has(operation)) { + clearState(); + } else { + scheduleTerminalSettle(); + } + }, + abortByUser() { + if (!isReplyOperationAbortable(operation)) { + return false; + } + const phaseBeforeAbort = phase; + abortWithReason("user_abort", createUserAbortError(), { + abortedCode: "aborted_by_user", + }); + if ( + isReplyOperationPreBackendPhase(phaseBeforeAbort) && + !retainStateUntilCompleteOperations.has(operation) + ) { + clearState(); + } else { + scheduleTerminalSettle(); + } + return true; + }, + abortForRestart() { + if (!isReplyOperationAbortable(operation)) { + return false; + } + const phaseBeforeAbort = phase; + abortWithReason("restart", createAgentRunRestartAbortError(), { + abortedCode: "aborted_for_restart", + }); + if ( + isReplyOperationPreBackendPhase(phaseBeforeAbort) && + !retainStateUntilCompleteOperations.has(operation) + ) { + clearState(); + } else { + scheduleTerminalSettle(); + } + return true; + }, + }; + + expireReplyOperationByOperation.set(operation, (reason, options) => { + if (replyRunState.activeRunsByKey.get(currentSessionKey) !== operation) { + return false; + } + // Set the terminal result BEFORE cancelling the backend: cancel can + // synchronously re-enter abortByUser() from the run loop's abort handler, + // which would stamp aborted_by_user and misattribute a watchdog expiry. + if (!result) { + abortFrozenOperations.add(operation); + detachUpstreamAbort(); + // The reason distinguishes pre-run drops (user got nothing; feedback owed) + // from post-output stalls (finalization/terminal cleanup; feedback is noise). + staleExpiryReason = reason; + setResult({ kind: "failed", code: "run_stalled" }); + phase = "failed"; + } + const logStaleTakeoverRelease = () => { + diag.warn( + `reply run stale takeover: forced release sessionKey=${currentSessionKey} reason=${reason} phase=${phase} result=${replyRunSettle.formatReplyOperationResult( + result, + )} ageMs=${Date.now() - lastActivityAtMs} ranForMs=${Date.now() - startedAtMs}`, + ); + }; + if (options?.afterClearBarrier) { + // Prepare the recovery fence before cancellation, but retain exact lane + // ownership until cancel returns or the backend re-enters completion. + pendingClearBarrier = registerFollowupAdmissionBarrier( + currentSessionKey, + currentSessionId, + options.afterClearBarrier, + options.followupAdmissionBarrierTimeout, + ); + } + const backend = getAttachedBackend(operation); + let cancelFailed = false; + try { + backend?.cancel("superseded"); + } catch (error) { + cancelFailed = true; + diag.warn( + `reply run stale takeover cancel failed: sessionKey=${currentSessionKey} reason=${reason} owner=${stateCleared ? "completed" : "retained"} error=${String(error)}`, + ); + } + abortInternally(createAbortError("Reply operation expired as stale")); + if (stateCleared) { + logStaleTakeoverRelease(); + return true; + } + // cancel() only requests shutdown. A missing backend can also be a live + // pre-attachment owner, so only complete() may release the exact lane token. + if (!cancelFailed) { + diag.warn( + `reply run stale takeover retained: sessionKey=${currentSessionKey} reason=${reason} owner=awaiting_terminal_completion backend=${backend ? "attached" : "pending"}`, + ); + } + scheduleTerminalSettle(); + return false; + }); + const finalizationLease = replyRunSettle.createReplyRunFinalizationLease({ + owner: operation, + canExpire: () => + !stateCleared && + !result && + replyRunState.activeRunsByKey.get(currentSessionKey) === operation, + onActivity: recordActivity, + onFinalizationProgress: () => + markReplyRunDiagnosticProgress({ + sessionKey: currentSessionKey, + sessionId: currentSessionId, + reason: "reply_operation:finalizing_progress", + }), + onExpire: () => { + diag.warn( + `reply run finalization settle: forced release sessionKey=${currentSessionKey} phase=${phase} result=${replyRunSettle.formatReplyOperationResult( + result, + )} ageMs=${Date.now() - lastActivityAtMs} ranForMs=${Date.now() - startedAtMs}`, + ); + const expired = expireReplyOperationByOperation.get(operation)?.("finalization_stalled"); + if (expired === false && replyRunState.activeRunsByKey.get(currentSessionKey) === operation) { + // This lease is the finalization owner's bounded shutdown deadline. + // Do not grant a second terminal-settle lifetime after it expires. + forceClearReplyOperation(operation); + } + }, + }); + const terminalSettleTimer = replyRunSettle.createReplyRunSettleTimer({ + canExpire: () => replyRunState.activeRunsByKey.get(currentSessionKey) === operation, + onExpire: () => { + // Retained terminal results get one delivery grace window, not a second lifetime. + diag.warn( + `reply run terminal settle: forced release sessionKey=${currentSessionKey} phase=${phase} result=${replyRunSettle.formatReplyOperationResult( + result, + )} ageMs=${Date.now() - lastActivityAtMs} ranForMs=${Date.now() - startedAtMs}`, + ); + clearState(); + }, + }); + + evictReplyOperationByOperation.set(operation, () => { + if (stateCleared) { + return; + } + if (!result) { + setResult({ kind: "aborted", code: "aborted_for_restart" }); + phase = "aborted"; + } + abortInternally(createAgentRunRestartAbortError()); + let cancelError: unknown; + let cancelFailed = false; + try { + getAttachedBackend(operation)?.cancel("restart"); + } catch (error) { + cancelFailed = true; + cancelError = error; + diag.warn( + `reply run lifecycle eviction cancel failed: sessionKey=${currentSessionKey} error=${String(error)}`, + ); + } finally { + clearState(); + } + if (cancelFailed) { + throw cancelError; + } + }); + + replyRunState.activeRunsByKey.set(sessionKey, operation); + replyRunState.activeSessionIdsByKey.set(sessionKey, currentSessionId); + replyRunState.activeKeysBySessionId.set(currentSessionId, sessionKey); + registerWaitSessionId(sessionKey, currentSessionId); + markReplyRunDiagnosticProgress({ + sessionKey, + sessionId: currentSessionId, + reason: "reply_operation:queued", + }); + if (upstreamAbortSignal) { + operationsByUpstreamAbortSignal.set(upstreamAbortSignal, operation); + const abortFromUpstream = () => { + if (result) { + return; + } + const restart = isAgentRunRestartAbortReason(upstreamAbortSignal.reason); + const phaseBeforeAbort = phase; + abortWithReason(restart ? "restart" : "user_abort", upstreamAbortSignal.reason, { + abortedCode: restart ? "aborted_for_restart" : "aborted_by_user", + }); + if ( + isReplyOperationPreBackendPhase(phaseBeforeAbort) && + !retainStateUntilCompleteOperations.has(operation) + ) { + clearState(); + } else { + scheduleTerminalSettle(); + } + }; + if (upstreamAbortSignal.aborted) { + abortFromUpstream(); + } else { + upstreamAbortHandler = abortFromUpstream; + upstreamAbortSignal.addEventListener("abort", upstreamAbortHandler, { once: true }); + } + } + + return operation; +} + +export function expireStaleReplyOperation( + operation: ReplyOperation, + reason: ReplyOperationStaleReason, + options?: ReplyOperationStaleExpiryOptions, +): boolean { + return expireReplyOperationByOperation.get(operation)?.(reason, options) ?? false; +} + +export function forceClearReplyOperation(operation: ReplyOperation, cause?: unknown): boolean { + if (replyRunState.activeRunsByKey.get(operation.key) !== operation) { + return false; + } + operation.fail("run_failed", cause); + operation.complete(); + return true; +} diff --git a/src/auto-reply/reply/reply-run-registry.registry.ts b/src/auto-reply/reply/reply-run-registry.registry.ts new file mode 100644 index 000000000000..8d9445d0bb24 --- /dev/null +++ b/src/auto-reply/reply/reply-run-registry.registry.ts @@ -0,0 +1,499 @@ +// Tracks active reply runs so stop, queue, and status commands can coordinate. +import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { + isAgentEventLifecycleGenerationCurrent, + registerAgentEventLifecycleRotationHandler, +} from "../../infra/agent-events.js"; +import { resolveTimerTimeoutMs } from "../../shared/number-coercion.js"; +import * as replyRunSettle from "./reply-run-finalization-lease.js"; +import { + replyMessageInjectionTargetOperation, + type ReplyMessageInjectionTarget, + type ReplyOperation, + type ReplyOperationPhase, + type ReplyRunRegistry, +} from "./reply-run-registry.contracts.js"; +import { resolveReplyMessageInjectionRejection } from "./reply-run-registry.message-injection.js"; +import { + createReplyOperation, + expireStaleReplyOperation, + forceClearReplyOperation, +} from "./reply-run-registry.operation.js"; +import { + clearReplyRunState, + evictReplyOperationByOperation, + getAttachedBackend, + isReplyOperationPreBackendPhase, + isReplyRunCompacting, + isReplyRunEvidenceStale, + markReplyRunDiagnosticProgress, + replyRunState, + resolveReplyRunForCurrentSessionId, + resolveReplyRunWaitKey, + type ReplyRunAdmissionBarrier, +} from "./reply-run-registry.state.js"; + +type ReplyOperationStaleReason = replyRunSettle.ReplyOperationStaleReason; + +type ReplyRunWaiter = { + finish: (ended: boolean) => void; + timer?: NodeJS.Timeout; +}; + +export async function waitForReplyOperationOwnerSettlement( + operation: ReplyOperation, + timeoutMs: number, +): Promise { + const settlement = operation.ownerSettlement; + if (!settlement) { + return true; + } + const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 100, 100); + let timer: NodeJS.Timeout | undefined; + const settled = await Promise.race([ + settlement.then(() => true), + new Promise((resolve) => { + timer = setTimeout(() => resolve(false), resolvedTimeoutMs); + timer.unref?.(); + }), + ]); + if (timer) { + clearTimeout(timer); + } + return settled; +} + +export function expireStaleReplyRunBySessionId( + sessionId: string, + reason: ReplyOperationStaleReason, + options?: Parameters[2], +): boolean { + const operation = resolveReplyRunForCurrentSessionId(sessionId); + return operation ? expireStaleReplyOperation(operation, reason, options) : false; +} + +// lastActivityAtMs is refreshed by agent events only; timers and user-message + +export function markReplyOperationGlobalLaneWaitProgress(operation: ReplyOperation): void { + if (operation.result || operation.phase !== "waiting_for_global_lane") { + return; + } + markReplyRunDiagnosticProgress({ + sessionKey: operation.key, + sessionId: operation.sessionId, + reason: "global_lane:waiting", + }); +} + +export function isReplyRunEvidenceStaleBySessionId(sessionId: string): boolean { + const operation = resolveReplyRunForCurrentSessionId(sessionId); + return operation ? isReplyRunEvidenceStale(operation) : false; +} + +export const replyRunRegistry: ReplyRunRegistry = { + begin(params) { + return createReplyOperation(params); + }, + get(sessionKey) { + const normalizedSessionKey = normalizeOptionalString(sessionKey); + if (!normalizedSessionKey) { + return undefined; + } + return replyRunState.activeRunsByKey.get(normalizedSessionKey); + }, + isActive(sessionKey) { + const normalizedSessionKey = normalizeOptionalString(sessionKey); + if (!normalizedSessionKey) { + return false; + } + return replyRunState.activeRunsByKey.has(normalizedSessionKey); + }, + resolveMessageInjectionTarget({ sessionKey, originatingLeafEntryId, expectedRunId }) { + const operation = this.get(sessionKey); + const resolved = resolveReplyMessageInjectionRejection({ + operation, + originatingLeafEntryId, + expectedRunId, + }); + if (!("injection" in resolved)) { + return undefined; + } + const target: ReplyMessageInjectionTarget = { + [replyMessageInjectionTargetOperation]: operation!, + identity: normalizeOptionalString(expectedRunId) ? "run" : "leaf", + ...(resolved.backend.runId ? { runId: resolved.backend.runId } : {}), + originatingLeafEntryId, + }; + return target; + }, + abort(sessionKey) { + const operation = this.get(sessionKey); + if (!operation) { + return false; + } + return operation.abortByUser(); + }, + waitForIdle(sessionKey, timeoutMs, opts) { + const normalizedSessionKey = normalizeOptionalString(sessionKey); + if (!normalizedSessionKey || !replyRunState.activeRunsByKey.has(normalizedSessionKey)) { + return Promise.resolve(true); + } + if (opts?.signal?.aborted) { + return Promise.resolve(false); + } + return new Promise((resolve) => { + const waiters = replyRunState.waitersByKey.get(normalizedSessionKey) ?? new Set(); + let abortHandler: (() => void) | undefined; + let settled = false; + const waiter: ReplyRunWaiter = { + finish: (ended) => { + if (settled) { + return; + } + settled = true; + waiters.delete(waiter); + if (waiters.size === 0) { + replyRunState.waitersByKey.delete(normalizedSessionKey); + } + if (waiter.timer) { + clearTimeout(waiter.timer); + } + if (abortHandler) { + opts?.signal?.removeEventListener("abort", abortHandler); + } + resolve(ended); + }, + }; + if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) { + waiter.timer = setTimeout( + () => waiter.finish(false), + resolveTimerTimeoutMs(timeoutMs, 100, 100), + ); + } + if (opts?.signal) { + abortHandler = () => waiter.finish(false); + opts.signal.addEventListener("abort", abortHandler, { once: true }); + } + waiters.add(waiter); + replyRunState.waitersByKey.set(normalizedSessionKey, waiters); + if (!replyRunState.activeRunsByKey.has(normalizedSessionKey)) { + waiter.finish(true); + } + }); + }, + resolveSessionId(sessionKey) { + const normalizedSessionKey = normalizeOptionalString(sessionKey); + if (!normalizedSessionKey) { + return undefined; + } + return replyRunState.activeSessionIdsByKey.get(normalizedSessionKey); + }, +}; + +export function resolveActiveReplyRunSessionId(sessionKey: string): string | undefined { + return replyRunRegistry.resolveSessionId(sessionKey); +} + +/** Cancels the current reply backend only when its native run identity matches exactly. */ +export function supersedeReplyRunByRunId(runId: string, beforeCancel: () => void): boolean { + const expectedRunId = normalizeOptionalString(runId); + if (!expectedRunId) { + return false; + } + for (const operation of replyRunState.activeRunsByKey.values()) { + const backend = getAttachedBackend(operation); + if (normalizeOptionalString(backend?.runId) !== expectedRunId) { + continue; + } + beforeCancel(); + backend?.cancel("superseded"); + return true; + } + return false; +} + +export function resolveActiveReplyRunThreadId(sessionKey: string): string | number | undefined { + return replyRunRegistry.get(sessionKey)?.routeThreadId; +} + +export function isReplyRunActiveForSessionId(sessionId: string): boolean { + return resolveReplyRunForCurrentSessionId(sessionId) !== undefined; +} + +export function resolveReplyRunPhaseForSessionId( + sessionId: string, +): ReplyOperationPhase | undefined { + return resolveReplyRunForCurrentSessionId(sessionId)?.phase; +} + +export function isReplyRunAbortableForCompaction(sessionId: string): boolean { + const operation = resolveReplyRunForCurrentSessionId(sessionId); + // Manual compaction uses this as a coordination gate: a finalizing run still + // needs to drain even when its frozen outcome rejects the abort itself. + return Boolean(operation && !isReplyOperationPreBackendPhase(operation.phase)); +} + +export function abortReplyRunBySessionId(sessionId: string): boolean { + const operation = resolveReplyRunForCurrentSessionId(sessionId); + if (!operation) { + return false; + } + return operation.abortByUser(); +} + +export function resolveActiveReplyOperationForSessionId( + sessionId: string, +): ReplyOperation | undefined { + return resolveReplyRunForCurrentSessionId(sessionId); +} + +export function forceClearReplyRunBySessionId(sessionId: string, cause?: unknown): boolean { + const operation = resolveReplyRunForCurrentSessionId(sessionId); + return operation ? forceClearReplyOperation(operation, cause) : false; +} + +export function clearReplyRunForResetBySessionId(sessionId: string): void { + const operation = resolveReplyRunForCurrentSessionId(sessionId); + if (!operation || isReplyOperationPreBackendPhase(operation.phase)) { + return; + } + operation.abortForRestart(); + // Backend cancellation may synchronously retire this operation and admit a + // replacement. Only clear the exact archived operation resolved above. + if (replyRunState.activeRunsByKey.get(operation.key) === operation) { + operation.complete(); + } +} + +export function waitForReplyRunEndBySessionId( + sessionId: string, + timeoutMs?: number | null, +): Promise { + const waitKey = resolveReplyRunWaitKey(sessionId); + if (!waitKey) { + return Promise.resolve(true); + } + return replyRunRegistry.waitForIdle(waitKey, timeoutMs); +} + +async function waitForReplyRunAdmissionBarrier(params: { + barriersByKey: Map; + minimumTimeoutMs: number; + sessionKey: string; + signal?: AbortSignal; + timeoutMs?: number | null; +}): Promise<{ settled: boolean; sessionId?: string }> { + const deadline = + typeof params.timeoutMs === "number" + ? Date.now() + + resolveTimerTimeoutMs(params.timeoutMs, params.minimumTimeoutMs, params.minimumTimeoutMs) + : undefined; + let sessionId: string | undefined; + while (true) { + if (params.signal?.aborted) { + return { settled: false }; + } + const barrier = params.barriersByKey.get(params.sessionKey); + if (!barrier) { + return { settled: true, sessionId }; + } + const remainingMs = deadline === undefined ? undefined : deadline - Date.now(); + if (remainingMs !== undefined && remainingMs <= 0) { + return { settled: false }; + } + let timer: NodeJS.Timeout | undefined; + let abortHandler: (() => void) | undefined; + const outcome = await Promise.race([ + barrier.settled.then(() => true), + ...(remainingMs !== undefined + ? [ + new Promise((resolve) => { + timer = setTimeout(() => resolve(false), Math.max(1, remainingMs)); + timer.unref?.(); + }), + ] + : []), + ...(params.signal + ? [ + new Promise((resolve) => { + abortHandler = () => resolve(false); + params.signal?.addEventListener("abort", abortHandler, { once: true }); + if (params.signal?.aborted) { + abortHandler(); + } + }), + ] + : []), + ]); + if (timer) { + clearTimeout(timer); + } + if (abortHandler) { + params.signal?.removeEventListener("abort", abortHandler); + } + if (!outcome) { + return { settled: false }; + } + sessionId = barrier.sessionId; + } +} + +export async function waitForReplyRunFollowupAdmission( + sessionKey: string, + timeoutMs: number, + opts?: { signal?: AbortSignal }, +): Promise<{ settled: boolean; sessionId?: string }> { + const normalizedSessionKey = normalizeOptionalString(sessionKey); + return normalizedSessionKey + ? await waitForReplyRunAdmissionBarrier({ + barriersByKey: replyRunState.followupAdmissionBarriersByKey, + minimumTimeoutMs: 100, + sessionKey: normalizedSessionKey, + signal: opts?.signal, + timeoutMs, + }) + : { settled: true }; +} + +export async function waitForReplyRunSuccessorAdmission( + sessionKey: string, + timeoutMs?: number | null, + opts?: { signal?: AbortSignal }, +): Promise<{ settled: boolean; sessionId?: string }> { + const normalizedSessionKey = normalizeOptionalString(sessionKey); + return normalizedSessionKey + ? await waitForReplyRunAdmissionBarrier({ + barriersByKey: replyRunState.successorAdmissionBarriersByKey, + minimumTimeoutMs: 0, + sessionKey: normalizedSessionKey, + signal: opts?.signal, + timeoutMs, + }) + : { settled: true }; +} + +export function abortActiveReplyRuns(opts: { + mode: "all" | "compacting"; + onAbortError?: (sessionId: string, error: unknown) => void; +}): boolean { + let aborted = false; + for (const operation of replyRunState.activeRunsByKey.values()) { + if (opts.mode === "compacting" && !isReplyRunCompacting(operation)) { + continue; + } + try { + if (operation.abortForRestart()) { + aborted = true; + } + } catch (error) { + if (operation.result?.kind === "aborted" && operation.result.code === "aborted_for_restart") { + aborted = true; + } + opts.onAbortError?.(operation.sessionId, error); + } + } + return aborted; +} + +export function getActiveReplyRunCount(): number { + return replyRunState.activeRunsByKey.size; +} + +export function listActiveReplyRunSessionIds(): string[] { + return [...replyRunState.activeSessionIdsByKey.values()]; +} + +export function listActiveReplyRunSessionKeys(): string[] { + return [...replyRunState.activeSessionIdsByKey.keys()]; +} + +function evictPriorLifecycleReplyRuns(): void { + const errors: unknown[] = []; + for (const operation of replyRunState.activeRunsByKey.values()) { + if ( + operation.lifecycleGeneration && + isAgentEventLifecycleGenerationCurrent(operation.lifecycleGeneration) + ) { + continue; + } + const evict = evictReplyOperationByOperation.get(operation); + if (evict) { + try { + evict(); + } catch (error) { + errors.push(error); + try { + clearReplyRunState({ + sessionKey: operation.key, + sessionId: operation.sessionId, + operation, + }); + } catch (clearError) { + errors.push(clearError); + } + } + continue; + } + // Pre-generation hot-loaded operations have no retained callback, but their + // public method still closes over the module instance that owns the backend. + try { + if (!operation.abortForRestart()) { + errors.push(new Error(`Stale reply operation was not abortable: ${operation.key}`)); + } + } catch (error) { + errors.push(error); + } + // Admission stays occupied until the old closure clears it. If abort + // synchronously clears and replaces the slot, its captured stateCleared + // makes this completion idempotent instead of erasing the replacement. + try { + operation.complete(); + } catch (error) { + errors.push(error); + } + try { + clearReplyRunState({ + sessionKey: operation.key, + sessionId: operation.sessionId, + operation, + }); + } catch (error) { + errors.push(error); + } + } + if (errors.length > 0) { + throw new AggregateError(errors, "Failed to abort stale reply runs"); + } +} + +registerAgentEventLifecycleRotationHandler("reply-runs", evictPriorLifecycleReplyRuns); + +const replyRunRegistryTestApi = { + resetReplyRunRegistry(): void { + for (const [sessionKey, sessionId] of replyRunState.activeSessionIdsByKey) { + markReplyRunDiagnosticProgress({ + sessionKey, + sessionId, + reason: "reply_operation:registry_reset", + }); + } + replyRunState.activeRunsByKey.clear(); + replyRunState.activeSessionIdsByKey.clear(); + replyRunState.activeKeysBySessionId.clear(); + replyRunState.waitKeysBySessionId.clear(); + replyRunSettle.resetReplyRunSettleTimersForTesting(); + for (const waiters of replyRunState.waitersByKey.values()) { + for (const waiter of waiters) { + waiter.finish(false); + } + } + replyRunState.waitersByKey.clear(); + replyRunState.followupAdmissionBarriersByKey.clear(); + replyRunState.successorAdmissionBarriersByKey.clear(); + }, +}; + +if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.replyRunRegistryTestApi")] = + replyRunRegistryTestApi; +} diff --git a/src/auto-reply/reply/reply-run-registry.state.ts b/src/auto-reply/reply/reply-run-registry.state.ts new file mode 100644 index 000000000000..dedb4e681944 --- /dev/null +++ b/src/auto-reply/reply/reply-run-registry.state.ts @@ -0,0 +1,444 @@ +import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { createAbortError } from "../../infra/abort-signal.js"; +import { + getDiagnosticSessionActivitySnapshot, + markDiagnosticRunProgress, + resolveRunStaleThresholdMs, +} from "../../logging/diagnostic-run-activity.js"; +import { createDeferred } from "../../shared/deferred.js"; +import { resolveGlobalSingleton } from "../../shared/global-singleton.js"; +import { resolveTimerTimeoutMs } from "../../shared/number-coercion.js"; +import type { ReplyFollowupAdmissionBarrierTimeoutPolicy } from "./reply-dispatcher.types.js"; +import type { ReplyOperationStaleReason } from "./reply-run-finalization-lease.js"; +import { + REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, + type ReplyBackendHandle, + type ReplyOperation, + type ReplyOperationPhase, +} from "./reply-run-registry.contracts.js"; + +type ReplyRunWaiter = { + finish: (ended: boolean) => void; + timer?: NodeJS.Timeout; +}; + +export type ReplyRunAdmissionBarrier = { + settled: Promise; + sessionId: string; +}; + +type ReplyRunState = { + activeRunsByKey: Map; + activeSessionIdsByKey: Map; + activeKeysBySessionId: Map; + waitKeysBySessionId: Map; + waitersByKey: Map>; + followupAdmissionBarriersByKey: Map; + successorAdmissionBarriersByKey: Map; + evictOperationByOperation?: WeakMap void>; +}; + +const REPLY_RUN_STATE_KEY = Symbol.for("openclaw.replyRunRegistry"); + +export const replyRunState = resolveGlobalSingleton(REPLY_RUN_STATE_KEY, () => ({ + activeRunsByKey: new Map(), + activeSessionIdsByKey: new Map(), + activeKeysBySessionId: new Map(), + waitKeysBySessionId: new Map(), + waitersByKey: new Map>(), + followupAdmissionBarriersByKey: new Map(), + successorAdmissionBarriersByKey: new Map(), + evictOperationByOperation: new WeakMap void>(), +})); +replyRunState.followupAdmissionBarriersByKey ??= new Map(); +replyRunState.successorAdmissionBarriersByKey ??= new Map(); +export const evictReplyOperationByOperation = + replyRunState.evictOperationByOperation ?? + (replyRunState.evictOperationByOperation = new WeakMap void>()); + +export function createUserAbortError(): Error { + return createAbortError("Reply operation aborted by user"); +} + +export function registerWaitSessionId(sessionKey: string, sessionId: string): void { + replyRunState.waitKeysBySessionId.set(sessionId, sessionKey); +} + +function clearWaitSessionIds(sessionKey: string): void { + for (const [sessionId, mappedKey] of replyRunState.waitKeysBySessionId) { + if (mappedKey === sessionKey) { + replyRunState.waitKeysBySessionId.delete(sessionId); + } + } +} + +export function notifyReplyRunEnded(sessionKey: string): void { + const waiters = replyRunState.waitersByKey.get(sessionKey); + if (!waiters || waiters.size === 0) { + return; + } + replyRunState.waitersByKey.delete(sessionKey); + for (const waiter of waiters) { + waiter.finish(true); + } +} + +export function resolveReplyRunForCurrentSessionId(sessionId: string): ReplyOperation | undefined { + const normalizedSessionId = normalizeOptionalString(sessionId); + if (!normalizedSessionId) { + return undefined; + } + const sessionKey = replyRunState.activeKeysBySessionId.get(normalizedSessionId); + if (!sessionKey) { + return undefined; + } + return replyRunState.activeRunsByKey.get(sessionKey); +} + +export function resolveReplyRunWaitKey(sessionId: string): string | undefined { + const normalizedSessionId = normalizeOptionalString(sessionId); + if (!normalizedSessionId) { + return undefined; + } + return ( + replyRunState.activeKeysBySessionId.get(normalizedSessionId) ?? + replyRunState.waitKeysBySessionId.get(normalizedSessionId) + ); +} + +export function isReplyRunCompacting(operation: ReplyOperation): boolean { + if (operation.phase === "preflight_compacting" || operation.phase === "memory_flushing") { + return true; + } + if (operation.phase !== "running") { + return false; + } + const backend = getAttachedBackend(operation); + return backend?.isCompacting?.() ?? false; +} + +export function isReplyOperationPreBackendPhase(phase: ReplyOperationPhase): boolean { + return ( + phase === "queued" || + phase === "waiting_for_deferred_maintenance" || + phase === "waiting_for_global_lane" + ); +} + +export const attachedBackendByOperation = new WeakMap(); +export const abortFrozenOperations = new WeakSet(); +export const operationsByUpstreamAbortSignal = new WeakMap(); +export const retainStateUntilCompleteOperations = new WeakSet(); +const afterClearCallbacksByOperation = new WeakMap< + ReplyOperation, + Set<(sessionId: string) => void> +>(); +const successorBarrierStartsByOperation = new WeakMap void>>(); +type ReplyOperationSuccessorBarrierGroup = { + registrationKey: string; + barriers: Set; +}; +// Alias-keyed fences registered for one lane rotate together. Rekeyed command +// operations retain prior-lane identities so source successors do not adopt +// the target session. +const successorBarrierGroupsByOperation = new WeakMap< + ReplyOperation, + Set +>(); +export type ReplyOperationStaleExpiryOptions = { + afterClearBarrier?: PromiseLike; + followupAdmissionBarrierTimeout?: number | ReplyFollowupAdmissionBarrierTimeoutPolicy; +}; +export const expireReplyOperationByOperation = new WeakMap< + ReplyOperation, + (reason: ReplyOperationStaleReason, options?: ReplyOperationStaleExpiryOptions) => boolean +>(); + +export function getAttachedBackend(operation: ReplyOperation): ReplyBackendHandle | undefined { + return attachedBackendByOperation.get(operation); +} + +export function isReplyOperationAbortable(operation: ReplyOperation): boolean { + if (operation.result || abortFrozenOperations.has(operation)) { + return false; + } + const backend = getAttachedBackend(operation); + if (!backend?.isAbortable) { + return true; + } + try { + return backend.isAbortable(); + } catch { + return false; + } +} + +export function isReplyRunAbortableForSignal(signal: AbortSignal): boolean { + const operation = operationsByUpstreamAbortSignal.get(signal); + return operation ? isReplyOperationAbortable(operation) : true; +} + +/** Keep terminal state registered until the operation owner exits via complete(). */ +export function retainReplyOperationUntilComplete(operation: ReplyOperation): void { + retainStateUntilCompleteOperations.add(operation); +} + +/** Queue-first compatibility adapter for shipped Plugin SDK/embedded handles. */ + +export function runAfterReplyOperationClear( + operation: ReplyOperation, + afterClear: (sessionId: string) => void, +): void { + if (replyRunState.activeRunsByKey.get(operation.key) !== operation) { + const barrier = replyRunState.followupAdmissionBarriersByKey.get(operation.key); + if (barrier) { + void barrier.settled.then(() => afterClear(barrier.sessionId)); + return; + } + afterClear(operation.sessionId); + return; + } + const callbacks = + afterClearCallbacksByOperation.get(operation) ?? new Set<(sessionId: string) => void>(); + callbacks.add(afterClear); + afterClearCallbacksByOperation.set(operation, callbacks); +} + +function registerSuccessorAdmissionBarrier( + sessionKey: string, + sessionId: string, + barrier: Promise, +): ReplyRunAdmissionBarrier { + const barriersByKey = replyRunState.successorAdmissionBarriersByKey; + const previous = barriersByKey.get(sessionKey)?.settled; + const settled = previous ? Promise.all([previous, barrier]).then(() => undefined) : barrier; + const entry = { settled, sessionId }; + barriersByKey.set(sessionKey, entry); + void settled.then(() => { + if (barriersByKey.get(sessionKey) === entry) { + barriersByKey.delete(sessionKey); + } + }); + return entry; +} + +/** Fence successor admission until owner handoff started at slot clear settles. */ +export function registerReplyOperationSuccessorBarrier(params: { + operation: ReplyOperation; + sessionId: string; + sessionKeys: readonly string[]; + start: () => PromiseLike; +}): void { + const settlement = createDeferred(); + const barriers = new Set(); + for (const sessionKey of new Set(params.sessionKeys.map(normalizeOptionalString))) { + if (sessionKey) { + barriers.add( + registerSuccessorAdmissionBarrier(sessionKey, params.sessionId, settlement.promise), + ); + } + } + let started = false; + const start = () => { + if (started) { + return; + } + started = true; + try { + void Promise.resolve(params.start()).then( + () => settlement.resolve(undefined), + () => {}, + ); + } catch { + // A failed handoff leaves the fence closed. Visible callers stay + // abortably blocked; bounded queued callers cannot observe a partial release. + } + }; + if (replyRunState.activeRunsByKey.get(params.operation.key) !== params.operation) { + start(); + return; + } + const groups = + successorBarrierGroupsByOperation.get(params.operation) ?? + new Set(); + groups.add({ registrationKey: params.operation.key, barriers }); + successorBarrierGroupsByOperation.set(params.operation, groups); + const starts = successorBarrierStartsByOperation.get(params.operation) ?? new Set<() => void>(); + starts.add(start); + successorBarrierStartsByOperation.set(params.operation, starts); +} + +export function startReplyOperationSuccessorBarriers(operation: ReplyOperation): void { + const starts = successorBarrierStartsByOperation.get(operation); + // These maps are operation-owned lifecycle metadata, not identity indexes. + // Clear drops both before handoff starts so adoption cannot retain stale groups. + successorBarrierStartsByOperation.delete(operation); + successorBarrierGroupsByOperation.delete(operation); + if (!starts) { + return; + } + for (const start of starts) { + start(); + } +} + +export function updateSuccessorAdmissionSessionId( + operation: ReplyOperation, + sessionId: string, +): void { + for (const group of successorBarrierGroupsByOperation.get(operation) ?? []) { + if (group.registrationKey !== operation.key) { + continue; + } + for (const barrier of group.barriers) { + barrier.sessionId = sessionId; + } + } +} + +export function isReplyRunSuccessorAdmissionBlocked(sessionKey: string): boolean { + const normalizedSessionKey = normalizeOptionalString(sessionKey); + return Boolean( + normalizedSessionKey && + !replyRunState.activeRunsByKey.has(normalizedSessionKey) && + replyRunState.successorAdmissionBarriersByKey.has(normalizedSessionKey), + ); +} + +export function flushReplyOperationAfterClear(operation: ReplyOperation, sessionId: string): void { + const callbacks = afterClearCallbacksByOperation.get(operation); + if (!callbacks) { + return; + } + afterClearCallbacksByOperation.delete(operation); + for (const callback of callbacks) { + callback(sessionId); + } +} + +export function waitForReplyBarrierSettlement( + barrier: PromiseLike, + timeout: number | ReplyFollowupAdmissionBarrierTimeoutPolicy = REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, +): Promise { + // Owners may extend this for bounded retry envelopes; all barriers retain a failsafe. + return new Promise((resolve) => { + let settled = false; + let timer: ReturnType; + const finish = () => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + resolve(); + }; + const schedule = (delayMs: number, callback: () => void) => { + timer = setTimeout(callback, delayMs); + timer.unref?.(); + }; + if (typeof timeout === "number") { + schedule(resolveTimerTimeoutMs(timeout, REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS), finish); + } else { + const startedAt = Date.now(); + const maxTimeoutMs = resolveTimerTimeoutMs( + timeout.maxTimeoutMs, + REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, + ); + const checkOwnerActivity = () => { + const remainingMs = maxTimeoutMs - (Date.now() - startedAt); + if (remainingMs <= 0) { + finish(); + return; + } + let shouldExtend: boolean; + try { + shouldExtend = timeout.shouldExtend(); + } catch { + finish(); + return; + } + if (!shouldExtend) { + finish(); + return; + } + schedule(Math.min(REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, remainingMs), checkOwnerActivity); + }; + schedule(Math.min(REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, maxTimeoutMs), checkOwnerActivity); + } + void Promise.resolve(barrier).then(finish, finish); + }); +} + +export function registerFollowupAdmissionBarrier( + sessionKey: string, + sessionId: string, + barrier: PromiseLike, + timeout: number | ReplyFollowupAdmissionBarrierTimeoutPolicy = REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, +): ReplyRunAdmissionBarrier { + const barriersByKey = replyRunState.followupAdmissionBarriersByKey; + const previous = barriersByKey.get(sessionKey)?.settled; + const current = waitForReplyBarrierSettlement(barrier, timeout); + const settled = previous ? Promise.all([previous, current]).then(() => undefined) : current; + const entry = { settled, sessionId }; + barriersByKey.set(sessionKey, entry); + void settled.then(() => { + if (barriersByKey.get(sessionKey) === entry) { + barriersByKey.delete(sessionKey); + } + }); + return entry; +} + +export function updateFollowupAdmissionSessionId(sessionKey: string, sessionId: string): void { + const barrier = replyRunState.followupAdmissionBarriersByKey.get(sessionKey); + if (barrier) { + barrier.sessionId = sessionId; + } +} + +export function clearReplyRunState(params: { + sessionKey: string; + sessionId: string; + operation: ReplyOperation; +}): void { + if (replyRunState.activeRunsByKey.get(params.sessionKey) !== params.operation) { + if ( + replyRunState.activeKeysBySessionId.get(params.sessionId) === params.sessionKey && + replyRunState.activeSessionIdsByKey.get(params.sessionKey) !== params.sessionId + ) { + replyRunState.activeKeysBySessionId.delete(params.sessionId); + } + return; + } + replyRunState.activeRunsByKey.delete(params.sessionKey); + replyRunState.activeSessionIdsByKey.delete(params.sessionKey); + if (replyRunState.activeKeysBySessionId.get(params.sessionId) === params.sessionKey) { + replyRunState.activeKeysBySessionId.delete(params.sessionId); + } + clearWaitSessionIds(params.sessionKey); + notifyReplyRunEnded(params.sessionKey); +} + +export function markReplyRunDiagnosticProgress(params: { + sessionKey: string; + sessionId: string; + reason: string; +}): void { + markDiagnosticRunProgress({ + sessionId: params.sessionId, + sessionKey: params.sessionKey, + reason: params.reason, + }); +} + +export function isReplyRunEvidenceStale(operation: ReplyOperation): boolean { + const activity = getDiagnosticSessionActivitySnapshot({ + sessionId: operation.sessionId, + sessionKey: operation.key, + }); + return ( + !operation.result && + operation.phase !== "waiting_for_global_lane" && + Date.now() - operation.lastActivityAtMs > resolveRunStaleThresholdMs(activity) + ); +} diff --git a/src/auto-reply/reply/reply-run-registry.ts b/src/auto-reply/reply/reply-run-registry.ts index 3cce6be7407c..88502ce621e3 100644 --- a/src/auto-reply/reply/reply-run-registry.ts +++ b/src/auto-reply/reply/reply-run-registry.ts @@ -1,2012 +1,62 @@ -// Tracks active reply runs so stop, queue, and status commands can coordinate. -import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { - createAgentRunRestartAbortError, - isAgentRunRestartAbortReason, -} from "../../agents/run-termination.js"; -import { createAbortError } from "../../infra/abort-signal.js"; -import { - getAgentEventLifecycleGeneration, - isAgentEventLifecycleGenerationCurrent, - registerAgentEventLifecycleRotationHandler, -} from "../../infra/agent-events.js"; -import type { ImageContent } from "../../llm/types.js"; -import { - getDiagnosticSessionActivitySnapshot, - markDiagnosticRunProgress, - resolveRunStaleThresholdMs, -} from "../../logging/diagnostic-run-activity.js"; -import { diagnosticLogger as diag } from "../../logging/diagnostic-runtime.js"; -import type { MediaFact } from "../../media/media-facts.js"; -import type { PromptImageOrderEntry } from "../../media/prompt-image-order.js"; -import type { UserTurnTranscriptRecorder } from "../../sessions/user-turn-transcript.types.js"; -import { createDeferred } from "../../shared/deferred.js"; -import { resolveGlobalSingleton } from "../../shared/global-singleton.js"; -import { resolveTimerTimeoutMs } from "../../shared/number-coercion.js"; -import type { - SourceReplyDeliveryMode, - TaskSuggestionDeliveryMode, -} from "../get-reply-options.types.js"; -import type { ReplyFollowupAdmissionBarrierTimeoutPolicy } from "./reply-dispatcher.types.js"; -import * as replyRunSettle from "./reply-run-finalization-lease.js"; - -type ReplyRunKey = string; - -type ReplyBackendKind = "embedded" | "cli"; - -type ReplyBackendCancelReason = "user_abort" | "restart" | "superseded"; - -export type ReplyBackendQueueMessageOptions = { - steeringMode?: "all"; - /** True when this queue item came from the channel's current user turn. */ - isInboundUserMessage?: boolean; - debounceMs?: number; - /** Ordered current-turn images to inject with the steering text. */ - images?: ImageContent[]; - imageOrder?: PromptImageOrderEntry[]; - /** Ordered facts represented by attachment text in this steering prompt. */ - media?: MediaFact[]; - deliveryTimeoutMs?: number; - waitForTranscriptCommit?: boolean; - /** Stable source identity for exact queued-message commit/cancellation matching. */ - queueIdentity?: string; - abortSignal?: AbortSignal; - /** Releases arrival ordering once the runtime has actually accepted this queue item. */ - onQueueAccepted?: (accepted: boolean) => void; - sourceReplyDeliveryMode?: SourceReplyDeliveryMode; - taskSuggestionDeliveryMode?: TaskSuggestionDeliveryMode; - /** Prepared channel turn to merge only at transcript persistence. */ - userTurnTranscriptRecorder?: UserTurnTranscriptRecorder; -}; - -export type ReplyBackendQueueMessageResult = { - /** Acceptance was irreversible, but the harness could not prove transcript commitment. */ - transcriptCommit: "unconfirmed"; - errorMessage: string; -}; - -export type ReplyBackendMessageInjection = { - /** Runtime-owned admission state; independent from token streaming. */ - isAvailable(): boolean; - queueMessage( - text: string, - options?: ReplyBackendQueueMessageOptions, - ): Promise; -}; - -export type ReplyBackendHandle = { - readonly kind: ReplyBackendKind; - readonly runId?: string; - readonly sourceReplyDeliveryMode?: SourceReplyDeliveryMode; - readonly taskSuggestionDeliveryMode?: TaskSuggestionDeliveryMode; - /** True only when queueMessage preserves images supplied in its options. */ - readonly supportsQueueMessageImages?: boolean; - cancel(reason?: ReplyBackendCancelReason): void; - readonly messageInjection?: ReplyBackendMessageInjection; - /** @deprecated Compatibility for shipped embedded handles. Use messageInjection. */ - isStreaming?: () => boolean; - isStopped?: () => boolean; - isAbortable?: () => boolean; - /** @deprecated Compatibility for shipped embedded handles. Use messageInjection. */ - queueMessage?: ( - text: string, - options?: ReplyBackendQueueMessageOptions, - ) => Promise; - /** - * Compatibility-only hook so legacy "abort compacting runs" paths can still - * find embedded runs that are compacting during the main run phase. - */ - isCompacting?: () => boolean; -}; - -const replyMessageInjectionTargetOperation = Symbol("replyMessageInjectionTargetOperation"); -export type ReplyMessageInjectionTarget = { - readonly [replyMessageInjectionTargetOperation]: ReplyOperation; - /** Legacy targets stay leaf-bound even when their backend exposes a run id. */ - readonly identity: "leaf" | "run"; - readonly runId?: string; - readonly originatingLeafEntryId: string | null | undefined; -}; - -type ReplyMessageInjectionRejectionReason = - | "no_active_run" - | "not_running" - | "stale_run" - | "leaf_mismatch" - | "run_mismatch" - | "injection_unavailable" - | ReplyBackendQueueMessageMismatch - | "runtime_rejected"; - -export type ReplyMessageInjectionOutcome = - | { status: "accepted"; result?: ReplyBackendQueueMessageResult } - | { status: "rejected"; reason: ReplyMessageInjectionRejectionReason; errorMessage?: string }; - -export type ReplyMessageInjectionAttempt = { - /** Native run identity captured with the opaque operation target. */ - targetRunId: string | undefined; - /** Leaf-bound compatibility must reject before ACK instead of falling through. */ - rejectBeforeAck?: true; - /** Settles once the runtime accepts or rejects ownership of this exact message. */ - acceptance: Promise; - /** Settles after the backend confirms or rejects this exact injection. */ - outcome: Promise; -}; - -type ReplyBackendQueueMessageMismatch = - | "image_input_unsupported" - | "source_reply_delivery_mode_mismatch" - | "task_suggestion_delivery_mode_mismatch"; - -/** Prevents steering a turn into a run that cannot preserve its model-facing input. */ -export function resolveReplyBackendQueueMessageMismatch( - backend: Pick< - ReplyBackendHandle, - "sourceReplyDeliveryMode" | "supportsQueueMessageImages" | "taskSuggestionDeliveryMode" - >, - options?: ReplyBackendQueueMessageOptions, -): ReplyBackendQueueMessageMismatch | undefined { - if (options?.images?.length && backend.supportsQueueMessageImages !== true) { - return "image_input_unsupported"; - } - if ( - options?.sourceReplyDeliveryMode === "message_tool_only" && - backend.sourceReplyDeliveryMode !== "message_tool_only" - ) { - return "source_reply_delivery_mode_mismatch"; - } - // User turns carry this own property even when disabled; internal wakeups - // omit it so they inherit the active run's already-negotiated tool surface. - if ( - options !== undefined && - Object.hasOwn(options, "taskSuggestionDeliveryMode") && - options?.taskSuggestionDeliveryMode !== backend.taskSuggestionDeliveryMode - ) { - return "task_suggestion_delivery_mode_mismatch"; - } - return undefined; -} - -export type ReplyOperationPhase = - | "queued" - | "waiting_for_deferred_maintenance" - | "waiting_for_global_lane" - | "preflight_compacting" - | "memory_flushing" - | "running" - | "completed" - | "failed" - | "aborted"; - -type ReplyOperationFailureCode = - | "gateway_draining" - | "command_lane_cleared" - | "aborted_by_user" - | "session_corruption_reset" - | "run_stalled" - | "run_failed"; - -type ReplyOperationAbortCode = "aborted_by_user" | "aborted_for_restart"; - -type ReplyOperationResult = - | { kind: "completed" } - | { kind: "failed"; code: ReplyOperationFailureCode; cause?: unknown } - | { kind: "aborted"; code: ReplyOperationAbortCode }; - -export type ReplyOperation = { - readonly key: ReplyRunKey; - readonly sessionId: string; - /** Gateway lifecycle that admitted this process-local owner. */ - readonly lifecycleGeneration?: string; - readonly routeThreadId?: string | number; - /** Transcript branch leaf from which this operation was admitted. */ - readonly originatingLeafEntryId?: string | null; - readonly abortSignal: AbortSignal; - readonly resetTriggered: boolean; - /** - * True when this operation was admitted to recover a terminal session (a - * leftover failed/timeout/killed run). Concurrent visible turns reading the - * same terminal store snapshot must NOT force-clear such an operation: it is a - * sibling recovery already in flight, not the proven stale leftover. - */ - readonly terminalRecovery: boolean; - /** - * Sticky fact for audio accepted into this operation after its originating turn. - * Final delivery reads it because the original dispatch context cannot change. - */ - readonly acceptedSteeredInboundAudio: boolean; - readonly phase: ReplyOperationPhase; - readonly result: ReplyOperationResult | null; - /** Set when a stale-watchdog expiry forced this operation's run_stalled result. */ - readonly staleExpiryReason?: ReplyOperationStaleReason; - readonly startedAtMs: number; - readonly lastActivityAtMs: number; - /** True when this operation has owned the supplied session ID. */ - hasOwnedSessionId(sessionId: string): boolean; - recordActivity(): void; - setPhase( - next: - | "queued" - | "waiting_for_deferred_maintenance" - | "waiting_for_global_lane" - | "preflight_compacting" - | "memory_flushing" - | "running", - ): void; - /** Mark this operation as waiting on prior same-session maintenance. */ - markWaitingForDeferredMaintenance(): void; - /** Return a maintenance-waiting operation to queued if the run has not started. */ - markDeferredMaintenanceWaitEnded(): void; - /** Mark this operation as waiting for process-global run capacity. */ - markWaitingForGlobalLane(): void; - /** Return a global-lane-waiting operation to queued once capacity is granted. */ - markGlobalLaneWaitEnded(): void; - /** Mark this operation as an in-flight terminal-session recovery. */ - markTerminalRecovery(): void; - markAcceptedSteeredInboundAudio(): void; - updateSessionId(nextSessionId: string): void; - /** - * Move this queued operation to another session key's run slot. Native command - * turns admit under the slash SOURCE key; when the command continues into a full - * agent turn it must own the TARGET session's slot so concurrent target inbounds - * queue/steer instead of double-admitting. Throws ReplyRunAlreadyActiveError when - * the target slot is owned. - */ - updateSessionKey(nextSessionKey: string): void; - attachBackend(handle: ReplyBackendHandle): void; - detachBackend(handle: ReplyBackendHandle): void; - /** Reject later aborts after the backend has committed its terminal outcome. */ - freezeAbort(): void; - /** - * Keep a failed operation active until complete() releases the session lane. - * Dispatch uses this while a user-visible failure payload still needs delivery. - */ - retainFailureUntilComplete(): void; - /** Settles after the lifecycle owner's final delivery/persistence barrier. */ - readonly ownerSettlement?: Promise; - complete(): void; - /** - * Complete the operation, clear active-run state, then run follow-up work. - * Use when the follow-up can create another ReplyOperation for this session. - */ - completeThen(afterClear: () => void): void; - /** - * Clear active-run state immediately, but delay registered after-clear work - * until delivery or another external barrier settles. - */ - completeWithAfterClearBarrier( - barrier: PromiseLike, - timeout?: number | ReplyFollowupAdmissionBarrierTimeoutPolicy, - ): void; - fail(code: Exclude, cause?: unknown): void; - abortByUser(): boolean; - abortForRestart(): boolean; -}; - -type ReplyRunRegistry = { - begin(params: { - sessionKey: string; - sessionId: string; - resetTriggered: boolean; - routeThreadId?: string | number; - originatingLeafEntryId?: string | null; - upstreamAbortSignal?: AbortSignal; - }): ReplyOperation; - get(sessionKey: string): ReplyOperation | undefined; - isActive(sessionKey: string): boolean; - resolveMessageInjectionTarget(params: { - sessionKey: string; - originatingLeafEntryId: string | null | undefined; - expectedRunId?: string; - }): ReplyMessageInjectionTarget | undefined; - abort(sessionKey: string): boolean; - waitForIdle( - sessionKey: string, - timeoutMs?: number | null, - opts?: { signal?: AbortSignal }, - ): Promise; - resolveSessionId(sessionKey: string): string | undefined; -}; - -type ReplyRunWaiter = { - finish: (ended: boolean) => void; - timer?: NodeJS.Timeout; -}; - -type ReplyRunAdmissionBarrier = { - settled: Promise; - sessionId: string; -}; - -type ReplyRunState = { - activeRunsByKey: Map; - activeSessionIdsByKey: Map; - activeKeysBySessionId: Map; - waitKeysBySessionId: Map; - waitersByKey: Map>; - followupAdmissionBarriersByKey: Map; - successorAdmissionBarriersByKey: Map; - evictOperationByOperation?: WeakMap void>; -}; - -const REPLY_RUN_STATE_KEY = Symbol.for("openclaw.replyRunRegistry"); - -const replyRunState = resolveGlobalSingleton(REPLY_RUN_STATE_KEY, () => ({ - activeRunsByKey: new Map(), - activeSessionIdsByKey: new Map(), - activeKeysBySessionId: new Map(), - waitKeysBySessionId: new Map(), - waitersByKey: new Map>(), - followupAdmissionBarriersByKey: new Map(), - successorAdmissionBarriersByKey: new Map(), - evictOperationByOperation: new WeakMap void>(), -})); -replyRunState.followupAdmissionBarriersByKey ??= new Map(); -replyRunState.successorAdmissionBarriersByKey ??= new Map(); -const evictReplyOperationByOperation = - replyRunState.evictOperationByOperation ?? - (replyRunState.evictOperationByOperation = new WeakMap void>()); - -export const REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS = 15_000; -// Terminal results must release the lane even if the owner never resumes. -// Without this, abort/failure can leave the session wedged until process restart. -export const REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS = 60_000; - -type ReplyOperationStaleReason = replyRunSettle.ReplyOperationStaleReason; - -export class ReplyRunAlreadyActiveError extends Error { - constructor(sessionKey: string) { - super(`Reply run already active for ${sessionKey}`); - this.name = "ReplyRunAlreadyActiveError"; - } -} - -export class ReplyRunFollowupAdmissionBlockedError extends Error { - constructor(sessionKey: string) { - super(`Reply follow-up admission is blocked for ${sessionKey}`); - this.name = "ReplyRunFollowupAdmissionBlockedError"; - } -} - -export class ReplyRunSuccessorAdmissionBlockedError extends Error { - constructor(sessionKey: string) { - super(`Reply successor admission is blocked for ${sessionKey}`); - this.name = "ReplyRunSuccessorAdmissionBlockedError"; - } -} - -function createUserAbortError(): Error { - return createAbortError("Reply operation aborted by user"); -} - -function registerWaitSessionId(sessionKey: string, sessionId: string): void { - replyRunState.waitKeysBySessionId.set(sessionId, sessionKey); -} - -function clearWaitSessionIds(sessionKey: string): void { - for (const [sessionId, mappedKey] of replyRunState.waitKeysBySessionId) { - if (mappedKey === sessionKey) { - replyRunState.waitKeysBySessionId.delete(sessionId); - } - } -} - -function notifyReplyRunEnded(sessionKey: string): void { - const waiters = replyRunState.waitersByKey.get(sessionKey); - if (!waiters || waiters.size === 0) { - return; - } - replyRunState.waitersByKey.delete(sessionKey); - for (const waiter of waiters) { - waiter.finish(true); - } -} - -function resolveReplyRunForCurrentSessionId(sessionId: string): ReplyOperation | undefined { - const normalizedSessionId = normalizeOptionalString(sessionId); - if (!normalizedSessionId) { - return undefined; - } - const sessionKey = replyRunState.activeKeysBySessionId.get(normalizedSessionId); - if (!sessionKey) { - return undefined; - } - return replyRunState.activeRunsByKey.get(sessionKey); -} - -function resolveReplyRunWaitKey(sessionId: string): string | undefined { - const normalizedSessionId = normalizeOptionalString(sessionId); - if (!normalizedSessionId) { - return undefined; - } - return ( - replyRunState.activeKeysBySessionId.get(normalizedSessionId) ?? - replyRunState.waitKeysBySessionId.get(normalizedSessionId) - ); -} - -function isReplyRunCompacting(operation: ReplyOperation): boolean { - if (operation.phase === "preflight_compacting" || operation.phase === "memory_flushing") { - return true; - } - if (operation.phase !== "running") { - return false; - } - const backend = getAttachedBackend(operation); - return backend?.isCompacting?.() ?? false; -} - -function isReplyOperationPreBackendPhase(phase: ReplyOperationPhase): boolean { - return ( - phase === "queued" || - phase === "waiting_for_deferred_maintenance" || - phase === "waiting_for_global_lane" - ); -} - -const attachedBackendByOperation = new WeakMap(); -const abortFrozenOperations = new WeakSet(); -const operationsByUpstreamAbortSignal = new WeakMap(); -const retainStateUntilCompleteOperations = new WeakSet(); -const afterClearCallbacksByOperation = new WeakMap< +export { + REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, + REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS, + ReplyRunAlreadyActiveError, + ReplyRunFollowupAdmissionBlockedError, + ReplyRunSuccessorAdmissionBlockedError, +} from "./reply-run-registry.contracts.js"; +export type { + ReplyBackendHandle, + ReplyBackendMessageInjection, + ReplyBackendQueueMessageOptions, + ReplyBackendQueueMessageResult, + ReplyMessageInjectionAttempt, + ReplyMessageInjectionOutcome, + ReplyMessageInjectionTarget, ReplyOperation, - Set<(sessionId: string) => void> ->(); -const successorBarrierStartsByOperation = new WeakMap void>>(); -type ReplyOperationSuccessorBarrierGroup = { - registrationKey: string; - barriers: Set; -}; -// Alias-keyed fences registered for one lane rotate together. Rekeyed command -// operations retain prior-lane identities so source successors do not adopt -// the target session. -const successorBarrierGroupsByOperation = new WeakMap< - ReplyOperation, - Set ->(); -type ReplyOperationStaleExpiryOptions = { - afterClearBarrier?: PromiseLike; - followupAdmissionBarrierTimeout?: number | ReplyFollowupAdmissionBarrierTimeoutPolicy; -}; -const expireReplyOperationByOperation = new WeakMap< - ReplyOperation, - (reason: ReplyOperationStaleReason, options?: ReplyOperationStaleExpiryOptions) => boolean ->(); - -function getAttachedBackend(operation: ReplyOperation): ReplyBackendHandle | undefined { - return attachedBackendByOperation.get(operation); -} - -function isReplyOperationAbortable(operation: ReplyOperation): boolean { - if (operation.result || abortFrozenOperations.has(operation)) { - return false; - } - const backend = getAttachedBackend(operation); - if (!backend?.isAbortable) { - return true; - } - try { - return backend.isAbortable(); - } catch { - return false; - } -} - -export function isReplyRunAbortableForSignal(signal: AbortSignal): boolean { - const operation = operationsByUpstreamAbortSignal.get(signal); - return operation ? isReplyOperationAbortable(operation) : true; -} - -/** Keep terminal state registered until the operation owner exits via complete(). */ -export function retainReplyOperationUntilComplete(operation: ReplyOperation): void { - retainStateUntilCompleteOperations.add(operation); -} - -/** Queue-first compatibility adapter for shipped Plugin SDK/embedded handles. */ -function resolveReplyBackendMessageInjection( - backend: ReplyBackendHandle, -): ReplyBackendMessageInjection | undefined { - if (backend.messageInjection) { - return backend.messageInjection; - } - if (!backend.queueMessage) { - return undefined; - } - return { - isAvailable: () => { - if (backend.isStopped) { - return !backend.isStopped(); - } - // Legacy handles already expose the only capability that matters here: - // queueMessage. Let the runtime accept or reject instead of guessing from - // unrelated token-stream state. - return true; - }, - queueMessage: (text, options) => - options ? backend.queueMessage!(text, options) : backend.queueMessage!(text), - }; -} - -function resolveReplyMessageInjectionRejection(params: { - operation: ReplyOperation | undefined; - originatingLeafEntryId: string | null | undefined; - expectedRunId?: string; - options?: ReplyBackendQueueMessageOptions; -}): - | { reason: ReplyMessageInjectionRejectionReason; errorMessage?: string } - | { backend: ReplyBackendHandle; injection: ReplyBackendMessageInjection } { - const { operation } = params; - if (!operation || replyRunState.activeRunsByKey.get(operation.key) !== operation) { - return { reason: "no_active_run" }; - } - if (operation.result || operation.phase !== "running") { - return { reason: "not_running" }; - } - const expectedRunId = normalizeOptionalString(params.expectedRunId); - // Exact run identity supersedes the operation's immutable origin leaf. The - // same run advances its transcript leaf during ordinary tool/output progress. - if (!expectedRunId && operation.originatingLeafEntryId !== params.originatingLeafEntryId) { - return { reason: "leaf_mismatch" }; - } - if (isReplyRunEvidenceStale(operation)) { - return { reason: "stale_run" }; - } - const backend = getAttachedBackend(operation); - const injection = backend ? resolveReplyBackendMessageInjection(backend) : undefined; - if (!backend || !injection) { - return { reason: "injection_unavailable" }; - } - if (expectedRunId && normalizeOptionalString(backend.runId) !== expectedRunId) { - return { reason: "run_mismatch" }; - } - try { - if (!injection.isAvailable()) { - return { reason: "injection_unavailable" }; - } - } catch (error) { - return { reason: "injection_unavailable", errorMessage: String(error) }; - } - const mismatch = resolveReplyBackendQueueMessageMismatch(backend, params.options); - return mismatch ? { reason: mismatch } : { backend, injection }; -} - -/** Run work after an operation no longer owns its session lane. */ -export function runAfterReplyOperationClear( - operation: ReplyOperation, - afterClear: (sessionId: string) => void, -): void { - if (replyRunState.activeRunsByKey.get(operation.key) !== operation) { - const barrier = replyRunState.followupAdmissionBarriersByKey.get(operation.key); - if (barrier) { - void barrier.settled.then(() => afterClear(barrier.sessionId)); - return; - } - afterClear(operation.sessionId); - return; - } - const callbacks = - afterClearCallbacksByOperation.get(operation) ?? new Set<(sessionId: string) => void>(); - callbacks.add(afterClear); - afterClearCallbacksByOperation.set(operation, callbacks); -} - -function registerSuccessorAdmissionBarrier( - sessionKey: string, - sessionId: string, - barrier: Promise, -): ReplyRunAdmissionBarrier { - const barriersByKey = replyRunState.successorAdmissionBarriersByKey; - const previous = barriersByKey.get(sessionKey)?.settled; - const settled = previous ? Promise.all([previous, barrier]).then(() => undefined) : barrier; - const entry = { settled, sessionId }; - barriersByKey.set(sessionKey, entry); - void settled.then(() => { - if (barriersByKey.get(sessionKey) === entry) { - barriersByKey.delete(sessionKey); - } - }); - return entry; -} - -/** Fence successor admission until owner handoff started at slot clear settles. */ -export function registerReplyOperationSuccessorBarrier(params: { - operation: ReplyOperation; - sessionId: string; - sessionKeys: readonly string[]; - start: () => PromiseLike; -}): void { - const settlement = createDeferred(); - const barriers = new Set(); - for (const sessionKey of new Set(params.sessionKeys.map(normalizeOptionalString))) { - if (sessionKey) { - barriers.add( - registerSuccessorAdmissionBarrier(sessionKey, params.sessionId, settlement.promise), - ); - } - } - let started = false; - const start = () => { - if (started) { - return; - } - started = true; - try { - void Promise.resolve(params.start()).then( - () => settlement.resolve(undefined), - () => {}, - ); - } catch { - // A failed handoff leaves the fence closed. Visible callers stay - // abortably blocked; bounded queued callers cannot observe a partial release. - } - }; - if (replyRunState.activeRunsByKey.get(params.operation.key) !== params.operation) { - start(); - return; - } - const groups = - successorBarrierGroupsByOperation.get(params.operation) ?? - new Set(); - groups.add({ registrationKey: params.operation.key, barriers }); - successorBarrierGroupsByOperation.set(params.operation, groups); - const starts = successorBarrierStartsByOperation.get(params.operation) ?? new Set<() => void>(); - starts.add(start); - successorBarrierStartsByOperation.set(params.operation, starts); -} - -function startReplyOperationSuccessorBarriers(operation: ReplyOperation): void { - const starts = successorBarrierStartsByOperation.get(operation); - // These maps are operation-owned lifecycle metadata, not identity indexes. - // Clear drops both before handoff starts so adoption cannot retain stale groups. - successorBarrierStartsByOperation.delete(operation); - successorBarrierGroupsByOperation.delete(operation); - if (!starts) { - return; - } - for (const start of starts) { - start(); - } -} - -function updateSuccessorAdmissionSessionId(operation: ReplyOperation, sessionId: string): void { - for (const group of successorBarrierGroupsByOperation.get(operation) ?? []) { - if (group.registrationKey !== operation.key) { - continue; - } - for (const barrier of group.barriers) { - barrier.sessionId = sessionId; - } - } -} - -export function isReplyRunSuccessorAdmissionBlocked(sessionKey: string): boolean { - const normalizedSessionKey = normalizeOptionalString(sessionKey); - return Boolean( - normalizedSessionKey && - !replyRunState.activeRunsByKey.has(normalizedSessionKey) && - replyRunState.successorAdmissionBarriersByKey.has(normalizedSessionKey), - ); -} - -function flushReplyOperationAfterClear(operation: ReplyOperation, sessionId: string): void { - const callbacks = afterClearCallbacksByOperation.get(operation); - if (!callbacks) { - return; - } - afterClearCallbacksByOperation.delete(operation); - for (const callback of callbacks) { - callback(sessionId); - } -} - -export function waitForReplyBarrierSettlement( - barrier: PromiseLike, - timeout: number | ReplyFollowupAdmissionBarrierTimeoutPolicy = REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, -): Promise { - // Owners may extend this for bounded retry envelopes; all barriers retain a failsafe. - return new Promise((resolve) => { - let settled = false; - let timer: ReturnType; - const finish = () => { - if (settled) { - return; - } - settled = true; - clearTimeout(timer); - resolve(); - }; - const schedule = (delayMs: number, callback: () => void) => { - timer = setTimeout(callback, delayMs); - timer.unref?.(); - }; - if (typeof timeout === "number") { - schedule(resolveTimerTimeoutMs(timeout, REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS), finish); - } else { - const startedAt = Date.now(); - const maxTimeoutMs = resolveTimerTimeoutMs( - timeout.maxTimeoutMs, - REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, - ); - const checkOwnerActivity = () => { - const remainingMs = maxTimeoutMs - (Date.now() - startedAt); - if (remainingMs <= 0) { - finish(); - return; - } - let shouldExtend: boolean; - try { - shouldExtend = timeout.shouldExtend(); - } catch { - finish(); - return; - } - if (!shouldExtend) { - finish(); - return; - } - schedule(Math.min(REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, remainingMs), checkOwnerActivity); - }; - schedule(Math.min(REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, maxTimeoutMs), checkOwnerActivity); - } - void Promise.resolve(barrier).then(finish, finish); - }); -} - -function registerFollowupAdmissionBarrier( - sessionKey: string, - sessionId: string, - barrier: PromiseLike, - timeout: number | ReplyFollowupAdmissionBarrierTimeoutPolicy = REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, -): ReplyRunAdmissionBarrier { - const barriersByKey = replyRunState.followupAdmissionBarriersByKey; - const previous = barriersByKey.get(sessionKey)?.settled; - const current = waitForReplyBarrierSettlement(barrier, timeout); - const settled = previous ? Promise.all([previous, current]).then(() => undefined) : current; - const entry = { settled, sessionId }; - barriersByKey.set(sessionKey, entry); - void settled.then(() => { - if (barriersByKey.get(sessionKey) === entry) { - barriersByKey.delete(sessionKey); - } - }); - return entry; -} - -function updateFollowupAdmissionSessionId(sessionKey: string, sessionId: string): void { - const barrier = replyRunState.followupAdmissionBarriersByKey.get(sessionKey); - if (barrier) { - barrier.sessionId = sessionId; - } -} - -function clearReplyRunState(params: { - sessionKey: string; - sessionId: string; - operation: ReplyOperation; -}): void { - if (replyRunState.activeRunsByKey.get(params.sessionKey) !== params.operation) { - if ( - replyRunState.activeKeysBySessionId.get(params.sessionId) === params.sessionKey && - replyRunState.activeSessionIdsByKey.get(params.sessionKey) !== params.sessionId - ) { - replyRunState.activeKeysBySessionId.delete(params.sessionId); - } - return; - } - replyRunState.activeRunsByKey.delete(params.sessionKey); - replyRunState.activeSessionIdsByKey.delete(params.sessionKey); - if (replyRunState.activeKeysBySessionId.get(params.sessionId) === params.sessionKey) { - replyRunState.activeKeysBySessionId.delete(params.sessionId); - } - clearWaitSessionIds(params.sessionKey); - notifyReplyRunEnded(params.sessionKey); -} - -function markReplyRunDiagnosticProgress(params: { - sessionKey: string; - sessionId: string; - reason: string; -}): void { - markDiagnosticRunProgress({ - sessionId: params.sessionId, - sessionKey: params.sessionKey, - reason: params.reason, - }); -} - -export function createReplyOperation(params: { - sessionKey: string; - sessionId: string; - resetTriggered: boolean; - routeThreadId?: string | number; - originatingLeafEntryId?: string | null; - upstreamAbortSignal?: AbortSignal; - respectFollowupAdmissionBarrier?: boolean; -}): ReplyOperation { - const sessionKey = normalizeOptionalString(params.sessionKey); - const sessionId = normalizeOptionalString(params.sessionId); - if (!sessionKey) { - throw new Error("Reply operations require a canonical sessionKey"); - } - if (!sessionId) { - throw new Error("Reply operations require a sessionId"); - } - if ( - params.respectFollowupAdmissionBarrier && - replyRunState.followupAdmissionBarriersByKey.has(sessionKey) - ) { - throw new ReplyRunFollowupAdmissionBlockedError(sessionKey); - } - if (replyRunState.activeRunsByKey.has(sessionKey)) { - throw new ReplyRunAlreadyActiveError(sessionKey); - } - if (replyRunState.successorAdmissionBarriersByKey.has(sessionKey)) { - throw new ReplyRunSuccessorAdmissionBlockedError(sessionKey); - } - - const controller = new AbortController(); - // Mutable so updateSessionKey can move the run slot (command-turn continuation - // adoption); every closure below must read this, never params.sessionKey. - let currentSessionKey = sessionKey; - let currentSessionId = sessionId; - let phase: ReplyOperationPhase = "queued"; - let phaseBeforeGlobalLaneWait: "queued" | "running" | undefined; - let staleExpiryReason: ReplyOperationStaleReason | undefined; - let result: ReplyOperationResult | null = null; - let stateCleared = false; - let clearBarrierSettlement: Promise | undefined; - let pendingClearBarrier: ReplyRunAdmissionBarrier | undefined; - let retainFailureUntilComplete = false; - let terminalRecovery = false; - let acceptedSteeredInboundAudio = false; - const ownerSettlement = createDeferred(); - let ownerSettled = false; - const settleOwner = () => { - if (ownerSettled) { - return; - } - ownerSettled = true; - ownerSettlement.resolve(undefined); - }; - const startedAtMs = Date.now(); - const lifecycleGeneration = getAgentEventLifecycleGeneration(); - let lastActivityAtMs = startedAtMs; - const upstreamAbortSignal = params.upstreamAbortSignal; - let upstreamAbortHandler: (() => void) | undefined; - const detachUpstreamAbort = () => { - if (!upstreamAbortHandler) { - return; - } - upstreamAbortSignal?.removeEventListener("abort", upstreamAbortHandler); - upstreamAbortHandler = undefined; - }; - const ownedSessionIds = new Set([sessionId]); - const recordActivity = () => { - lastActivityAtMs = Date.now(); - }; - const setResult = (next: ReplyOperationResult) => { - result = next; - recordActivity(); - }; - - const clearState = ( - afterClearBarrier?: PromiseLike, - followupAdmissionBarrierTimeout?: number | ReplyFollowupAdmissionBarrierTimeoutPolicy, - ) => { - if (stateCleared) { - return; - } - stateCleared = true; - terminalSettleTimer.clear(); - finalizationLease.clear(); - expireReplyOperationByOperation.delete(operation); - evictReplyOperationByOperation.delete(operation); - detachUpstreamAbort(); - const registeredBarrier = afterClearBarrier - ? registerFollowupAdmissionBarrier( - currentSessionKey, - currentSessionId, - afterClearBarrier, - followupAdmissionBarrierTimeout, - ) - : pendingClearBarrier; - pendingClearBarrier = undefined; - updateFollowupAdmissionSessionId(currentSessionKey, currentSessionId); - // Recovery-owner handoff must begin before the old slot wakes a successor; - // otherwise that successor can snapshot durable state the handoff then mutates. - startReplyOperationSuccessorBarriers(operation); - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "reply_operation:ended", - }); - clearReplyRunState({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - operation, - }); - if (!registeredBarrier) { - flushReplyOperationAfterClear(operation, currentSessionId); - return; - } - void registeredBarrier.settled.then(() => - flushReplyOperationAfterClear(operation, registeredBarrier.sessionId), - ); - clearBarrierSettlement = registeredBarrier.settled; - }; - - const abortInternally = (reason?: unknown) => { - if (!controller.signal.aborted) { - controller.abort(reason); - } - }; - - const scheduleTerminalSettle = () => { - if (stateCleared) { - return; - } - terminalSettleTimer.scheduleOnce(REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS); - }; - - const abortWithReason = ( - reason: ReplyBackendCancelReason, - abortReason: unknown, - opts?: { abortedCode?: ReplyOperationAbortCode }, - ) => { - if (opts?.abortedCode && !result) { - setResult({ kind: "aborted", code: opts.abortedCode }); - detachUpstreamAbort(); - } - phase = "aborted"; - abortInternally(abortReason); - getAttachedBackend(operation)?.cancel(reason); - }; - - const operation: ReplyOperation = { - get key() { - return currentSessionKey; - }, - get sessionId() { - return currentSessionId; - }, - lifecycleGeneration, - get routeThreadId() { - return params.routeThreadId; - }, - get originatingLeafEntryId() { - return params.originatingLeafEntryId; - }, - get abortSignal() { - return controller.signal; - }, - get resetTriggered() { - return params.resetTriggered; - }, - get terminalRecovery() { - return terminalRecovery; - }, - get acceptedSteeredInboundAudio() { - return acceptedSteeredInboundAudio; - }, - get phase() { - return phase; - }, - get result() { - return result; - }, - get staleExpiryReason() { - return staleExpiryReason; - }, - get startedAtMs() { - return startedAtMs; - }, - get lastActivityAtMs() { - return lastActivityAtMs; - }, - hasOwnedSessionId(candidateSessionId) { - const normalizedSessionId = normalizeOptionalString(candidateSessionId); - return normalizedSessionId ? ownedSessionIds.has(normalizedSessionId) : false; - }, - recordActivity() { - finalizationLease.recordActivity(); - }, - setPhase(next) { - if (result) { - return; - } - recordActivity(); - phase = next; - }, - markWaitingForDeferredMaintenance() { - if (result || phase !== "queued") { - return; - } - phase = "waiting_for_deferred_maintenance"; - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "deferred_maintenance:waiting", - }); - }, - markDeferredMaintenanceWaitEnded() { - if (result || phase !== "waiting_for_deferred_maintenance") { - return; - } - phase = "queued"; - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "deferred_maintenance:wait_ended", - }); - }, - markWaitingForGlobalLane() { - if (result || (phase !== "queued" && phase !== "running")) { - return; - } - // Queued-on-lane is healthy waiting, not a wedged run. Removing this phase - // lets stale recovery silently drop replies while global capacity is busy. - phaseBeforeGlobalLaneWait = phase; - phase = "waiting_for_global_lane"; - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "global_lane:waiting", - }); - }, - markGlobalLaneWaitEnded() { - if (result || phase !== "waiting_for_global_lane") { - return; - } - phase = phaseBeforeGlobalLaneWait ?? "queued"; - phaseBeforeGlobalLaneWait = undefined; - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "global_lane:wait_ended", - }); - }, - markTerminalRecovery() { - terminalRecovery = true; - }, - markAcceptedSteeredInboundAudio() { - acceptedSteeredInboundAudio = true; - }, - updateSessionId(nextSessionId) { - if (result) { - return; - } - const normalizedNextSessionId = normalizeOptionalString(nextSessionId); - if (!normalizedNextSessionId || normalizedNextSessionId === currentSessionId) { - return; - } - recordActivity(); - if ( - replyRunState.activeKeysBySessionId.has(normalizedNextSessionId) && - replyRunState.activeKeysBySessionId.get(normalizedNextSessionId) !== currentSessionKey - ) { - throw new Error( - `Cannot rebind reply operation ${currentSessionKey} to active session ${normalizedNextSessionId}`, - ); - } - replyRunState.activeKeysBySessionId.delete(currentSessionId); - registerWaitSessionId(currentSessionKey, currentSessionId); - currentSessionId = normalizedNextSessionId; - ownedSessionIds.add(currentSessionId); - updateFollowupAdmissionSessionId(currentSessionKey, currentSessionId); - updateSuccessorAdmissionSessionId(operation, currentSessionId); - replyRunState.activeSessionIdsByKey.set(currentSessionKey, currentSessionId); - replyRunState.activeKeysBySessionId.set(currentSessionId, currentSessionKey); - registerWaitSessionId(currentSessionKey, currentSessionId); - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "reply_operation:session_updated", - }); - }, - updateSessionKey(nextSessionKey) { - const normalizedNextKey = normalizeOptionalString(nextSessionKey); - if (!normalizedNextKey) { - throw new Error("Reply operations require a canonical sessionKey"); - } - if (normalizedNextKey === currentSessionKey) { - return; - } - // Only a queued reservation may move slots: once the run started (or the - // operation settled), abort/steer/wait paths already resolved this key. - if (result || stateCleared || phase !== "queued") { - throw new Error(`Cannot rekey reply operation ${currentSessionKey} in phase ${phase}`); - } - if (replyRunState.activeRunsByKey.has(normalizedNextKey)) { - throw new ReplyRunAlreadyActiveError(normalizedNextKey); - } - if (replyRunState.successorAdmissionBarriersByKey.has(normalizedNextKey)) { - throw new ReplyRunSuccessorAdmissionBlockedError(normalizedNextKey); - } - recordActivity(); - const previousKey = currentSessionKey; - replyRunState.activeRunsByKey.delete(previousKey); - replyRunState.activeSessionIdsByKey.delete(previousKey); - currentSessionKey = normalizedNextKey; - replyRunState.activeRunsByKey.set(currentSessionKey, operation); - replyRunState.activeSessionIdsByKey.set(currentSessionKey, currentSessionId); - replyRunState.activeKeysBySessionId.set(currentSessionId, currentSessionKey); - // Wait/abort lookups resolve keys via owned session IDs; move them so - // waitForReplyRunEndBySessionId keeps finding this operation. - for (const ownedSessionId of ownedSessionIds) { - if (replyRunState.waitKeysBySessionId.get(ownedSessionId) === previousKey) { - replyRunState.waitKeysBySessionId.set(ownedSessionId, currentSessionKey); - } - } - // The previous key's slot is idle now; wake turns waiting on it. - notifyReplyRunEnded(previousKey); - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "reply_operation:session_key_adopted", - }); - }, - attachBackend(handle) { - if (result) { - handle.cancel( - result.kind === "aborted" - ? result.code === "aborted_for_restart" - ? "restart" - : "user_abort" - : "superseded", - ); - return; - } - recordActivity(); - attachedBackendByOperation.set(operation, handle); - if (controller.signal.aborted) { - handle.cancel("superseded"); - } - }, - detachBackend(handle) { - if (getAttachedBackend(operation) === handle) { - attachedBackendByOperation.delete(operation); - } - }, - freezeAbort() { - abortFrozenOperations.add(operation); - detachUpstreamAbort(); - finalizationLease.begin(); - }, - retainFailureUntilComplete() { - retainFailureUntilComplete = true; - }, - ownerSettlement: ownerSettlement.promise, - complete() { - if (!result) { - setResult({ kind: "completed" }); - phase = "completed"; - } - clearState(); - settleOwner(); - }, - completeThen(afterClear) { - runAfterReplyOperationClear(operation, afterClear); - operation.complete(); - }, - completeWithAfterClearBarrier(barrier, timeoutMs) { - if (!result) { - setResult({ kind: "completed" }); - phase = "completed"; - } - const wasAlreadyCleared = stateCleared; - const ownerCompletionSettlement = pendingClearBarrier - ? waitForReplyBarrierSettlement(barrier, timeoutMs) - : undefined; - clearState(barrier, timeoutMs); - // This barrier owns dispatch delivery and terminal persistence. Stale - // expiry may have already cleared the slot, but recovery must still wait - // for that old owner's durable work before admitting a queued turn. - const completionSettlement = wasAlreadyCleared - ? waitForReplyBarrierSettlement(barrier, timeoutMs) - : (ownerCompletionSettlement ?? clearBarrierSettlement); - if (completionSettlement) { - void completionSettlement.then(settleOwner); - } else { - settleOwner(); - } - }, - fail(code, cause) { - abortFrozenOperations.add(operation); - detachUpstreamAbort(); - finalizationLease.clear(); - if (!result) { - setResult({ kind: "failed", code, cause }); - phase = "failed"; - } - if (!retainFailureUntilComplete && !retainStateUntilCompleteOperations.has(operation)) { - clearState(); - } else { - scheduleTerminalSettle(); - } - }, - abortByUser() { - if (!isReplyOperationAbortable(operation)) { - return false; - } - const phaseBeforeAbort = phase; - abortWithReason("user_abort", createUserAbortError(), { - abortedCode: "aborted_by_user", - }); - if ( - isReplyOperationPreBackendPhase(phaseBeforeAbort) && - !retainStateUntilCompleteOperations.has(operation) - ) { - clearState(); - } else { - scheduleTerminalSettle(); - } - return true; - }, - abortForRestart() { - if (!isReplyOperationAbortable(operation)) { - return false; - } - const phaseBeforeAbort = phase; - abortWithReason("restart", createAgentRunRestartAbortError(), { - abortedCode: "aborted_for_restart", - }); - if ( - isReplyOperationPreBackendPhase(phaseBeforeAbort) && - !retainStateUntilCompleteOperations.has(operation) - ) { - clearState(); - } else { - scheduleTerminalSettle(); - } - return true; - }, - }; - - expireReplyOperationByOperation.set(operation, (reason, options) => { - if (replyRunState.activeRunsByKey.get(currentSessionKey) !== operation) { - return false; - } - // Set the terminal result BEFORE cancelling the backend: cancel can - // synchronously re-enter abortByUser() from the run loop's abort handler, - // which would stamp aborted_by_user and misattribute a watchdog expiry. - if (!result) { - abortFrozenOperations.add(operation); - detachUpstreamAbort(); - // The reason distinguishes pre-run drops (user got nothing; feedback owed) - // from post-output stalls (finalization/terminal cleanup; feedback is noise). - staleExpiryReason = reason; - setResult({ kind: "failed", code: "run_stalled" }); - phase = "failed"; - } - const logStaleTakeoverRelease = () => { - diag.warn( - `reply run stale takeover: forced release sessionKey=${currentSessionKey} reason=${reason} phase=${phase} result=${replyRunSettle.formatReplyOperationResult( - result, - )} ageMs=${Date.now() - lastActivityAtMs} ranForMs=${Date.now() - startedAtMs}`, - ); - }; - if (options?.afterClearBarrier) { - // Prepare the recovery fence before cancellation, but retain exact lane - // ownership until cancel returns or the backend re-enters completion. - pendingClearBarrier = registerFollowupAdmissionBarrier( - currentSessionKey, - currentSessionId, - options.afterClearBarrier, - options.followupAdmissionBarrierTimeout, - ); - } - const backend = getAttachedBackend(operation); - let cancelFailed = false; - try { - backend?.cancel("superseded"); - } catch (error) { - cancelFailed = true; - diag.warn( - `reply run stale takeover cancel failed: sessionKey=${currentSessionKey} reason=${reason} owner=${stateCleared ? "completed" : "retained"} error=${String(error)}`, - ); - } - abortInternally(createAbortError("Reply operation expired as stale")); - if (stateCleared) { - logStaleTakeoverRelease(); - return true; - } - // cancel() only requests shutdown. A missing backend can also be a live - // pre-attachment owner, so only complete() may release the exact lane token. - if (!cancelFailed) { - diag.warn( - `reply run stale takeover retained: sessionKey=${currentSessionKey} reason=${reason} owner=awaiting_terminal_completion backend=${backend ? "attached" : "pending"}`, - ); - } - scheduleTerminalSettle(); - return false; - }); - const finalizationLease = replyRunSettle.createReplyRunFinalizationLease({ - owner: operation, - canExpire: () => - !stateCleared && - !result && - replyRunState.activeRunsByKey.get(currentSessionKey) === operation, - onActivity: recordActivity, - onFinalizationProgress: () => - markReplyRunDiagnosticProgress({ - sessionKey: currentSessionKey, - sessionId: currentSessionId, - reason: "reply_operation:finalizing_progress", - }), - onExpire: () => { - diag.warn( - `reply run finalization settle: forced release sessionKey=${currentSessionKey} phase=${phase} result=${replyRunSettle.formatReplyOperationResult( - result, - )} ageMs=${Date.now() - lastActivityAtMs} ranForMs=${Date.now() - startedAtMs}`, - ); - const expired = expireReplyOperationByOperation.get(operation)?.("finalization_stalled"); - if (expired === false && replyRunState.activeRunsByKey.get(currentSessionKey) === operation) { - // This lease is the finalization owner's bounded shutdown deadline. - // Do not grant a second terminal-settle lifetime after it expires. - forceClearReplyOperation(operation); - } - }, - }); - const terminalSettleTimer = replyRunSettle.createReplyRunSettleTimer({ - canExpire: () => replyRunState.activeRunsByKey.get(currentSessionKey) === operation, - onExpire: () => { - // Retained terminal results get one delivery grace window, not a second lifetime. - diag.warn( - `reply run terminal settle: forced release sessionKey=${currentSessionKey} phase=${phase} result=${replyRunSettle.formatReplyOperationResult( - result, - )} ageMs=${Date.now() - lastActivityAtMs} ranForMs=${Date.now() - startedAtMs}`, - ); - clearState(); - }, - }); - - evictReplyOperationByOperation.set(operation, () => { - if (stateCleared) { - return; - } - if (!result) { - setResult({ kind: "aborted", code: "aborted_for_restart" }); - phase = "aborted"; - } - abortInternally(createAgentRunRestartAbortError()); - let cancelError: unknown; - let cancelFailed = false; - try { - getAttachedBackend(operation)?.cancel("restart"); - } catch (error) { - cancelFailed = true; - cancelError = error; - diag.warn( - `reply run lifecycle eviction cancel failed: sessionKey=${currentSessionKey} error=${String(error)}`, - ); - } finally { - clearState(); - } - if (cancelFailed) { - throw cancelError; - } - }); - - replyRunState.activeRunsByKey.set(sessionKey, operation); - replyRunState.activeSessionIdsByKey.set(sessionKey, currentSessionId); - replyRunState.activeKeysBySessionId.set(currentSessionId, sessionKey); - registerWaitSessionId(sessionKey, currentSessionId); - markReplyRunDiagnosticProgress({ - sessionKey, - sessionId: currentSessionId, - reason: "reply_operation:queued", - }); - if (upstreamAbortSignal) { - operationsByUpstreamAbortSignal.set(upstreamAbortSignal, operation); - const abortFromUpstream = () => { - if (result) { - return; - } - const restart = isAgentRunRestartAbortReason(upstreamAbortSignal.reason); - const phaseBeforeAbort = phase; - abortWithReason(restart ? "restart" : "user_abort", upstreamAbortSignal.reason, { - abortedCode: restart ? "aborted_for_restart" : "aborted_by_user", - }); - if ( - isReplyOperationPreBackendPhase(phaseBeforeAbort) && - !retainStateUntilCompleteOperations.has(operation) - ) { - clearState(); - } else { - scheduleTerminalSettle(); - } - }; - if (upstreamAbortSignal.aborted) { - abortFromUpstream(); - } else { - upstreamAbortHandler = abortFromUpstream; - upstreamAbortSignal.addEventListener("abort", upstreamAbortHandler, { once: true }); - } - } - - return operation; -} - -export function expireStaleReplyOperation( - operation: ReplyOperation, - reason: ReplyOperationStaleReason, - options?: ReplyOperationStaleExpiryOptions, -): boolean { - return expireReplyOperationByOperation.get(operation)?.(reason, options) ?? false; -} - -/** Wait for the old lifecycle owner's terminal work after stale expiry clears its slot. */ -export async function waitForReplyOperationOwnerSettlement( - operation: ReplyOperation, - timeoutMs: number, -): Promise { - const settlement = operation.ownerSettlement; - if (!settlement) { - return true; - } - const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 100, 100); - let timer: NodeJS.Timeout | undefined; - const settled = await Promise.race([ - settlement.then(() => true), - new Promise((resolve) => { - timer = setTimeout(() => resolve(false), resolvedTimeoutMs); - timer.unref?.(); - }), - ]); - if (timer) { - clearTimeout(timer); - } - return settled; -} - -export function expireStaleReplyRunBySessionId( - sessionId: string, - reason: ReplyOperationStaleReason, - options?: Parameters[2], -): boolean { - const operation = resolveReplyRunForCurrentSessionId(sessionId); - return operation ? expireStaleReplyOperation(operation, reason, options) : false; -} - -// lastActivityAtMs is refreshed by agent events only; timers and user-message -// injection never refresh it, so quiet runs age toward reclaim. -export function isReplyRunEvidenceStale(operation: ReplyOperation): boolean { - const activity = getDiagnosticSessionActivitySnapshot({ - sessionId: operation.sessionId, - sessionKey: operation.key, - }); - return ( - !operation.result && - operation.phase !== "waiting_for_global_lane" && - Date.now() - operation.lastActivityAtMs > resolveRunStaleThresholdMs(activity) - ); -} - -export function markReplyOperationGlobalLaneWaitProgress(operation: ReplyOperation): void { - if (operation.result || operation.phase !== "waiting_for_global_lane") { - return; - } - markReplyRunDiagnosticProgress({ - sessionKey: operation.key, - sessionId: operation.sessionId, - reason: "global_lane:waiting", - }); -} - -export function isReplyRunEvidenceStaleBySessionId(sessionId: string): boolean { - const operation = resolveReplyRunForCurrentSessionId(sessionId); - return operation ? isReplyRunEvidenceStale(operation) : false; -} - -export const replyRunRegistry: ReplyRunRegistry = { - begin(params) { - return createReplyOperation(params); - }, - get(sessionKey) { - const normalizedSessionKey = normalizeOptionalString(sessionKey); - if (!normalizedSessionKey) { - return undefined; - } - return replyRunState.activeRunsByKey.get(normalizedSessionKey); - }, - isActive(sessionKey) { - const normalizedSessionKey = normalizeOptionalString(sessionKey); - if (!normalizedSessionKey) { - return false; - } - return replyRunState.activeRunsByKey.has(normalizedSessionKey); - }, - resolveMessageInjectionTarget({ sessionKey, originatingLeafEntryId, expectedRunId }) { - const operation = this.get(sessionKey); - const resolved = resolveReplyMessageInjectionRejection({ - operation, - originatingLeafEntryId, - expectedRunId, - }); - if (!("injection" in resolved)) { - return undefined; - } - const target: ReplyMessageInjectionTarget = { - [replyMessageInjectionTargetOperation]: operation!, - identity: normalizeOptionalString(expectedRunId) ? "run" : "leaf", - ...(resolved.backend.runId ? { runId: resolved.backend.runId } : {}), - originatingLeafEntryId, - }; - return target; - }, - abort(sessionKey) { - const operation = this.get(sessionKey); - if (!operation) { - return false; - } - return operation.abortByUser(); - }, - waitForIdle(sessionKey, timeoutMs, opts) { - const normalizedSessionKey = normalizeOptionalString(sessionKey); - if (!normalizedSessionKey || !replyRunState.activeRunsByKey.has(normalizedSessionKey)) { - return Promise.resolve(true); - } - if (opts?.signal?.aborted) { - return Promise.resolve(false); - } - return new Promise((resolve) => { - const waiters = replyRunState.waitersByKey.get(normalizedSessionKey) ?? new Set(); - let abortHandler: (() => void) | undefined; - let settled = false; - const waiter: ReplyRunWaiter = { - finish: (ended) => { - if (settled) { - return; - } - settled = true; - waiters.delete(waiter); - if (waiters.size === 0) { - replyRunState.waitersByKey.delete(normalizedSessionKey); - } - if (waiter.timer) { - clearTimeout(waiter.timer); - } - if (abortHandler) { - opts?.signal?.removeEventListener("abort", abortHandler); - } - resolve(ended); - }, - }; - if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) { - waiter.timer = setTimeout( - () => waiter.finish(false), - resolveTimerTimeoutMs(timeoutMs, 100, 100), - ); - } - if (opts?.signal) { - abortHandler = () => waiter.finish(false); - opts.signal.addEventListener("abort", abortHandler, { once: true }); - } - waiters.add(waiter); - replyRunState.waitersByKey.set(normalizedSessionKey, waiters); - if (!replyRunState.activeRunsByKey.has(normalizedSessionKey)) { - waiter.finish(true); - } - }); - }, - resolveSessionId(sessionKey) { - const normalizedSessionKey = normalizeOptionalString(sessionKey); - if (!normalizedSessionKey) { - return undefined; - } - return replyRunState.activeSessionIdsByKey.get(normalizedSessionKey); - }, -}; - -export function resolveActiveReplyRunSessionId(sessionKey: string): string | undefined { - return replyRunRegistry.resolveSessionId(sessionKey); -} - -/** Cancels the current reply backend only when its native run identity matches exactly. */ -export function supersedeReplyRunByRunId(runId: string, beforeCancel: () => void): boolean { - const expectedRunId = normalizeOptionalString(runId); - if (!expectedRunId) { - return false; - } - for (const operation of replyRunState.activeRunsByKey.values()) { - const backend = getAttachedBackend(operation); - if (normalizeOptionalString(backend?.runId) !== expectedRunId) { - continue; - } - beforeCancel(); - backend?.cancel("superseded"); - return true; - } - return false; -} - -export function resolveActiveReplyRunThreadId(sessionKey: string): string | number | undefined { - return replyRunRegistry.get(sessionKey)?.routeThreadId; -} - -export function isReplyRunActiveForSessionId(sessionId: string): boolean { - return resolveReplyRunForCurrentSessionId(sessionId) !== undefined; -} - -export function resolveReplyRunPhaseForSessionId( - sessionId: string, -): ReplyOperationPhase | undefined { - return resolveReplyRunForCurrentSessionId(sessionId)?.phase; -} - -export function isReplyRunAbortableForCompaction(sessionId: string): boolean { - const operation = resolveReplyRunForCurrentSessionId(sessionId); - // Manual compaction uses this as a coordination gate: a finalizing run still - // needs to drain even when its frozen outcome rejects the abort itself. - return Boolean(operation && !isReplyOperationPreBackendPhase(operation.phase)); -} - -export function beginReplyMessageInjectionTarget( - target: ReplyMessageInjectionTarget, - text: string, - options?: ReplyBackendQueueMessageOptions, -): ReplyMessageInjectionAttempt { - const resolved = resolveReplyMessageInjectionRejection({ - operation: target[replyMessageInjectionTargetOperation], - originatingLeafEntryId: target.originatingLeafEntryId, - expectedRunId: target.identity === "run" ? target.runId : undefined, - options, - }); - if (!("injection" in resolved)) { - const immediateRejection = { status: "rejected" as const, ...resolved }; - return { - targetRunId: target.runId, - ...(target.identity === "leaf" ? { rejectBeforeAck: true as const } : {}), - acceptance: Promise.resolve(false), - outcome: Promise.resolve(immediateRejection), - }; - } - // Injection is user input, not run evidence: stamping activity here would let - // sub-10-minute user messages re-arm a wedged run's staleness window forever. - // Invoke before the first await. The capability owns the final synchronous - // admission check, matching Codex's active-turn lock boundary. - const acceptance = createDeferred(); - let acceptanceSettled = false; - const settleAcceptance = (accepted: boolean) => { - if (acceptanceSettled) { - return; - } - acceptanceSettled = true; - acceptance.resolve(accepted); - }; - const callerOnQueueAccepted = options?.onQueueAccepted; - const queueOptions: ReplyBackendQueueMessageOptions = { - ...options, - onQueueAccepted: (accepted) => { - settleAcceptance(accepted); - callerOnQueueAccepted?.(accepted); - }, - }; - let queued: Promise; - try { - queued = resolved.injection.queueMessage(text, queueOptions); - } catch (error) { - settleAcceptance(false); - const immediateRejection = { - status: "rejected" as const, - reason: "runtime_rejected" as const, - errorMessage: String(error), - }; - return { - targetRunId: target.runId, - acceptance: acceptance.promise, - outcome: Promise.resolve(immediateRejection), - }; - } - const outcome = queued.then( - (result): ReplyMessageInjectionOutcome => { - settleAcceptance(true); - return result ? { status: "accepted", result } : { status: "accepted" }; - }, - (error: unknown): ReplyMessageInjectionOutcome => { - settleAcceptance(false); - return { - status: "rejected", - reason: "runtime_rejected", - errorMessage: String(error), - }; - }, - ); - return { - targetRunId: target.runId, - acceptance: acceptance.promise, - outcome, - }; -} - -/** Abort only the operation captured by this target; never a same-key successor. */ -export function abortReplyMessageInjectionTarget(target: ReplyMessageInjectionTarget): boolean { - return target[replyMessageInjectionTargetOperation].abortByUser(); -} - -/** Record accepted input on the exact operation without rediscovering its session slot. */ -export function recordAcceptedReplyMessageInjectionTarget( - target: ReplyMessageInjectionTarget, - options?: { inboundAudio?: boolean }, -): void { - const operation = target[replyMessageInjectionTargetOperation]; - operation.recordActivity(); - if (options?.inboundAudio === true) { - operation.markAcceptedSteeredInboundAudio(); - } -} - -export function abortReplyRunBySessionId(sessionId: string): boolean { - const operation = resolveReplyRunForCurrentSessionId(sessionId); - if (!operation) { - return false; - } - return operation.abortByUser(); -} - -export function resolveActiveReplyOperationForSessionId( - sessionId: string, -): ReplyOperation | undefined { - return resolveReplyRunForCurrentSessionId(sessionId); -} - -export function forceClearReplyOperation(operation: ReplyOperation, cause?: unknown): boolean { - if (replyRunState.activeRunsByKey.get(operation.key) !== operation) { - return false; - } - operation.fail("run_failed", cause); - operation.complete(); - return true; -} - -export function forceClearReplyRunBySessionId(sessionId: string, cause?: unknown): boolean { - const operation = resolveReplyRunForCurrentSessionId(sessionId); - return operation ? forceClearReplyOperation(operation, cause) : false; -} - -export function clearReplyRunForResetBySessionId(sessionId: string): void { - const operation = resolveReplyRunForCurrentSessionId(sessionId); - if (!operation || isReplyOperationPreBackendPhase(operation.phase)) { - return; - } - operation.abortForRestart(); - // Backend cancellation may synchronously retire this operation and admit a - // replacement. Only clear the exact archived operation resolved above. - if (replyRunState.activeRunsByKey.get(operation.key) === operation) { - operation.complete(); - } -} - -export function waitForReplyRunEndBySessionId( - sessionId: string, - timeoutMs?: number | null, -): Promise { - const waitKey = resolveReplyRunWaitKey(sessionId); - if (!waitKey) { - return Promise.resolve(true); - } - return replyRunRegistry.waitForIdle(waitKey, timeoutMs); -} - -async function waitForReplyRunAdmissionBarrier(params: { - barriersByKey: Map; - minimumTimeoutMs: number; - sessionKey: string; - signal?: AbortSignal; - timeoutMs?: number | null; -}): Promise<{ settled: boolean; sessionId?: string }> { - const deadline = - typeof params.timeoutMs === "number" - ? Date.now() + - resolveTimerTimeoutMs(params.timeoutMs, params.minimumTimeoutMs, params.minimumTimeoutMs) - : undefined; - let sessionId: string | undefined; - while (true) { - if (params.signal?.aborted) { - return { settled: false }; - } - const barrier = params.barriersByKey.get(params.sessionKey); - if (!barrier) { - return { settled: true, sessionId }; - } - const remainingMs = deadline === undefined ? undefined : deadline - Date.now(); - if (remainingMs !== undefined && remainingMs <= 0) { - return { settled: false }; - } - let timer: NodeJS.Timeout | undefined; - let abortHandler: (() => void) | undefined; - const outcome = await Promise.race([ - barrier.settled.then(() => true), - ...(remainingMs !== undefined - ? [ - new Promise((resolve) => { - timer = setTimeout(() => resolve(false), Math.max(1, remainingMs)); - timer.unref?.(); - }), - ] - : []), - ...(params.signal - ? [ - new Promise((resolve) => { - abortHandler = () => resolve(false); - params.signal?.addEventListener("abort", abortHandler, { once: true }); - if (params.signal?.aborted) { - abortHandler(); - } - }), - ] - : []), - ]); - if (timer) { - clearTimeout(timer); - } - if (abortHandler) { - params.signal?.removeEventListener("abort", abortHandler); - } - if (!outcome) { - return { settled: false }; - } - sessionId = barrier.sessionId; - } -} - -export async function waitForReplyRunFollowupAdmission( - sessionKey: string, - timeoutMs: number, - opts?: { signal?: AbortSignal }, -): Promise<{ settled: boolean; sessionId?: string }> { - const normalizedSessionKey = normalizeOptionalString(sessionKey); - return normalizedSessionKey - ? await waitForReplyRunAdmissionBarrier({ - barriersByKey: replyRunState.followupAdmissionBarriersByKey, - minimumTimeoutMs: 100, - sessionKey: normalizedSessionKey, - signal: opts?.signal, - timeoutMs, - }) - : { settled: true }; -} - -export async function waitForReplyRunSuccessorAdmission( - sessionKey: string, - timeoutMs?: number | null, - opts?: { signal?: AbortSignal }, -): Promise<{ settled: boolean; sessionId?: string }> { - const normalizedSessionKey = normalizeOptionalString(sessionKey); - return normalizedSessionKey - ? await waitForReplyRunAdmissionBarrier({ - barriersByKey: replyRunState.successorAdmissionBarriersByKey, - minimumTimeoutMs: 0, - sessionKey: normalizedSessionKey, - signal: opts?.signal, - timeoutMs, - }) - : { settled: true }; -} - -export function abortActiveReplyRuns(opts: { - mode: "all" | "compacting"; - onAbortError?: (sessionId: string, error: unknown) => void; -}): boolean { - let aborted = false; - for (const operation of replyRunState.activeRunsByKey.values()) { - if (opts.mode === "compacting" && !isReplyRunCompacting(operation)) { - continue; - } - try { - if (operation.abortForRestart()) { - aborted = true; - } - } catch (error) { - if (operation.result?.kind === "aborted" && operation.result.code === "aborted_for_restart") { - aborted = true; - } - opts.onAbortError?.(operation.sessionId, error); - } - } - return aborted; -} - -export function getActiveReplyRunCount(): number { - return replyRunState.activeRunsByKey.size; -} - -export function listActiveReplyRunSessionIds(): string[] { - return [...replyRunState.activeSessionIdsByKey.values()]; -} - -export function listActiveReplyRunSessionKeys(): string[] { - return [...replyRunState.activeSessionIdsByKey.keys()]; -} - -function evictPriorLifecycleReplyRuns(): void { - const errors: unknown[] = []; - for (const operation of replyRunState.activeRunsByKey.values()) { - if ( - operation.lifecycleGeneration && - isAgentEventLifecycleGenerationCurrent(operation.lifecycleGeneration) - ) { - continue; - } - const evict = evictReplyOperationByOperation.get(operation); - if (evict) { - try { - evict(); - } catch (error) { - errors.push(error); - try { - clearReplyRunState({ - sessionKey: operation.key, - sessionId: operation.sessionId, - operation, - }); - } catch (clearError) { - errors.push(clearError); - } - } - continue; - } - // Pre-generation hot-loaded operations have no retained callback, but their - // public method still closes over the module instance that owns the backend. - try { - if (!operation.abortForRestart()) { - errors.push(new Error(`Stale reply operation was not abortable: ${operation.key}`)); - } - } catch (error) { - errors.push(error); - } - // Admission stays occupied until the old closure clears it. If abort - // synchronously clears and replaces the slot, its captured stateCleared - // makes this completion idempotent instead of erasing the replacement. - try { - operation.complete(); - } catch (error) { - errors.push(error); - } - try { - clearReplyRunState({ - sessionKey: operation.key, - sessionId: operation.sessionId, - operation, - }); - } catch (error) { - errors.push(error); - } - } - if (errors.length > 0) { - throw new AggregateError(errors, "Failed to abort stale reply runs"); - } -} - -registerAgentEventLifecycleRotationHandler("reply-runs", evictPriorLifecycleReplyRuns); - -const replyRunRegistryTestApi = { - resetReplyRunRegistry(): void { - for (const [sessionKey, sessionId] of replyRunState.activeSessionIdsByKey) { - markReplyRunDiagnosticProgress({ - sessionKey, - sessionId, - reason: "reply_operation:registry_reset", - }); - } - replyRunState.activeRunsByKey.clear(); - replyRunState.activeSessionIdsByKey.clear(); - replyRunState.activeKeysBySessionId.clear(); - replyRunState.waitKeysBySessionId.clear(); - replyRunSettle.resetReplyRunSettleTimersForTesting(); - for (const waiters of replyRunState.waitersByKey.values()) { - for (const waiter of waiters) { - waiter.finish(false); - } - } - replyRunState.waitersByKey.clear(); - replyRunState.followupAdmissionBarriersByKey.clear(); - replyRunState.successorAdmissionBarriersByKey.clear(); - }, -}; - -if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") { - (globalThis as Record)[Symbol.for("openclaw.replyRunRegistryTestApi")] = - replyRunRegistryTestApi; -} -/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ + ReplyOperationPhase, +} from "./reply-run-registry.contracts.js"; +export { + abortReplyMessageInjectionTarget, + beginReplyMessageInjectionTarget, + recordAcceptedReplyMessageInjectionTarget, + resolveReplyBackendQueueMessageMismatch, +} from "./reply-run-registry.message-injection.js"; +export { + createReplyOperation, + expireStaleReplyOperation, + forceClearReplyOperation, +} from "./reply-run-registry.operation.js"; +export { + abortActiveReplyRuns, + abortReplyRunBySessionId, + clearReplyRunForResetBySessionId, + expireStaleReplyRunBySessionId, + forceClearReplyRunBySessionId, + getActiveReplyRunCount, + isReplyRunAbortableForCompaction, + isReplyRunActiveForSessionId, + isReplyRunEvidenceStaleBySessionId, + listActiveReplyRunSessionIds, + listActiveReplyRunSessionKeys, + markReplyOperationGlobalLaneWaitProgress, + replyRunRegistry, + resolveActiveReplyOperationForSessionId, + resolveActiveReplyRunSessionId, + resolveActiveReplyRunThreadId, + resolveReplyRunPhaseForSessionId, + supersedeReplyRunByRunId, + waitForReplyOperationOwnerSettlement, + waitForReplyRunEndBySessionId, + waitForReplyRunFollowupAdmission, + waitForReplyRunSuccessorAdmission, +} from "./reply-run-registry.registry.js"; +export { + isReplyRunAbortableForSignal, + isReplyRunEvidenceStale, + isReplyRunSuccessorAdmissionBlocked, + registerReplyOperationSuccessorBarrier, + retainReplyOperationUntilComplete, + runAfterReplyOperationClear, + waitForReplyBarrierSettlement, +} from "./reply-run-registry.state.js";