@openclaw/codex 2026.7.1-beta.1 → 2026.7.1-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{app-server-policy-xi0ZmBGO.js → app-server-policy-BUk0GLMy.js} +5 -5
  2. package/dist/{attempt-notifications-l9LElGOU.js → attempt-notifications-BGsEIIDI.js} +5 -40
  3. package/dist/{command-handlers-vZy8xX5X.js → command-handlers-UVniG5Hl.js} +267 -232
  4. package/dist/command-rpc-BjgEPh19.js +784 -0
  5. package/dist/compact-CZzbcygx.js +603 -0
  6. package/dist/{computer-use-FPGG-mtX.js → computer-use-DDeySrnb.js} +3 -3
  7. package/dist/{config-C_whBx3M.js → config-CYEDnLJ2.js} +36 -15
  8. package/dist/conversation-binding-data-BHRpYbX3.js +121 -0
  9. package/dist/doctor-contract-api.js +439 -22
  10. package/dist/harness.js +29 -10
  11. package/dist/index.js +1119 -28
  12. package/dist/{media-understanding-provider-C5yYzx_6.js → media-understanding-provider-DgqRvU2_.js} +13 -8
  13. package/dist/media-understanding-provider.js +1 -1
  14. package/dist/{models-Ccy1_7nN.js → models-DDmO1zwd.js} +2 -2
  15. package/dist/notification-correlation-Bo7KB3ks.js +35 -0
  16. package/dist/{plugin-app-cache-key-CDioyur-.js → plugin-app-cache-key-o-AHbdaf.js} +8 -4
  17. package/dist/{protocol-dh-ETiNd.js → protocol-2POPqAY4.js} +2 -1
  18. package/dist/{protocol-validators-CB4mtyrs.js → protocol-validators-dZQ-UTOa.js} +117 -62
  19. package/dist/{provider-capabilities-CE5k8cmO.js → provider-capabilities-gTCwjfmh.js} +2920 -114
  20. package/dist/{provider-DosqwRb6.js → provider-cc_62eQE.js} +19 -10
  21. package/dist/provider.js +2 -2
  22. package/dist/{request-BQuSQF29.js → request-BcJyl8KL.js} +2 -2
  23. package/dist/{run-attempt-O3Zs32KI.js → run-attempt-8SPLiDlT.js} +1016 -2877
  24. package/dist/{sandbox-guard-pTY2COoC.js → sandbox-guard-DA2TQfZW.js} +3 -3
  25. package/dist/session-binding-Dc03iwRF.js +595 -0
  26. package/dist/session-binding-meta-B7aEMU7g.js +6 -0
  27. package/dist/{shared-client-rR6TDJA3.js → shared-client-4ICy3U6d.js} +892 -103
  28. package/dist/{side-question-DDVgFmpv.js → side-question-DkjXvYv7.js} +132 -140
  29. package/dist/{thread-lifecycle-DeSMZSXr.js → thread-lifecycle-BskXnNP-.js} +609 -444
  30. package/dist/{web-search-provider.runtime-Q89rQhsL.js → web-search-provider.runtime-B5F42P7x.js} +3 -3
  31. package/npm-shrinkwrap.json +34 -34
  32. package/openclaw.plugin.json +20 -0
  33. package/package.json +9 -6
  34. package/dist/client-IcTBKEqA.js +0 -732
  35. package/dist/client-factory-CwjdQQ4j.js +0 -18
  36. package/dist/compact-BzB9AqN1.js +0 -278
  37. package/dist/conversation-binding-CEE2UyYb.js +0 -1570
  38. package/dist/rate-limit-cache-C7qmZ0Jh.js +0 -26
  39. package/dist/session-binding-Cm0Gg7Z1.js +0 -339
@@ -1,194 +1,53 @@
1
- import { n as isJsonObject, t as flattenCodexDynamicToolFunctions } from "./protocol-dh-ETiNd.js";
2
- import { A as resolveCodexContextEngineProjectionReserveTokens, B as describeCodexNotificationCorrelation, D as fitCodexProjectedContextForTurnStart, E as resolveCodexDynamicToolsLoadingForRuntime, F as shouldBuildCodexPluginThreadConfig, H as readCodexNotificationThreadId, M as buildCodexPluginThreadConfig, N as buildCodexPluginThreadConfigInputFingerprint, O as projectContextEngineAssemblyForCodex, P as mergeCodexThreadConfigs, U as readCodexNotificationTurnId, _ as resolveCodexWebSearchPlan, a as buildDeveloperInstructions, b as sanitizeCodexHistoryImagePayloads, c as codexDynamicToolsFingerprint, f as resolveCodexAppServerThreadModelSelection, h as startOrResumeThread, i as buildContextEngineBinding, k as resolveCodexContextEngineProjectionMaxChars, l as isContextEngineBindingCompatible, n as areCodexDynamicToolFingerprintsCompatible, o as buildTurnCollaborationMode, s as buildTurnStartParams, v as isCodexAppServerProfilerEnabled, z as defaultCodexAppInventoryCache } from "./thread-lifecycle-DeSMZSXr.js";
3
- import { C as readCodexNotificationItem, D as updateActiveCompletionBlockerItemIds, E as shouldDisarmAssistantCompletionIdleWatch, O as updateActiveTurnItemIds, S as isTurnNotification, T as readRawResponseToolCallId, _ as isRawToolOutputCompletionNotification, a as isCodexNotificationOutsideActiveRun, b as isRetryableErrorNotification, c as isCurrentThreadOptionalTurnRequestParams, d as isNativeResponseStreamDeltaNotification, f as isNativeToolProgressNotification, g as isRawReasoningCompletionNotification, h as isRawFunctionToolOutputCompletionNotification, i as isAssistantCompletionReleaseNotification, l as isCurrentThreadTurnRequestParams, m as isRawAssistantProgressNotification, n as describeNotificationActivity, o as isCodexTurnAbortMarkerNotification, p as isPendingOpenClawDynamicToolCompletionNotification, r as isAssistantCommentaryCompletionNotification, s as isCurrentApprovalTurnRequestParams, t as codexExecutionToolName, u as isFileChangePatchUpdatedNotification, v as isReasoningItemCompletionNotification, w as readNotificationItemId, x as isTerminalTurnStatus, y as isReasoningProgressNotification } from "./attempt-notifications-l9LElGOU.js";
4
- import { _ as withMcpElicitationsApprovalPolicy, a as isCodexAppServerApprovalPolicyAllowedByRequirements, d as resolveCodexAppServerRuntimeOptions, f as resolveCodexComputerUseConfig, g as shouldAutoApproveCodexAppServerApprovals, h as resolveOpenClawExecPolicyForCodexAppServer, m as resolveCodexPluginsPolicy, p as resolveCodexModelBackedReviewerPolicyContext, s as isCodexSandboxExecServerEnabled, u as readCodexPluginConfig } from "./config-C_whBx3M.js";
5
- import { a as readCodexDynamicToolCallParams, c as readCodexTurn, i as assertCodexTurnStartResponse } from "./protocol-validators-CB4mtyrs.js";
6
- import { l as resolveCodexUsageLimitResetAtMs, s as formatCodexUsageLimitErrorMessage, u as shouldRefreshCodexRateLimitsForUsageLimitMessage } from "./provider-DosqwRb6.js";
7
- import { a as isCodexAppServerConnectionClosedError, c as MIN_CODEX_SANDBOX_EXEC_SERVER_APP_SERVER_VERSION, i as isCodexAppServerApprovalRequest, n as CodexAppServerRpcError, r as compareCodexAppServerVersions } from "./client-IcTBKEqA.js";
8
- import { i as isCodexAppServerNativeAuthProfile, l as writeCodexAppServerBinding, n as clearCodexAppServerBinding, o as readCodexAppServerBinding, r as clearCodexAppServerBindingForThread } from "./session-binding-Cm0Gg7Z1.js";
9
- import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS, t as buildCodexAppServerRuntimeFingerprint } from "./plugin-app-cache-key-CDioyur-.js";
10
- import { a as formatCodexDisplayText, n as resolveCodexAppServerForOpenClawToolPolicy, t as resolveCodexAppServerForModelProvider } from "./app-server-policy-xi0ZmBGO.js";
11
- import { d as resolveCodexAppServerAuthAccountCacheKey, f as resolveCodexAppServerAuthProfileId, h as resolveCodexAppServerHomeDir, m as resolveCodexAppServerFallbackApiKeyCacheKey, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, o as releaseLeasedSharedCodexAppServerClient, p as resolveCodexAppServerAuthProfileIdForAgent, s as retireSharedCodexAppServerClientIfCurrent, t as clearSharedCodexAppServerClientIfCurrent, u as refreshCodexAppServerAuthTokens } from "./shared-client-rR6TDJA3.js";
12
- import { t as defaultLeasedCodexAppServerClientFactory } from "./client-factory-CwjdQQ4j.js";
13
- import { A as CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS, C as resolveCodexMessageToolProvider, D as shouldWarnCodexDynamicToolBuildStageSummary, E as shouldRequireCodexSandboxExecServerEnvironment, F as resolveCodexTurnTerminalIdleTimeoutMs, I as withCodexStartupTimeout, M as resolveCodexStartupTimeoutMs, N as resolveCodexTurnAssistantCompletionIdleTimeoutMs, P as resolveCodexTurnCompletionIdleTimeoutMs, S as resolveCodexExternalSandboxPolicyForOpenClawSandbox, T as shouldEnableCodexAppServerNativeToolSurface, _ as createCodexDynamicToolBuildStageTracker, a as buildCodexNativeHookRelayConfig, b as resolveCodexAppServerExecutionCwd, c as resolveCodexNativeHookRelayEvents, d as handleCodexAppServerElicitationRequest, f as createCodexDynamicToolBridge, g as buildDynamicTools, h as emitDynamicToolTerminalDiagnostic, i as CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS, j as resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs, k as handleCodexAppServerApprovalRequest, l as resolveCodexNativeHookRelayTtlMs, m as emitDynamicToolStartedDiagnostic, o as buildCodexNativeHookRelayDisabledConfig, p as emitDynamicToolErrorDiagnostic, s as createCodexNativeHookRelay, t as resolveCodexProviderWebSearchSupport, u as scheduleCodexNativeHookRelayUnregister, v as disableCodexPluginThreadConfig, w as resolveCodexSandboxEnvironmentSelection, x as resolveCodexAppServerHookChannelId, y as formatCodexDynamicToolBuildStageSummary } from "./provider-capabilities-CE5k8cmO.js";
14
- import { t as ensureCodexComputerUse } from "./computer-use-FPGG-mtX.js";
15
- import { n as rememberCodexRateLimits, t as readRecentCodexRateLimits } from "./rate-limit-cache-C7qmZ0Jh.js";
1
+ import { n as flattenCodexDynamicToolFunctions, r as isJsonObject } from "./protocol-2POPqAY4.js";
2
+ import { S as updateActiveTurnItemIds, _ as readCodexNotificationItem, a as isCodexTurnAbortMarkerNotification, b as shouldDisarmAssistantCompletionIdleWatch, c as isPendingOpenClawDynamicToolCompletionNotification, d as isRawReasoningCompletionNotification, f as isRawToolOutputCompletionNotification, g as isTerminalTurnStatus, h as isRetryableErrorNotification, i as isAssistantCompletionReleaseNotification, l as isRawAssistantProgressNotification, m as isReasoningProgressNotification, n as describeNotificationActivity, o as isFileChangePatchUpdatedNotification, p as isReasoningItemCompletionNotification, r as isAssistantCommentaryCompletionNotification, s as isNativeToolProgressNotification, t as codexExecutionToolName, u as isRawFunctionToolOutputCompletionNotification, v as readNotificationItemId, x as updateActiveCompletionBlockerItemIds, y as readRawResponseToolCallId } from "./attempt-notifications-BGsEIIDI.js";
3
+ import { _ as shouldAutoApproveCodexAppServerApprovals, a as isCodexAppServerApprovalPolicyAllowedByRequirements, d as resolveCodexAppServerRuntimeOptions, g as resolveOpenClawExecPolicyForCodexAppServer, h as resolveCodexPluginsPolicy, m as resolveCodexModelBackedReviewerPolicyContext, p as resolveCodexComputerUseConfig, s as isCodexSandboxExecServerEnabled, u as readCodexPluginConfig, v as withMcpElicitationsApprovalPolicy } from "./config-CYEDnLJ2.js";
4
+ import { a as readCodexDynamicToolCallParams, i as assertCodexTurnStartResponse } from "./protocol-validators-dZQ-UTOa.js";
5
+ import { A as resolveCodexContextEngineProjectionReserveTokens, B as buildCodexPluginThreadConfigInputFingerprint, D as fitCodexProjectedContextForTurnStart, E as resolveCodexDynamicToolsLoadingForRuntime, F as interruptCodexTurnBestEffort, H as shouldBuildCodexPluginThreadConfig, I as retireCodexAppServerClientAfterTimedOutTurn, K as defaultCodexAppInventoryCache, L as unsubscribeCodexThreadBestEffort, M as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, N as CodexAppServerUnsafeSubscriptionError, O as projectContextEngineAssemblyForCodex, P as closeCodexStartupClientBestEffort, V as mergeCodexThreadConfigs, _ as resolveCodexWebSearchPlan, a as buildDeveloperInstructions, c as codexDynamicToolsFingerprint, f as resolveCodexAppServerThreadModelSelection, h as startOrResumeThread, i as buildContextEngineBinding, j as CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS, k as resolveCodexContextEngineProjectionMaxChars, l as isContextEngineBindingCompatible, n as areCodexDynamicToolFingerprintsCompatible, o as buildTurnCollaborationMode, s as buildTurnStartParams, v as isCodexAppServerProfilerEnabled, z as buildCodexPluginThreadConfig } from "./thread-lifecycle-BskXnNP-.js";
6
+ import { c as formatCodexUsageLimitErrorMessage, d as shouldRefreshCodexRateLimitsForUsageLimitMessage, u as resolveCodexUsageLimitResetAtMs } from "./provider-cc_62eQE.js";
7
+ import { _ as resolveCodexAppServerAuthAccountCacheKey, a as getLeasedSharedCodexAppServerClient, b as resolveCodexAppServerFallbackApiKeyCacheKey, d as isCodexAppServerApprovalRequest, f as isCodexAppServerConnectionClosedError, g as rememberCodexRateLimitsRead, h as readRecentCodexRateLimits, m as readCodexRateLimitsRevision, o as releaseLeasedSharedCodexAppServerClient, p as ensureCodexAppServerClientRuntime, s as retireSharedCodexAppServerClientIfCurrent, t as clearSharedCodexAppServerClientIfCurrent, u as CodexAppServerRpcError, v as resolveCodexAppServerAuthProfileId, x as resolveCodexAppServerHomeDir, y as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-4ICy3U6d.js";
8
+ import { o as sessionBindingIdentity, r as isCodexAppServerNativeAuthProfile } from "./session-binding-Dc03iwRF.js";
9
+ import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS, t as buildCodexAppServerRuntimeFingerprint } from "./plugin-app-cache-key-o-AHbdaf.js";
10
+ import { a as formatCodexDisplayText, n as resolveCodexAppServerForOpenClawToolPolicy, t as resolveCodexAppServerForModelProvider } from "./app-server-policy-BUk0GLMy.js";
11
+ import { n as readCodexNotificationThreadId, r as readCodexNotificationTurnId, t as isCodexNotificationForTurn } from "./notification-correlation-Bo7KB3ks.js";
12
+ import { $ as isDynamicToolTerminalDiagnosticEvent, A as createCodexDynamicToolBuildStageTracker, B as shouldWarnCodexDynamicToolBuildStageSummary, C as resolveCodexLocalRuntimeAttribution, D as emitDynamicToolStartedDiagnostic, E as emitDynamicToolErrorDiagnostic, F as resolveCodexExternalSandboxPolicyForOpenClawSandbox, G as resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs, H as readCodexMirroredSessionHistoryMessages, I as resolveCodexMessageToolProvider, J as resolveCodexTurnCompletionIdleTimeoutMs, K as resolveCodexStartupTimeoutMs, L as resolveCodexSandboxEnvironmentSelection, M as formatCodexDynamicToolBuildStageSummary, N as resolveCodexAppServerExecutionCwd, O as emitDynamicToolTerminalDiagnostic, P as resolveCodexAppServerHookChannelId, Q as hasPendingDynamicToolTerminalDiagnostic, R as shouldEnableCodexAppServerNativeToolSurface, S as scheduleCodexNativeHookRelayUnregister, T as createCodexDynamicToolBridge, U as handleCodexAppServerApprovalRequest, W as CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS, X as withCodexStartupTimeout, Y as resolveCodexTurnTerminalIdleTimeoutMs, Z as handleDynamicToolCallWithTimeout, _ as buildCodexNativeHookRelayDisabledConfig, a as shouldEmitTranscriptToolProgress, at as toCodexDynamicToolProgressResponse, b as resolveCodexNativeHookRelayEvents, c as mirrorPromptAtTurnStartBestEffort, d as resolveCodexToolProgressDetailMode, et as isMatchingDynamicToolTerminalDiagnostic, f as sanitizeCodexToolArguments, g as buildCodexNativeHookRelayConfig, h as CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS, it as shouldReleaseTurnAfterTerminalDynamicTool, j as disableCodexPluginThreadConfig, k as buildDynamicTools, l as mirrorTranscriptBestEffort, nt as resolveTerminalDynamicToolBatchAction, o as buildCodexUserPromptMessage, ot as toCodexDynamicToolProtocolResponse, p as sanitizeCodexToolResponse, q as resolveCodexTurnAssistantCompletionIdleTimeoutMs, r as CodexAppServerEventProjector, rt as shouldBlockTerminalReleaseForNonTerminalDynamicToolResult, s as createCodexAppServerUserMessagePersistenceNotifier, st as resolveCodexToolAbortTerminalReason, t as resolveCodexProviderWebSearchSupport, tt as resolveDynamicToolCallTimeoutMs, u as inferCodexDynamicToolMeta, v as createCodexNativeHookRelay, w as handleCodexAppServerElicitationRequest, x as resolveCodexNativeHookRelayTtlMs, y as emitCodexNativePreToolUseFailureDiagnostic, z as shouldRequireCodexSandboxExecServerEnvironment } from "./provider-capabilities-gTCwjfmh.js";
13
+ import { t as ensureCodexComputerUse } from "./computer-use-DDeySrnb.js";
16
14
  import fs from "node:fs/promises";
17
15
  import path, { posix } from "node:path";
18
- import { asDateTimestampMs, parseStrictNonNegativeInteger, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
19
- import { asFiniteNumber, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
16
+ import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
17
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
20
18
  import { createHash, randomUUID } from "node:crypto";
21
19
  import fsSync from "node:fs";
22
20
  import { markAuthProfileBlockedUntil, resolveAgentDir, resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/agent-runtime";
23
21
  import { loadExecApprovals } from "openclaw/plugin-sdk/exec-approvals-runtime";
24
- import { CODEX_APP_SERVER_CONTEXT_ENGINE_HOST, FAST_MODE_AUTO_PROGRESS_KIND, TOOL_PROGRESS_OUTPUT_MAX_CHARS, assembleHarnessContextEngine, assertContextEngineHostSupport, awaitAgentEndSideEffects, bootstrapHarnessContextEngine, buildAgentHarnessUserInputAnswers, buildBootstrapContextForFiles, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, deliverAgentHarnessUserInputPrompt, embeddedAgentLog, emitAgentEvent, emptyAgentHarnessUserInputAnswers, finalizeHarnessContextEngineTurn, formatErrorMessage, formatFastModeAutoProgressText, formatToolAggregate, formatToolProgressOutput, getAgentHarnessHookRunner, getBeforeToolCallPolicyDiagnosticState, inferToolMetaFromArgs, isActiveHarnessContextEngine, loadCodexBundleMcpThreadConfig, normalizeUsage, resolveAgentHarnessBeforePromptBuildResult, resolveBootstrapFilesForRun, resolveContextEngineOwnerPluginId, resolveFastModeForElapsed, resolveSandboxContext, resolveSessionAgentIds as resolveSessionAgentIds$1, resolveUserPath, runAgentCleanupStep, runAgentEndSideEffects, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
22
+ import { asFiniteNumber, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
23
+ import { CODEX_APP_SERVER_CONTEXT_ENGINE_HOST, FAST_MODE_AUTO_PROGRESS_KIND, assembleHarnessContextEngine, assertContextEngineHostSupport, awaitAgentEndSideEffects, bootstrapHarnessContextEngine, buildAgentHarnessUserInputAnswers, buildBootstrapContextForFiles, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, clearActiveEmbeddedRun, deliverAgentHarnessUserInputPrompt, embeddedAgentLog, emitAgentEvent, emptyAgentHarnessUserInputAnswers, finalizeHarnessContextEngineTurn, formatErrorMessage, formatFastModeAutoProgressText, getAgentHarnessHookRunner, getBeforeToolCallPolicyDiagnosticState, isActiveHarnessContextEngine, loadCodexBundleMcpThreadConfig, resolveAgentHarnessBeforePromptBuildResult, resolveAgentRunAbortLifecycleFields, resolveBootstrapFilesForRun, resolveContextEngineOwnerPluginId, resolveFastModeForElapsed, resolveSandboxContext, resolveSessionAgentIds as resolveSessionAgentIds$1, resolveUserPath, runAgentCleanupStep, runAgentEndSideEffects, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
25
24
  import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";
26
25
  import { spawn } from "node:child_process";
27
26
  import { once } from "node:events";
28
27
  import { WebSocketServer } from "ws";
29
- import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
30
- import { createDiagnosticTraceContextFromActiveScope, emitTrustedDiagnosticEvent, emitTrustedDiagnosticEventWithPrivateData, freezeDiagnosticTraceContext, hasPendingInternalDiagnosticEvent, onInternalDiagnosticEvent, resolveDiagnosticModelContentCapturePolicy } from "openclaw/plugin-sdk/diagnostic-runtime";
31
28
  import { appendRegularFile, pathExists } from "openclaw/plugin-sdk/security-runtime";
29
+ import { createDiagnosticTraceContextFromActiveScope, emitTrustedDiagnosticEvent, emitTrustedDiagnosticEventWithPrivateData, freezeDiagnosticTraceContext, onInternalDiagnosticEvent, resolveDiagnosticModelContentCapturePolicy } from "openclaw/plugin-sdk/diagnostic-runtime";
32
30
  import { MESSAGE_TOOL_DELIVERY_HINTS } from "openclaw/plugin-sdk/message-tool-delivery-hints";
33
- import { buildSessionContext, migrateSessionEntries, parseSessionEntries } from "openclaw/plugin-sdk/agent-sessions";
34
- import { publishSessionTranscriptUpdateByIdentity, resolveSessionTranscriptTarget, withSessionTranscriptWriteLock } from "openclaw/plugin-sdk/session-transcript-runtime";
35
31
  import { isIP } from "node:net";
36
32
  import { SsrFBlockedError, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
37
- import { generatedImageAssetFromBase64 } from "openclaw/plugin-sdk/image-generation";
38
- import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
39
33
  import { createAgentHarnessTaskRuntime, deliverAgentHarnessTaskCompletion, isDurableAgentHarnessCompletionDelivery } from "openclaw/plugin-sdk/agent-harness-task-runtime";
40
- //#region extensions/codex/src/app-server/attempt-client-cleanup.ts
41
- /**
42
- * Best-effort cleanup helpers for Codex app-server startup attempts and turns.
43
- */
44
- /** Timeout for best-effort app-server turn interruption during cleanup. */
45
- const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
46
- /** Timeout for best-effort thread unsubscribe during cleanup. */
47
- const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
48
- async function closeClientAndWaitIfAvailable(client) {
49
- const closeable = client;
50
- if (typeof closeable.closeAndWait === "function") {
51
- await closeable.closeAndWait();
52
- return;
53
- }
54
- closeable.close?.();
55
- }
56
- async function closeCodexStartupClientBestEffort(client) {
57
- if (!client) return;
58
- const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
59
- if (unclaimedSharedClient.closed) {
60
- await closeClientAndWaitIfAvailable(client);
61
- return;
62
- }
63
- if (unclaimedSharedClient.found) {
64
- if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
65
- return;
66
- }
67
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
68
- if (retiredSharedClient) {
69
- if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
70
- return;
71
- }
72
- if (clearSharedCodexAppServerClientIfCurrent(client)) {
73
- await closeClientAndWaitIfAvailable(client);
74
- return;
75
- }
76
- await closeClientAndWaitIfAvailable(client);
77
- }
78
- /** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
79
- function interruptCodexTurnBestEffort(client, params) {
80
- const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
81
- const requestParams = {
82
- threadId: params.threadId,
83
- turnId: params.turnId
84
- };
85
- try {
86
- const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
87
- Promise.resolve(interrupt).catch((error) => {
88
- embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
89
- });
90
- } catch (error) {
91
- embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
92
- }
93
- }
94
- /** Unsubscribes from a thread while swallowing cleanup-only failures. */
95
- async function unsubscribeCodexThreadBestEffort(client, params) {
96
- try {
97
- await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
98
- } catch (error) {
99
- embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
100
- threadId: params.threadId,
101
- error
102
- });
103
- }
104
- }
105
- /**
106
- * Retires the shared client after a timed-out turn so later runs do not reuse a
107
- * potentially wedged app-server connection.
108
- */
109
- async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
110
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
111
- const detachedSharedClient = Boolean(retiredSharedClient);
112
- interruptCodexTurnBestEffort(client, {
113
- threadId: params.threadId,
114
- turnId: params.turnId,
115
- timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
116
- });
117
- await unsubscribeCodexThreadBestEffort(client, {
118
- threadId: params.threadId,
119
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
120
- });
121
- let closedClient = retiredSharedClient?.closed ?? false;
122
- if (!detachedSharedClient) {
123
- const close = client.close;
124
- if (typeof close === "function") try {
125
- close.call(client);
126
- closedClient = true;
127
- } catch (error) {
128
- embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
129
- threadId: params.threadId,
130
- turnId: params.turnId,
131
- error
132
- });
133
- }
134
- }
135
- embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
136
- threadId: params.threadId,
137
- turnId: params.turnId,
138
- reason: params.reason,
139
- detachedSharedClient,
140
- closedClient,
141
- activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
142
- });
143
- }
144
- //#endregion
145
- //#region extensions/codex/src/app-server/session-history.ts
146
- /**
147
- * Reads OpenClaw session history for Codex transcript mirroring and sanitizes
148
- * image payloads before replaying messages into the app-server projector.
149
- */
150
- /** Returns sanitized session-context messages for a Codex mirrored session file. */
151
- async function readCodexMirroredSessionHistoryMessages(target) {
152
- try {
153
- await resolveSessionTranscriptTarget(resolveCodexHistoryTranscriptTarget(target));
154
- const entries = parseSessionEntries(await fs.readFile(target.sessionFile, "utf-8"));
155
- if (entries.length === 0) return [];
156
- const firstEntry = entries[0];
157
- if (firstEntry?.type !== "session" || typeof firstEntry.id !== "string") return;
158
- migrateSessionEntries(entries);
159
- return sanitizeCodexHistoryImagePayloads(buildSessionContext(entries.filter((entry) => {
160
- return entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.type !== "session";
161
- })).messages, "codex mirrored history");
162
- } catch {
163
- return;
164
- }
165
- }
166
- function resolveCodexHistoryTranscriptTarget(target) {
167
- return {
168
- ...target.agentId ? { agentId: target.agentId } : {},
169
- sessionFile: target.sessionFile,
170
- sessionId: target.sessionId,
171
- sessionKey: target.sessionKey ?? ""
172
- };
173
- }
174
- //#endregion
175
34
  //#region extensions/codex/src/app-server/attempt-context.ts
176
35
  /**
177
36
  * Builds Codex app-server prompt context, workspace bootstrap injections,
178
37
  * system-prompt reports, and context-engine projection decisions.
179
38
  */
180
- const CODEX_NATIVE_PROJECT_DOC_BASENAMES = new Set(["agents.md"]);
181
- const CODEX_INHERITED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = new Set(["tools.md"]);
182
- const CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = new Set([
39
+ const CODEX_NATIVE_PROJECT_DOC_BASENAMES = /* @__PURE__ */ new Set(["agents.md"]);
40
+ const CODEX_INHERITED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = /* @__PURE__ */ new Set(["tools.md"]);
41
+ const CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = /* @__PURE__ */ new Set([
183
42
  "identity.md",
184
43
  "soul.md",
185
44
  "user.md"
186
45
  ]);
187
- const CODEX_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = new Set([...CODEX_INHERITED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES, ...CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES]);
46
+ const CODEX_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = /* @__PURE__ */ new Set([...CODEX_INHERITED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES, ...CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES]);
188
47
  const CODEX_HEARTBEAT_CONTEXT_BASENAME = "heartbeat.md";
189
48
  const CODEX_MEMORY_CONTEXT_BASENAME = "memory.md";
190
- const CODEX_MEMORY_TOOL_NAMES = new Set(["memory_search", "memory_get"]);
191
- const CODEX_BOOTSTRAP_CONTEXT_ORDER = new Map([
49
+ const CODEX_MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set(["memory_search", "memory_get"]);
50
+ const CODEX_BOOTSTRAP_CONTEXT_ORDER = /* @__PURE__ */ new Map([
192
51
  ["soul.md", 10],
193
52
  ["identity.md", 20],
194
53
  ["user.md", 30],
@@ -427,10 +286,10 @@ function stableJsonHash(value) {
427
286
  function buildCodexBootstrapInjectionStats(params) {
428
287
  const injectedIndex = indexCodexContextFileContent(params.injectedFiles);
429
288
  const developerInstructionIndex = indexCodexContextFileContent(params.developerInstructionFiles ?? []);
430
- const memoryToolRoutedPaths = new Set((params.memoryToolRoutedBootstrapFiles ?? []).map((file) => readNonEmptyString$1(file.path)).filter(isNonEmptyString$1).map(normalizeCodexContextFilePath));
289
+ const memoryToolRoutedPaths = new Set((params.memoryToolRoutedBootstrapFiles ?? []).map((file) => readNonEmptyString(file.path)).filter(isNonEmptyString$1).map(normalizeCodexContextFilePath));
431
290
  return params.bootstrapFiles.map((file) => {
432
- const fileName = readNonEmptyString$1(file.name);
433
- const pathValue = readNonEmptyString$1(file.path) ?? fileName ?? "";
291
+ const fileName = readNonEmptyString(file.name);
292
+ const pathValue = readNonEmptyString(file.path) ?? fileName ?? "";
434
293
  const displayName = (fileName ?? getCodexContextFileDisplayBasename(pathValue)) || pathValue;
435
294
  const baseName = getCodexContextFileBasename(pathValue || fileName || "");
436
295
  const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
@@ -461,7 +320,7 @@ function indexCodexContextFileContent(files) {
461
320
  const byPath = /* @__PURE__ */ new Map();
462
321
  const byBaseName = /* @__PURE__ */ new Map();
463
322
  for (const file of files) {
464
- const pathValue = readNonEmptyString$1(file.path);
323
+ const pathValue = readNonEmptyString(file.path);
465
324
  if (!pathValue) continue;
466
325
  if (!byPath.has(pathValue)) byPath.set(pathValue, file.content);
467
326
  const baseName = getCodexContextFileBasename(pathValue);
@@ -485,7 +344,7 @@ function readCodexIndexedContextFileContent(index, pathValue, fileName) {
485
344
  function readPositiveNumber(value) {
486
345
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
487
346
  }
488
- function readNonEmptyString$1(value) {
347
+ function readNonEmptyString(value) {
489
348
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
490
349
  }
491
350
  /**
@@ -706,13 +565,13 @@ function isMissingCodexBootstrapContextFile(file) {
706
565
  }
707
566
  function toCodexEmbeddedContextFile(file) {
708
567
  return {
709
- path: readNonEmptyString$1(file.path) ?? readNonEmptyString$1(file.name) ?? "",
568
+ path: readNonEmptyString(file.path) ?? readNonEmptyString(file.name) ?? "",
710
569
  content: file.content ?? ""
711
570
  };
712
571
  }
713
572
  function isCodexWorkspaceRootMemoryBootstrapFile(params) {
714
573
  return isCodexWorkspaceRootMemoryPath({
715
- filePath: readNonEmptyString$1(params.file.path) ?? readNonEmptyString$1(params.file.name) ?? "",
574
+ filePath: readNonEmptyString(params.file.path) ?? readNonEmptyString(params.file.name) ?? "",
716
575
  workspaceDir: params.workspaceDir
717
576
  });
718
577
  }
@@ -819,6 +678,7 @@ function buildCodexPluginThreadConfigEligibilityLogData(params) {
819
678
  enabled: params.pluginThreadConfigRequired,
820
679
  policyConfigured: params.resolvedPluginPolicy?.configured === true,
821
680
  policyEnabled: params.resolvedPluginPolicy?.enabled === true,
681
+ allowAllPlugins: params.resolvedPluginPolicy?.allowAllPlugins === true,
822
682
  pluginConfigKeys: params.resolvedPluginPolicy?.pluginPolicies.map((plugin) => plugin.configKey).toSorted(),
823
683
  enabledPluginConfigKeys: params.enabledPluginConfigKeys,
824
684
  appCacheKeyFingerprint: fingerprintCodexLogValue("openclaw:codex:plugin-app-cache-key:v1", params.pluginAppCacheKey),
@@ -927,7 +787,7 @@ function reportCodexExecutionNotification(params) {
927
787
  }
928
788
  /** Returns true when a notification ends the current app-server turn. */
929
789
  function isTerminalCodexTurnNotificationForTurn(params) {
930
- if (!isTurnNotification(params.notification.params, params.threadId, params.turnId)) return false;
790
+ if (!isCodexNotificationForTurn(params.notification.params, params.threadId, params.turnId)) return false;
931
791
  return params.notification.method === "turn/completed" || isCodexTurnAbortMarkerNotification(params.notification, { currentPromptTexts: params.currentPromptTexts });
932
792
  }
933
793
  /**
@@ -936,11 +796,10 @@ function isTerminalCodexTurnNotificationForTurn(params) {
936
796
  */
937
797
  function applyCodexTurnNotificationState(params) {
938
798
  const { notification, turnWatches } = params;
939
- const isCurrentTurnNotification = isTurnNotification(notification.params, params.threadId, params.turnId);
799
+ const isCurrentTurnNotification = isCodexNotificationForTurn(notification.params, params.threadId, params.turnId);
940
800
  const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
941
- const isNativeResponseStreamDelta = isNativeResponseStreamDeltaNotification(notification);
942
801
  let turnCrossedToolHandoff = params.turnCrossedToolHandoff;
943
- if (isCurrentTurnNotification && !isNativeResponseStreamDelta) {
802
+ if (isCurrentTurnNotification) {
944
803
  turnWatches.touchActivity(`notification:${notification.method}`, {
945
804
  details: describeNotificationActivity(notification),
946
805
  attemptProgress: true
@@ -985,7 +844,7 @@ function applyCodexTurnNotificationState(params) {
985
844
  else if (rawResponseItemCompletedWithNoActiveItems) turnWatches.armCompletionIdleWatch();
986
845
  else if (isCurrentTurnNotification && rawToolOutputCompletion) armPostToolContinuationWatch();
987
846
  else if (isCurrentTurnNotification && shouldDisarmAssistantCompletionIdleWatch(notification)) turnWatches.disarmAssistantCompletionIdleWatch();
988
- if (turnWatches.isCompletionIdleWatchArmed() && !turnWatches.isCompletionIdleWatchPinnedByTerminalError() && notification.method !== "turn/completed" && isCurrentTurnNotification && !isNativeResponseStreamDelta && !trackedDynamicToolCompletion && !rawToolOutputCompletion && !postToolProgressNeedsTerminalGuard && !postToolPatchUpdateNeedsTerminalGuard && !rawResponseItemCompletedWithNoActiveItems && !shouldArmNoToolPostProgressReplyWatch && !shouldArmNoToolPostRawProgressReplyWatch && !shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) turnWatches.disarmCompletionIdleWatch();
847
+ if (turnWatches.isCompletionIdleWatchArmed() && !turnWatches.isCompletionIdleWatchPinnedByTerminalError() && notification.method !== "turn/completed" && isCurrentTurnNotification && !trackedDynamicToolCompletion && !rawToolOutputCompletion && !postToolProgressNeedsTerminalGuard && !postToolPatchUpdateNeedsTerminalGuard && !rawResponseItemCompletedWithNoActiveItems && !shouldArmNoToolPostProgressReplyWatch && !shouldArmNoToolPostRawProgressReplyWatch && !shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) turnWatches.disarmCompletionIdleWatch();
989
848
  if (trackedDynamicToolCompletion) {
990
849
  const itemId = readNotificationItemId(notification);
991
850
  if (itemId) {
@@ -2258,7 +2117,6 @@ const CODEX_SANDBOX_EXEC_SERVER_MAX_INBOUND_MESSAGE_BYTES = 100 * 1024 * 1024;
2258
2117
  async function ensureCodexSandboxExecServerEnvironment(params) {
2259
2118
  if (!params.sandbox?.enabled || !params.sandbox.backend) return;
2260
2119
  if (!canExposeLocalExecServerToAppServer(params.appServerStartOptions)) throw new Error("OpenClaw Codex exec-server uses a local loopback URL and cannot be registered with a remote Codex app-server.");
2261
- assertCodexSandboxExecServerSupported(params.client);
2262
2120
  const execServer = await acquireOpenClawExecServer(params.sandbox);
2263
2121
  try {
2264
2122
  await params.client.request("environment/add", {
@@ -2287,10 +2145,6 @@ async function releaseCodexSandboxExecServerEnvironment(sandbox) {
2287
2145
  const server = await SANDBOX_EXEC_SERVERS.get(sandbox.runtimeId)?.catch(() => void 0);
2288
2146
  if (server) await releaseOpenClawExecServer(server);
2289
2147
  }
2290
- function assertCodexSandboxExecServerSupported(client) {
2291
- const detectedVersion = client.getServerVersion();
2292
- if (!detectedVersion || compareCodexAppServerVersions(detectedVersion, "0.132.0") < 0) throw new Error(`Codex app-server ${MIN_CODEX_SANDBOX_EXEC_SERVER_APP_SERVER_VERSION} or newer is required for OpenClaw sandbox exec-server environments, but detected ${detectedVersion ?? "an unknown version"}. Disable appServer.experimental.sandboxExecServer or configure a newer Codex app-server binary.`);
2293
- }
2294
2148
  function isEnvironmentAddUnsupported(error) {
2295
2149
  if (!(error instanceof Error)) return false;
2296
2150
  return error.message.includes("environment/add") && (error.message.includes("unknown variant") || error.message.includes("Method not found"));
@@ -2447,6 +2301,335 @@ async function dispatchRequest(execServer, processes, socket, request) {
2447
2301
  }
2448
2302
  }
2449
2303
  //#endregion
2304
+ //#region extensions/codex/src/app-server/turn-router.ts
2305
+ /** Keyed routing for all turn traffic on one shared Codex app-server client. */
2306
+ const DEFAULT_PREBIND_NOTIFICATION_LIMIT = 256;
2307
+ const CODEX_APP_SERVER_NATIVE_TURN_WAIT_TIMEOUT_MS = 3e4;
2308
+ const routers = /* @__PURE__ */ new WeakMap();
2309
+ /** Returns the sole router installed on a physical app-server client. */
2310
+ function getCodexAppServerTurnRouter(client) {
2311
+ const existing = routers.get(client);
2312
+ if (existing) return existing;
2313
+ const router = new ClientTurnRouter(client);
2314
+ routers.set(client, router);
2315
+ return router;
2316
+ }
2317
+ var ClientTurnRouter = class {
2318
+ constructor(client) {
2319
+ this.routes = /* @__PURE__ */ new Map();
2320
+ this.nativeTurnCompletionWatchers = /* @__PURE__ */ new Map();
2321
+ this.disposed = false;
2322
+ client.addNotificationHandler((notification) => this.routeNotification(notification));
2323
+ client.addRequestHandler((request) => this.routeRequest(request));
2324
+ client.addCloseHandler(() => this.dispose());
2325
+ }
2326
+ reserveThread(options) {
2327
+ this.assertActive();
2328
+ const threadId = requireId(options.threadId, "thread id");
2329
+ if (this.routes.has(threadId)) throw new Error(`codex app-server thread route already reserved: ${threadId}`);
2330
+ const route = {
2331
+ threadId,
2332
+ controller: new AbortController(),
2333
+ ended: deferred(),
2334
+ activated: deferred(),
2335
+ gate: "open",
2336
+ pending: [],
2337
+ notificationTail: Promise.resolve(),
2338
+ nativeTurnCompleted: false
2339
+ };
2340
+ this.routes.set(threadId, route);
2341
+ if (options.onNotification || options.onRequest) this.activateNow(route, options);
2342
+ const releaseOn = options.releaseOn;
2343
+ if (releaseOn) {
2344
+ const release = () => this.release(route, abortReason(releaseOn));
2345
+ releaseOn.addEventListener("abort", release, { once: true });
2346
+ route.detachReleaseOn = () => releaseOn.removeEventListener("abort", release);
2347
+ if (releaseOn.aborted) release();
2348
+ }
2349
+ return {
2350
+ threadId,
2351
+ signal: route.controller.signal,
2352
+ activate: (handlers) => this.activate(route, handlers),
2353
+ armTurn: () => this.armTurn(route),
2354
+ bindTurn: (turnId) => this.bindTurn(route, turnId),
2355
+ cancelTurn: () => this.cancelTurn(route),
2356
+ waitForTurnCompletion: (waitOptions) => this.waitForTurnCompletion(route, waitOptions),
2357
+ drain: () => this.drainNotifications(route),
2358
+ release: () => this.release(route)
2359
+ };
2360
+ }
2361
+ watchNativeTurnCompletion(options) {
2362
+ this.assertActive();
2363
+ const threadId = requireId(options.threadId, "thread id");
2364
+ const turnId = requireId(options.turnId, "turn id");
2365
+ let settle;
2366
+ const completion = new Promise((resolve) => {
2367
+ settle = resolve;
2368
+ });
2369
+ const watchers = this.nativeTurnCompletionWatchers.get(threadId) ?? /* @__PURE__ */ new Set();
2370
+ this.nativeTurnCompletionWatchers.set(threadId, watchers);
2371
+ let settled = false;
2372
+ const finish = (completed) => {
2373
+ if (settled) return;
2374
+ settled = true;
2375
+ watchers.delete(watcher);
2376
+ if (watchers.size === 0) this.nativeTurnCompletionWatchers.delete(threadId);
2377
+ clearTimeout(timeout);
2378
+ settle(completed);
2379
+ };
2380
+ const touch = () => {
2381
+ timeout.refresh();
2382
+ };
2383
+ const watcher = {
2384
+ turnId,
2385
+ finish,
2386
+ touch
2387
+ };
2388
+ watchers.add(watcher);
2389
+ const timeout = setTimeout(() => finish(false), Math.max(1, options.timeoutMs));
2390
+ timeout.unref?.();
2391
+ return {
2392
+ completion,
2393
+ cancel: () => finish(false)
2394
+ };
2395
+ }
2396
+ dispose() {
2397
+ if (this.disposed) return;
2398
+ this.disposed = true;
2399
+ for (const route of this.routes.values()) this.release(route, /* @__PURE__ */ new Error("codex app-server turn router closed"));
2400
+ for (const watchers of this.nativeTurnCompletionWatchers.values()) for (const watcher of watchers) watcher.finish(false);
2401
+ }
2402
+ async activate(route, handlers) {
2403
+ this.assertRoute(route);
2404
+ this.activateNow(route, handlers);
2405
+ await this.waitForNotifications(route);
2406
+ this.assertRoute(route);
2407
+ }
2408
+ activateNow(route, handlers) {
2409
+ if (route.handlers) throw new Error(`codex app-server thread route already activated: ${route.threadId}`);
2410
+ this.assertRoute(route);
2411
+ if (!handlers.onNotification && !handlers.onRequest) throw new Error("codex app-server thread route requires a notification or request handler");
2412
+ route.handlers = handlers;
2413
+ if (!handlers.onNotification) route.pending.length = 0;
2414
+ else if (route.gate !== "armed") this.flushNotifications(route);
2415
+ route.activated.resolve();
2416
+ }
2417
+ armTurn(route) {
2418
+ this.assertRoute(route);
2419
+ if (route.gate !== "open") throw new Error(`codex app-server thread route cannot arm from ${route.gate}`);
2420
+ route.gate = "armed";
2421
+ route.nativeTurnCompleted = false;
2422
+ route.binding = deferred();
2423
+ }
2424
+ async cancelTurn(route) {
2425
+ if (route.released || route.gate !== "armed") return;
2426
+ route.gate = "open";
2427
+ route.binding?.resolve();
2428
+ route.binding = void 0;
2429
+ this.flushNotifications(route);
2430
+ await this.waitForNotifications(route);
2431
+ this.assertRoute(route);
2432
+ }
2433
+ async bindTurn(route, turnIdInput) {
2434
+ this.assertRoute(route);
2435
+ if (!route.handlers) throw new Error("codex app-server thread route must be activated before binding a turn");
2436
+ if (route.gate !== "armed") throw new Error(`codex app-server thread route cannot bind from ${route.gate}`);
2437
+ const turnId = requireId(turnIdInput, "turn id");
2438
+ route.gate = "bound";
2439
+ route.turnId = turnId;
2440
+ this.flushNotifications(route);
2441
+ route.binding?.resolve();
2442
+ await this.waitForNotifications(route);
2443
+ this.assertRoute(route);
2444
+ }
2445
+ routeNotification(notification) {
2446
+ if (this.disposed) return;
2447
+ const scope = readScope(notification.params);
2448
+ const watchers = scope.threadId ? this.nativeTurnCompletionWatchers.get(scope.threadId) : void 0;
2449
+ const route = scope.threadId ? this.routes.get(scope.threadId) : void 0;
2450
+ if (!watchers && !route) return;
2451
+ const terminal = isCodexTerminalTurnNotification(notification);
2452
+ if (scope.turnId && watchers) {
2453
+ for (const watcher of watchers) if (watcher.turnId === scope.turnId) if (terminal) watcher.finish(true);
2454
+ else watcher.touch();
2455
+ }
2456
+ if (!route) return;
2457
+ const routeScope = {
2458
+ threadId: route.threadId,
2459
+ ...scope.turnId ? { turnId: scope.turnId } : {}
2460
+ };
2461
+ const receivedAtMs = Date.now();
2462
+ if (route.gate !== "bound" && terminal) if (route.nativeTurnCompletion) route.nativeTurnCompletion.resolve();
2463
+ else route.nativeTurnCompleted = true;
2464
+ if (!route.handlers) {
2465
+ this.bufferNotification(route, notification, routeScope, receivedAtMs);
2466
+ return;
2467
+ }
2468
+ const handler = route.handlers.onNotification;
2469
+ if (!handler) return;
2470
+ if (route.gate === "bound" && scope.turnId && scope.turnId !== route.turnId) return;
2471
+ if (route.gate === "armed") {
2472
+ this.bufferNotification(route, notification, routeScope, receivedAtMs);
2473
+ return;
2474
+ }
2475
+ route.handlers.onNotificationReceived?.(notification, routeScope, receivedAtMs);
2476
+ this.enqueueNotification(route, handler, notification, routeScope);
2477
+ return route.notificationTail;
2478
+ }
2479
+ async routeRequest(request) {
2480
+ if (this.disposed) return;
2481
+ const scope = readScope(request.params);
2482
+ if (!scope.threadId) return;
2483
+ const route = this.routes.get(scope.threadId);
2484
+ if (!route || route.released) return;
2485
+ if (!route.handlers) await route.activated.promise;
2486
+ if (route.released || !route.handlers) return;
2487
+ const handler = route.handlers.onRequest;
2488
+ if (!handler) return;
2489
+ while (route.gate === "armed") {
2490
+ await route.binding?.promise;
2491
+ if (route.released) return;
2492
+ }
2493
+ if (route.gate === "bound") {
2494
+ if (scope.turnId && scope.turnId !== route.turnId) return;
2495
+ if (route.released) return;
2496
+ }
2497
+ await this.waitForNotifications(route);
2498
+ if (route.released) return;
2499
+ try {
2500
+ const result = await handler(request, {
2501
+ threadId: scope.threadId,
2502
+ ...scope.turnId ? { turnId: scope.turnId } : {}
2503
+ });
2504
+ return route.released ? void 0 : result;
2505
+ } catch (error) {
2506
+ if (route.released) return;
2507
+ throw error;
2508
+ }
2509
+ }
2510
+ flushNotifications(route) {
2511
+ const handler = route.handlers?.onNotification;
2512
+ if (!handler) return;
2513
+ for (const pending of route.pending.splice(0)) if (!pending.scope.turnId || route.gate !== "bound" || pending.scope.turnId === route.turnId) {
2514
+ route.handlers?.onNotificationReceived?.(pending.notification, pending.scope, pending.receivedAtMs);
2515
+ this.enqueueNotification(route, handler, pending.notification, pending.scope);
2516
+ }
2517
+ }
2518
+ bufferNotification(route, notification, scope, receivedAtMs) {
2519
+ if (route.pending.length < DEFAULT_PREBIND_NOTIFICATION_LIMIT) {
2520
+ route.pending.push({
2521
+ notification,
2522
+ receivedAtMs,
2523
+ scope
2524
+ });
2525
+ return;
2526
+ }
2527
+ const error = /* @__PURE__ */ new Error(`codex app-server pre-bind notification buffer exceeded ${DEFAULT_PREBIND_NOTIFICATION_LIMIT} entries for thread ${route.threadId}`);
2528
+ embeddedAgentLog.warn(error.message);
2529
+ this.release(route, error);
2530
+ }
2531
+ enqueueNotification(route, handler, notification, scope) {
2532
+ if (route.released) return;
2533
+ route.notificationTail = route.notificationTail.then(() => handler(notification, scope)).catch((error) => {
2534
+ if (!route.released) embeddedAgentLog.warn("codex app-server keyed notification handler failed", {
2535
+ method: notification.method,
2536
+ threadId: route.threadId,
2537
+ turnId: route.turnId,
2538
+ error
2539
+ });
2540
+ });
2541
+ }
2542
+ async waitForNotifications(route) {
2543
+ await Promise.race([route.notificationTail, route.ended.promise]);
2544
+ }
2545
+ async drainNotifications(route) {
2546
+ await route.notificationTail;
2547
+ }
2548
+ async waitForTurnCompletion(route, options) {
2549
+ this.assertRoute(route);
2550
+ if (route.nativeTurnCompleted) {
2551
+ route.nativeTurnCompleted = false;
2552
+ return true;
2553
+ }
2554
+ if (route.nativeTurnCompletion) throw new Error(`codex app-server turn completion wait already active: ${route.threadId}`);
2555
+ const completion = deferred();
2556
+ route.nativeTurnCompletion = completion;
2557
+ let timeout;
2558
+ let removeAbort;
2559
+ const timedOut = new Promise((resolve) => {
2560
+ timeout = setTimeout(() => resolve(false), Math.max(1, options.timeoutMs));
2561
+ });
2562
+ const aborted = new Promise((resolve) => {
2563
+ const signal = options.signal;
2564
+ if (!signal) return;
2565
+ const onAbort = () => resolve(false);
2566
+ signal.addEventListener("abort", onAbort, { once: true });
2567
+ removeAbort = () => signal.removeEventListener("abort", onAbort);
2568
+ if (signal.aborted) onAbort();
2569
+ });
2570
+ try {
2571
+ return await Promise.race([
2572
+ completion.promise.then(() => true),
2573
+ route.ended.promise.then(() => false),
2574
+ timedOut,
2575
+ aborted
2576
+ ]);
2577
+ } finally {
2578
+ if (route.nativeTurnCompletion === completion) route.nativeTurnCompletion = void 0;
2579
+ if (timeout) clearTimeout(timeout);
2580
+ removeAbort?.();
2581
+ }
2582
+ }
2583
+ release(route, error = /* @__PURE__ */ new Error("codex app-server thread route is released")) {
2584
+ if (route.released) return;
2585
+ route.released = error;
2586
+ route.pending.length = 0;
2587
+ route.ended.resolve();
2588
+ route.activated.resolve();
2589
+ route.binding?.resolve();
2590
+ route.detachReleaseOn?.();
2591
+ route.controller.abort(error);
2592
+ if (this.routes.get(route.threadId) === route) this.routes.delete(route.threadId);
2593
+ }
2594
+ assertActive() {
2595
+ if (this.disposed) throw new Error("codex app-server turn router is closed");
2596
+ }
2597
+ assertRoute(route) {
2598
+ if (route.released) throw route.released;
2599
+ }
2600
+ };
2601
+ /** True after Codex will not continue the exact turn. */
2602
+ function isCodexTerminalTurnNotification(notification) {
2603
+ if (notification.method === "turn/completed") return true;
2604
+ return notification.method === "error" && isJsonObject(notification.params) && notification.params.willRetry === false;
2605
+ }
2606
+ function deferred() {
2607
+ let resolve;
2608
+ return {
2609
+ promise: new Promise((resolvePromise) => {
2610
+ resolve = resolvePromise;
2611
+ }),
2612
+ resolve
2613
+ };
2614
+ }
2615
+ function abortReason(signal) {
2616
+ return signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "codex app-server thread route aborted"));
2617
+ }
2618
+ function readScope(value) {
2619
+ if (!isJsonObject(value)) return {};
2620
+ const threadId = readCodexNotificationThreadId(value);
2621
+ const turnId = readCodexNotificationTurnId(value);
2622
+ return {
2623
+ ...threadId ? { threadId } : {},
2624
+ ...turnId ? { turnId } : {}
2625
+ };
2626
+ }
2627
+ function requireId(value, label) {
2628
+ const normalized = value.trim();
2629
+ if (!normalized) throw new Error(`codex app-server ${label} must not be empty`);
2630
+ return normalized;
2631
+ }
2632
+ //#endregion
2450
2633
  //#region extensions/codex/src/app-server/attempt-startup.ts
2451
2634
  /**
2452
2635
  * Startup orchestration for Codex app-server attempts, including shared-client
@@ -2500,7 +2683,11 @@ async function startCodexAttemptThread(params) {
2500
2683
  let startupAttemptError;
2501
2684
  let startupAttemptSucceeded = false;
2502
2685
  try {
2503
- startupClient = await params.attemptClientFactory(params.appServer.start, params.startupAuthProfileId, params.agentDir, params.config, {
2686
+ startupClient = await params.attemptClientFactory({
2687
+ startOptions: params.appServer.start,
2688
+ authProfileId: params.startupAuthProfileId,
2689
+ agentDir: params.agentDir,
2690
+ config: params.config,
2504
2691
  onStartedClient: (client) => {
2505
2692
  startupClientForAbandonedRequestCleanup = client;
2506
2693
  if (startupAbandoned || startupAbandonController.signal.aborted) closeCodexStartupClientBestEffort(client);
@@ -2519,6 +2706,12 @@ async function startCodexAttemptThread(params) {
2519
2706
  startupClientForAbandonedRequestCleanup = activeStartupClient;
2520
2707
  if (startupAbandoned) throw new Error("codex app-server startup timed out");
2521
2708
  if (startupAbandonController.signal.aborted) throw new Error("codex app-server startup aborted");
2709
+ ensureCodexAppServerClientRuntime(activeStartupClient, {
2710
+ agentDir: params.agentDir,
2711
+ authProfileId: params.startupAuthProfileId,
2712
+ config: params.config
2713
+ });
2714
+ const turnRouter = getCodexAppServerTurnRouter(activeStartupClient);
2522
2715
  await ensureCodexComputerUse({
2523
2716
  client: activeStartupClient,
2524
2717
  pluginConfig: params.pluginConfig,
@@ -2595,8 +2788,31 @@ async function startCodexAttemptThread(params) {
2595
2788
  remoteWorkspaceRoot: params.appServer.remoteWorkspaceRoot
2596
2789
  });
2597
2790
  const startupSandboxPolicy = startupSandboxEnvironment ? resolveCodexExternalSandboxPolicyForOpenClawSandbox(params.sandbox) : void 0;
2598
- const buildThreadLifecycleParams = (signal) => ({
2791
+ let startupReservation;
2792
+ const releaseStartupReservation = () => {
2793
+ startupReservation?.release();
2794
+ startupReservation = void 0;
2795
+ };
2796
+ const reserveStartupThread = (threadId) => {
2797
+ if (startupReservation) {
2798
+ if (startupReservation.threadId !== threadId) throw new Error(`codex app-server reserved ${startupReservation.threadId} but started ${threadId}`);
2799
+ return { release: releaseStartupReservation };
2800
+ }
2801
+ startupReservation = turnRouter.reserveThread({
2802
+ threadId,
2803
+ releaseOn: params.signal
2804
+ });
2805
+ return { release: releaseStartupReservation };
2806
+ };
2807
+ const releaseStartupResources = async () => {
2808
+ releaseStartupReservation();
2809
+ await releaseStartupSandboxEnvironment();
2810
+ };
2811
+ releaseStartupResourcesOnTimeout = releaseStartupResources;
2812
+ const buildThreadLifecycleParams = (signal, reserveResumeThread) => ({
2599
2813
  client: activeStartupClient,
2814
+ reserveResumeThread,
2815
+ bindingStore: params.bindingStore,
2600
2816
  params: params.buildAttemptParams(),
2601
2817
  agentId: params.sessionAgentId,
2602
2818
  cwd: startupExecutionCwd,
@@ -2636,15 +2852,25 @@ async function startCodexAttemptThread(params) {
2636
2852
  } : void 0
2637
2853
  });
2638
2854
  try {
2639
- const startupThread = await startOrResumeThread(buildThreadLifecycleParams(startupAbandonController.signal));
2640
- if (startupAbandonController.signal.aborted) {
2641
- await releaseStartupSandboxEnvironment();
2642
- throw new Error("codex app-server startup aborted");
2855
+ const startupThread = await startOrResumeThread(buildThreadLifecycleParams(startupAbandonController.signal, reserveStartupThread));
2856
+ try {
2857
+ reserveStartupThread(startupThread.threadId);
2858
+ } catch (error) {
2859
+ if (!await unsubscribeCodexThreadBestEffort(activeStartupClient, {
2860
+ threadId: startupThread.threadId,
2861
+ timeoutMs: 5e3
2862
+ })) throw new CodexAppServerUnsafeSubscriptionError("Codex startup subscription cleanup failed", { cause: error });
2863
+ throw error;
2643
2864
  }
2865
+ if (startupAbandonController.signal.aborted) throw new Error("codex app-server startup aborted");
2866
+ const startupRoute = startupReservation;
2867
+ if (!startupRoute) throw new Error("codex app-server startup did not reserve its thread route");
2644
2868
  startupSandboxEnvironmentAcquired = false;
2645
2869
  startupAttemptSucceeded = true;
2646
2870
  return {
2647
2871
  client: activeStartupClient,
2872
+ turnRouter,
2873
+ turnRoute: startupRoute,
2648
2874
  thread: startupThread,
2649
2875
  sandboxEnvironment: startupSandboxEnvironment,
2650
2876
  environmentSelection: startupEnvironmentSelection,
@@ -2653,10 +2879,10 @@ async function startCodexAttemptThread(params) {
2653
2879
  restartContextEngineCodexThread: () => startOrResumeThread(buildThreadLifecycleParams(params.signal))
2654
2880
  };
2655
2881
  } catch (error) {
2656
- await releaseStartupSandboxEnvironment();
2882
+ await releaseStartupResources();
2657
2883
  throw error;
2658
2884
  } finally {
2659
- if (releaseStartupResourcesOnTimeout === releaseStartupSandboxEnvironment) releaseStartupResourcesOnTimeout = void 0;
2885
+ if (releaseStartupResourcesOnTimeout === releaseStartupResources) releaseStartupResourcesOnTimeout = void 0;
2660
2886
  }
2661
2887
  } catch (error) {
2662
2888
  startupAttemptError = error;
@@ -2912,7 +3138,7 @@ function createCodexAttemptTurnWatchController(params) {
2912
3138
  }
2913
3139
  function scheduleAssistantCompletionIdleWatch() {
2914
3140
  clearAssistantCompletionIdleTimer();
2915
- if (params.isCompleted() || params.signal.aborted || !assistantCompletionIdleWatchArmed) return;
3141
+ if (params.isCompleted() || params.signal.aborted || !assistantCompletionIdleWatchArmed || params.getActiveFinalizationHookCount() > 0) return;
2916
3142
  const elapsedMs = Math.max(0, Date.now() - assistantCompletionLastActivityAt);
2917
3143
  const delayMs = Math.max(1, turnAssistantCompletionIdleTimeoutMs - elapsedMs);
2918
3144
  assistantCompletionIdleTimer = setTimeout(fireAssistantCompletionIdleRelease, delayMs);
@@ -2955,10 +3181,16 @@ function createCodexAttemptTurnWatchController(params) {
2955
3181
  }
2956
3182
  function fireAssistantCompletionIdleRelease() {
2957
3183
  if (params.isCompleted() || params.signal.aborted || !assistantCompletionIdleWatchArmed) return;
2958
- if (params.getActiveAppServerTurnRequests() > 0 || params.getActiveTurnItemCount() > 0) {
3184
+ if (params.getActiveAppServerTurnRequests() > 0 || params.getActiveTurnItemCount() > 0 || params.getActiveFinalizationHookCount() > 0) {
2959
3185
  scheduleAssistantCompletionIdleWatch();
2960
3186
  return;
2961
3187
  }
3188
+ if (!params.canReleaseAssistantCompletionIdle()) {
3189
+ assistantCompletionIdleWatchArmed = false;
3190
+ assistantCompletionLastActivityDetails = void 0;
3191
+ clearAssistantCompletionIdleTimer();
3192
+ return;
3193
+ }
2962
3194
  const idleMs = Math.max(0, Date.now() - assistantCompletionLastActivityAt);
2963
3195
  if (idleMs < turnAssistantCompletionIdleTimeoutMs) {
2964
3196
  scheduleAssistantCompletionIdleWatch();
@@ -3156,2406 +3388,23 @@ function createCodexAttemptTurnWatchController(params) {
3156
3388
  scheduleProgressWatches();
3157
3389
  },
3158
3390
  noteNotificationReceived: (method, options) => {
3159
- completionLastActivityAt = Date.now();
3391
+ const now = Date.now();
3392
+ completionLastActivityAt = Math.max(completionLastActivityAt, Math.min(now, options?.receivedAtMs ?? now));
3160
3393
  completionLastActivityReason = `notification:${method}`;
3161
3394
  if (options?.details !== void 0) completionLastActivityDetails = options.details;
3162
- if (options?.attemptProgress) recordAttemptProgress(completionLastActivityReason, options);
3163
- },
3164
- extendAttemptIdleWatch: (timeoutMs) => {
3165
- attemptIdleTimeoutOverrideMs = resolveWatchTimeoutMs(timeoutMs);
3166
- scheduleAttemptIdleWatch();
3167
- },
3168
- scheduleProgressWatches,
3169
- clearCompletionIdleTimer,
3170
- clearAssistantCompletionIdleTimer,
3171
- clearTerminalIdleTimer,
3172
- clearAttemptIdleTimer,
3173
- clearAllTimers
3174
- };
3175
- }
3176
- /** Hard cap for per-call Codex dynamic tool timeout overrides. */
3177
- const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
3178
- const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
3179
- /** Timeout for message-delivery dynamic tool calls. */
3180
- const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 12e4;
3181
- const LOG_FIELD_MAX_LENGTH = 160;
3182
- function normalizeLogField(value) {
3183
- if (typeof value !== "string") return;
3184
- const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
3185
- if (!normalized) return;
3186
- return normalized.length > LOG_FIELD_MAX_LENGTH ? `${normalized.slice(0, LOG_FIELD_MAX_LENGTH - 3)}...` : normalized;
3187
- }
3188
- function readNumericTimeoutMs(value) {
3189
- if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
3190
- if (typeof value === "string") {
3191
- const parsed = parseStrictNonNegativeInteger(value);
3192
- if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
3193
- }
3194
- }
3195
- function formatDynamicToolTimeoutDetails(params) {
3196
- const tool = normalizeLogField(params.call.tool) ?? "unknown";
3197
- const baseMeta = {
3198
- tool: params.call.tool,
3199
- toolCallId: params.call.callId,
3200
- threadId: params.call.threadId,
3201
- turnId: params.call.turnId,
3202
- timeoutMs: params.timeoutMs,
3203
- timeoutKind: "codex_dynamic_tool_rpc"
3204
- };
3205
- if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
3206
- responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
3207
- consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
3208
- meta: baseMeta
3209
- };
3210
- const action = normalizeLogField(params.call.arguments.action);
3211
- const sessionId = normalizeLogField(params.call.arguments.sessionId);
3212
- const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
3213
- const actionPart = action ? ` action=${action}` : "";
3214
- const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
3215
- const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
3216
- const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
3217
- const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
3218
- return {
3219
- responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
3220
- consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
3221
- meta: {
3222
- ...baseMeta,
3223
- processAction: action,
3224
- processSessionId: sessionId,
3225
- processRequestedTimeoutMs: requestedTimeoutMs
3226
- }
3227
- };
3228
- }
3229
- /**
3230
- * Runs a dynamic tool call with run-abort and per-call timeout handling,
3231
- * returning a Codex protocol response instead of throwing.
3232
- */
3233
- async function handleDynamicToolCallWithTimeout(params) {
3234
- let didNotifyAgentToolResult = false;
3235
- const notifyAgentToolResult = (event) => {
3236
- if (didNotifyAgentToolResult) return;
3237
- didNotifyAgentToolResult = true;
3238
- try {
3239
- params.onAgentToolResult?.(event);
3240
- } catch (error) {
3241
- embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
3242
- }
3243
- };
3244
- const notifyFailedToolResult = (message) => {
3245
- notifyAgentToolResult({
3246
- toolName: params.call.tool,
3247
- result: {
3248
- content: [{
3249
- type: "text",
3250
- text: message
3251
- }],
3252
- details: {
3253
- status: "failed",
3254
- error: message
3255
- }
3256
- },
3257
- isError: true
3258
- });
3259
- };
3260
- if (params.signal.aborted) {
3261
- const message = "OpenClaw dynamic tool call aborted before execution.";
3262
- params.onFallbackSelected?.();
3263
- notifyFailedToolResult(message);
3264
- return failedDynamicToolResponse(message);
3265
- }
3266
- const controller = new AbortController();
3267
- let timeout;
3268
- let timedOut = false;
3269
- let resolveAbort;
3270
- const abortFromRun = () => {
3271
- const message = "OpenClaw dynamic tool call aborted.";
3272
- params.onFallbackSelected?.();
3273
- controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
3274
- notifyFailedToolResult(message);
3275
- resolveAbort?.(failedDynamicToolResponse(message, { sideEffectEvidence: true }));
3276
- };
3277
- const abortPromise = new Promise((resolve) => {
3278
- resolveAbort = resolve;
3279
- });
3280
- const timeoutPromise = new Promise((resolve) => {
3281
- const timeoutMs = clampDynamicToolTimeoutMs(params.timeoutMs);
3282
- timeout = setTimeout(() => {
3283
- timedOut = true;
3284
- const timeoutDetails = formatDynamicToolTimeoutDetails({
3285
- call: params.call,
3286
- timeoutMs
3287
- });
3288
- params.onFallbackSelected?.();
3289
- controller.abort(new Error(timeoutDetails.responseMessage));
3290
- params.onTimeout?.();
3291
- embeddedAgentLog.warn("codex dynamic tool call timed out", {
3292
- ...timeoutDetails.meta,
3293
- consoleMessage: timeoutDetails.consoleMessage
3294
- });
3295
- notifyFailedToolResult(timeoutDetails.responseMessage);
3296
- resolve(failedDynamicToolResponse(timeoutDetails.responseMessage, { sideEffectEvidence: true }));
3297
- }, timeoutMs);
3298
- timeout.unref?.();
3299
- });
3300
- try {
3301
- params.signal.addEventListener("abort", abortFromRun, { once: true });
3302
- if (params.signal.aborted) abortFromRun();
3303
- const response = await Promise.race([
3304
- params.toolBridge.handleToolCall(params.call, {
3305
- signal: controller.signal,
3306
- onAgentToolResult: notifyAgentToolResult,
3307
- toolCallOrdinal: params.toolCallOrdinal
3308
- }),
3309
- abortPromise,
3310
- timeoutPromise
3311
- ]);
3312
- if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response));
3313
- return response;
3314
- } catch (error) {
3315
- const message = error instanceof Error ? error.message : String(error);
3316
- notifyFailedToolResult(message);
3317
- return failedDynamicToolResponse(message, { sideEffectEvidence: true });
3318
- } finally {
3319
- if (timeout) clearTimeout(timeout);
3320
- params.signal.removeEventListener("abort", abortFromRun);
3321
- resolveAbort = void 0;
3322
- if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
3323
- }
3324
- }
3325
- function readDynamicToolResponseText(response) {
3326
- return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
3327
- }
3328
- function failedDynamicToolResponse(message, options) {
3329
- const response = {
3330
- contentItems: [{
3331
- type: "inputText",
3332
- text: message
3333
- }],
3334
- success: false
3335
- };
3336
- Object.defineProperty(response, "diagnosticTerminalType", {
3337
- configurable: true,
3338
- enumerable: false,
3339
- value: "error"
3340
- });
3341
- if (options?.sideEffectEvidence === true) Object.defineProperty(response, "sideEffectEvidence", {
3342
- configurable: true,
3343
- enumerable: false,
3344
- value: true
3345
- });
3346
- return response;
3347
- }
3348
- /** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
3349
- function toCodexDynamicToolProtocolResponse(response) {
3350
- return {
3351
- contentItems: response.contentItems,
3352
- success: response.success
3353
- };
3354
- }
3355
- /** Adds async-started progress details when a tool result continues out of band. */
3356
- function toCodexDynamicToolProgressResponse(response, protocolResponse) {
3357
- if (response.asyncStarted !== true) return protocolResponse;
3358
- return {
3359
- ...protocolResponse,
3360
- details: {
3361
- async: true,
3362
- status: "started"
3363
- }
3364
- };
3365
- }
3366
- /** Decides whether a terminal dynamic tool response can release the Codex turn. */
3367
- function shouldReleaseTurnAfterTerminalDynamicTool(state) {
3368
- return !state.completed && !state.aborted && state.responseSuccess && !state.currentTurnHadNonTerminalDynamicToolResult && state.activeAppServerTurnRequests === 0 && state.activeTurnItemIdsCount === 0 && state.pendingOpenClawDynamicToolCompletionIdsCount === 0;
3369
- }
3370
- /** Returns true when a non-async result should block terminal-release shortcuts. */
3371
- function shouldBlockTerminalReleaseForNonTerminalDynamicToolResult(response) {
3372
- return response.asyncStarted !== true;
3373
- }
3374
- /** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
3375
- function resolveTerminalDynamicToolBatchAction(state) {
3376
- if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
3377
- if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
3378
- if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
3379
- return "idle";
3380
- }
3381
- /** Returns true for diagnostic events that terminate a dynamic tool call. */
3382
- function isDynamicToolTerminalDiagnosticEvent(event) {
3383
- return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
3384
- }
3385
- /** Matches terminal diagnostics to a specific dynamic tool call id/name. */
3386
- function isMatchingDynamicToolTerminalDiagnostic(params) {
3387
- if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
3388
- if (params.runId !== void 0) return params.event.runId === params.runId;
3389
- if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
3390
- if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
3391
- return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
3392
- }
3393
- /** Checks pending diagnostics for a terminal event matching a tool call. */
3394
- function hasPendingDynamicToolTerminalDiagnostic(params) {
3395
- return hasPendingInternalDiagnosticEvent((event) => {
3396
- if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
3397
- return isMatchingDynamicToolTerminalDiagnostic({
3398
- event,
3399
- call: params.call,
3400
- runId: params.runId,
3401
- sessionId: params.sessionId,
3402
- sessionKey: params.sessionKey
3403
- });
3404
- });
3405
- }
3406
- /** Resolves per-tool timeout, applying media/message defaults and hard caps. */
3407
- function resolveDynamicToolCallTimeoutMs(params) {
3408
- return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? 9e4);
3409
- }
3410
- function readDynamicToolCallTimeoutMs(value) {
3411
- if (!isJsonObject(value)) return;
3412
- return readPositiveFiniteTimeoutMs(value.timeoutMs);
3413
- }
3414
- function readConfiguredDynamicToolTimeoutMs(toolName, config) {
3415
- if (toolName === "image_generate") {
3416
- const imageGenerationModel = config?.agents?.defaults?.imageGenerationModel;
3417
- if (!imageGenerationModel || typeof imageGenerationModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
3418
- return readPositiveFiniteTimeoutMs(imageGenerationModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
3419
- }
3420
- if (toolName === "image") return readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds) ?? 6e4;
3421
- if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
3422
- }
3423
- function readTimeoutSecondsAsMs(value) {
3424
- const seconds = readPositiveFiniteTimeoutMs(value);
3425
- return seconds === void 0 ? void 0 : seconds * 1e3;
3426
- }
3427
- function readPositiveFiniteTimeoutMs(value) {
3428
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
3429
- }
3430
- function clampDynamicToolTimeoutMs(timeoutMs) {
3431
- return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
3432
- }
3433
- //#endregion
3434
- //#region extensions/codex/src/app-server/local-runtime-attribution.ts
3435
- const OPENAI_PROVIDER_ID = "openai";
3436
- const OPENAI_RESPONSES_API = "openai-responses";
3437
- const OPENAI_CODEX_RESPONSES_API = "openai-chatgpt-responses";
3438
- function normalizeRuntimeId(value) {
3439
- return value?.trim().toLowerCase() ?? "";
3440
- }
3441
- /** Maps local Codex runtime plans onto the provider/api pair exposed to event projection. */
3442
- function resolveCodexLocalRuntimeAttribution(params) {
3443
- const authProfileProvider = normalizeRuntimeId(params.runtimePlan?.auth?.authProfileProviderForAuth);
3444
- if (normalizeRuntimeId(params.runtimePlan?.observability.harnessId) === "codex" && authProfileProvider !== OPENAI_PROVIDER_ID && normalizeRuntimeId(params.model.provider) === OPENAI_PROVIDER_ID && normalizeRuntimeId(params.model.api) === OPENAI_RESPONSES_API) return {
3445
- provider: OPENAI_PROVIDER_ID,
3446
- api: OPENAI_CODEX_RESPONSES_API
3447
- };
3448
- return {
3449
- provider: params.provider,
3450
- api: params.model.api
3451
- };
3452
- }
3453
- //#endregion
3454
- //#region extensions/codex/src/app-server/tool-progress-normalization.ts
3455
- /**
3456
- * Normalizes and sanitizes Codex dynamic-tool progress payloads before they are
3457
- * emitted into OpenClaw events or logs.
3458
- */
3459
- /** Maps OpenClaw tool-progress config to the mode used by Codex progress metadata. */
3460
- function resolveCodexToolProgressDetailMode(value) {
3461
- return value === "raw" ? "raw" : "explain";
3462
- }
3463
- /** Recursively redacts sensitive strings and handles circular values in event payloads. */
3464
- function sanitizeCodexAgentEventValue(value, seen = /* @__PURE__ */ new WeakSet()) {
3465
- if (typeof value === "string") return redactToolPayloadText(value);
3466
- if (Array.isArray(value)) {
3467
- if (seen.has(value)) return "[Circular]";
3468
- seen.add(value);
3469
- return value.map((entry) => sanitizeCodexAgentEventValue(entry, seen));
3470
- }
3471
- if (value && typeof value === "object") {
3472
- if (seen.has(value)) return "[Circular]";
3473
- seen.add(value);
3474
- const out = {};
3475
- for (const [key, child] of Object.entries(value)) out[key] = typeof child === "string" ? redactSensitiveFieldValue(key, child) : sanitizeCodexAgentEventValue(child, seen);
3476
- return out;
3477
- }
3478
- return value;
3479
- }
3480
- /** Sanitizes a record-shaped Codex agent event payload. */
3481
- function sanitizeCodexAgentEventRecord(value) {
3482
- return sanitizeCodexAgentEventValue(value);
3483
- }
3484
- /** Sanitizes dynamic-tool arguments before diagnostic/event emission. */
3485
- function sanitizeCodexToolArguments(value) {
3486
- if (!isJsonObject(value)) return;
3487
- return sanitizeCodexAgentEventRecord(value);
3488
- }
3489
- /** Sanitizes a Codex dynamic-tool response before diagnostic/event emission. */
3490
- function sanitizeCodexToolResponse(response) {
3491
- return sanitizeCodexAgentEventRecord(response);
3492
- }
3493
- /** Infers compact human-readable tool metadata from Codex dynamic-tool arguments. */
3494
- function inferCodexDynamicToolMeta(call, detailMode) {
3495
- return inferToolMetaFromArgs(call.tool, call.arguments, { detailMode });
3496
- }
3497
- //#endregion
3498
- //#region extensions/codex/src/app-server/transcript-mirror.ts
3499
- const MIRROR_IDENTITY_META_KEY = "mirrorIdentity";
3500
- function buildSenderLabel(params) {
3501
- const label = params.senderName ?? params.senderUsername ?? params.senderE164 ?? params.senderId;
3502
- if (!label) return;
3503
- if (!params.senderId || label.includes(params.senderId)) return label;
3504
- return `${label} (${params.senderId})`;
3505
- }
3506
- function buildCodexUserPromptMessageFromPrepared(params, preparedUserMessage) {
3507
- const senderId = normalizeOptionalString(params.senderId);
3508
- const senderName = normalizeOptionalString(params.senderName);
3509
- const senderUsername = normalizeOptionalString(params.senderUsername);
3510
- const senderE164 = normalizeOptionalString(params.senderE164);
3511
- const senderLabel = buildSenderLabel({
3512
- senderId,
3513
- senderName,
3514
- senderUsername,
3515
- senderE164
3516
- });
3517
- const sourceChannel = normalizeOptionalString(params.inputProvenance?.sourceChannel ?? params.messageChannel ?? params.messageProvider);
3518
- if (preparedUserMessage) return {
3519
- role: "user",
3520
- timestamp: Date.now(),
3521
- ...params.inputProvenance ? { provenance: params.inputProvenance } : {},
3522
- ...sourceChannel ? { sourceChannel } : {},
3523
- ...senderId ? { senderId } : {},
3524
- ...senderName ? { senderName } : {},
3525
- ...senderUsername ? { senderUsername } : {},
3526
- ...senderE164 ? { senderE164 } : {},
3527
- ...senderLabel ? { senderLabel } : {},
3528
- ...preparedUserMessage
3529
- };
3530
- return {
3531
- role: "user",
3532
- content: params.prompt,
3533
- timestamp: Date.now(),
3534
- ...params.inputProvenance ? { provenance: params.inputProvenance } : {},
3535
- ...sourceChannel ? { sourceChannel } : {},
3536
- ...senderId ? { senderId } : {},
3537
- ...senderName ? { senderName } : {},
3538
- ...senderUsername ? { senderUsername } : {},
3539
- ...senderE164 ? { senderE164 } : {},
3540
- ...senderLabel ? { senderLabel } : {}
3541
- };
3542
- }
3543
- function buildCodexUserPromptMessage(params) {
3544
- return buildCodexUserPromptMessageFromPrepared(params, params.userTurnTranscriptRecorder?.message);
3545
- }
3546
- async function buildResolvedCodexUserPromptMessage(params) {
3547
- return buildCodexUserPromptMessageFromPrepared(params, await params.userTurnTranscriptRecorder?.resolveMessage() ?? params.userTurnTranscriptRecorder?.message);
3548
- }
3549
- async function mirrorTranscriptBestEffort(params) {
3550
- try {
3551
- const messages = await resolveFinalCodexMirrorMessages({
3552
- params: params.params,
3553
- messagesSnapshot: params.result.messagesSnapshot,
3554
- turnId: params.turnId
3555
- });
3556
- const mirrorResult = await mirrorCodexAppServerTranscript({
3557
- sessionFile: params.params.sessionFile,
3558
- agentId: params.agentId,
3559
- sessionKey: params.sessionKey,
3560
- sessionId: params.params.sessionId,
3561
- cwd: params.cwd,
3562
- messages,
3563
- idempotencyScope: `codex-app-server:${params.threadId}`,
3564
- config: params.params.config
3565
- });
3566
- for (const message of mirrorResult.userMessagesPresent) params.notifyUserMessagePersisted(message);
3567
- } catch (error) {
3568
- embeddedAgentLog.warn("failed to mirror codex app-server transcript", { error });
3569
- }
3570
- }
3571
- async function resolveFinalCodexMirrorMessages(params) {
3572
- if (params.params.suppressNextUserMessagePersistence || !params.params.userTurnTranscriptRecorder) return params.messagesSnapshot;
3573
- const resolvedPrompt = attachCodexMirrorIdentity(await buildResolvedCodexUserPromptMessage(params.params), `${params.turnId}:prompt`);
3574
- const firstUserIndex = params.messagesSnapshot.findIndex((message) => message.role === "user");
3575
- if (firstUserIndex === -1) return [resolvedPrompt, ...params.messagesSnapshot];
3576
- const messages = params.messagesSnapshot.slice();
3577
- messages[firstUserIndex] = resolvedPrompt;
3578
- return messages;
3579
- }
3580
- function createCodexAppServerUserMessagePersistenceNotifier(runParams) {
3581
- let notified = false;
3582
- return (message) => {
3583
- if (notified) return;
3584
- notified = true;
3585
- runParams.userTurnTranscriptRecorder?.markRuntimePersisted(message);
3586
- try {
3587
- runParams.onUserMessagePersisted?.(message);
3588
- } catch (error) {
3589
- embeddedAgentLog.warn("codex app-server user persistence notification failed", { error: formatErrorMessage(error) });
3590
- }
3591
- };
3592
- }
3593
- async function mirrorPromptAtTurnStartBestEffort(params) {
3594
- if (params.params.suppressNextUserMessagePersistence) return;
3595
- try {
3596
- const mirrorPromise = (async () => {
3597
- const userPromptMessage = attachCodexMirrorIdentity(await buildResolvedCodexUserPromptMessage(params.params), `${params.turnId}:prompt`);
3598
- const mirrorResult = await mirrorCodexAppServerTranscript({
3599
- sessionFile: params.params.sessionFile,
3600
- agentId: params.agentId,
3601
- sessionKey: params.sessionKey,
3602
- sessionId: params.params.sessionId,
3603
- cwd: params.cwd,
3604
- messages: [userPromptMessage],
3605
- idempotencyScope: `codex-app-server:${params.threadId}`,
3606
- config: params.params.config
3607
- });
3608
- for (const message of mirrorResult.userMessagesPresent) params.notifyUserMessagePersisted(message);
3609
- })();
3610
- params.params.userTurnTranscriptRecorder?.markRuntimePersistencePending(mirrorPromise);
3611
- await mirrorPromise;
3612
- } catch (error) {
3613
- embeddedAgentLog.warn("failed to mirror codex app-server prompt at turn start", { error });
3614
- }
3615
- }
3616
- /**
3617
- * Tag a message with a stable logical identity for mirror dedupe. Callers
3618
- * should use a value that is invariant for the same logical message across
3619
- * re-emits (e.g. `${turnId}:prompt`, `${turnId}:assistant`) but distinct
3620
- * for genuinely-distinct messages (different turns, different kinds). When
3621
- * present this identity replaces the role/content fingerprint in the
3622
- * idempotency key, so the dedupe survives caller-scope rotation without
3623
- * collapsing distinct same-content turns.
3624
- */
3625
- function attachCodexMirrorIdentity(message, identity) {
3626
- const record = message;
3627
- const existing = record["__openclaw"];
3628
- const baseMeta = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
3629
- return {
3630
- ...record,
3631
- __openclaw: {
3632
- ...baseMeta,
3633
- [MIRROR_IDENTITY_META_KEY]: identity
3634
- }
3635
- };
3636
- }
3637
- function readMirrorIdentity(message) {
3638
- const meta = message["__openclaw"];
3639
- if (!meta || typeof meta !== "object" || Array.isArray(meta)) return;
3640
- const id = meta[MIRROR_IDENTITY_META_KEY];
3641
- return typeof id === "string" && id.length > 0 ? id : void 0;
3642
- }
3643
- function fingerprintMirrorMessageContent(message) {
3644
- const payload = JSON.stringify({
3645
- role: message.role,
3646
- content: message.content
3647
- });
3648
- return createHash("sha256").update(payload).digest("hex").slice(0, 16);
3649
- }
3650
- function buildMirrorDedupeIdentity(message) {
3651
- const explicit = readMirrorIdentity(message);
3652
- if (explicit) return explicit;
3653
- return `${message.role}:${fingerprintMirrorMessageContent(message)}`;
3654
- }
3655
- async function mirrorCodexAppServerTranscript(params) {
3656
- const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
3657
- if (messages.length === 0) return { userMessagesPresent: [] };
3658
- const transcriptTarget = resolveCodexMirrorTranscriptTarget(params);
3659
- const { appendedUpdates, userMessagesPresent } = await withSessionTranscriptWriteLock({
3660
- ...transcriptTarget,
3661
- config: params.config
3662
- }, async (transcript) => {
3663
- const nextAppendedUpdates = [];
3664
- const nextUserMessagesPresent = [];
3665
- const mirrorState = readTranscriptMirrorState(await transcript.readEvents());
3666
- let nextMessageSeq = mirrorState.messageCount;
3667
- for (const message of messages) {
3668
- const dedupeIdentity = buildMirrorDedupeIdentity(message);
3669
- const idempotencyKey = params.idempotencyScope ? `${params.idempotencyScope}:${dedupeIdentity}` : void 0;
3670
- const transcriptMessage = {
3671
- ...message,
3672
- ...idempotencyKey ? { idempotencyKey } : {}
3673
- };
3674
- if (idempotencyKey && mirrorState.idempotencyKeys.has(idempotencyKey)) {
3675
- const persistedUserMessage = mirrorState.userMessagesByIdempotencyKey.get(idempotencyKey);
3676
- if (persistedUserMessage) nextUserMessagesPresent.push(persistedUserMessage);
3677
- continue;
3678
- }
3679
- const nextMessage = runAgentHarnessBeforeMessageWriteHook({
3680
- message: transcriptMessage,
3681
- agentId: params.agentId,
3682
- sessionKey: params.sessionKey
3683
- });
3684
- if (!nextMessage) continue;
3685
- const messageToAppend = idempotencyKey ? {
3686
- ...nextMessage,
3687
- idempotencyKey
3688
- } : nextMessage;
3689
- const appended = await transcript.appendMessage({
3690
- message: messageToAppend,
3691
- idempotencyLookup: idempotencyKey ? "caller-checked" : "scan",
3692
- cwd: params.cwd
3693
- });
3694
- if (!appended) continue;
3695
- const { messageId, message: appendedMessage } = appended;
3696
- if (appendedMessage.role === "user") {
3697
- nextUserMessagesPresent.push(appendedMessage);
3698
- if (idempotencyKey) mirrorState.userMessagesByIdempotencyKey.set(idempotencyKey, appendedMessage);
3699
- }
3700
- nextMessageSeq += 1;
3701
- nextAppendedUpdates.push({
3702
- messageId,
3703
- message: appendedMessage,
3704
- messageSeq: nextMessageSeq
3705
- });
3706
- if (idempotencyKey) mirrorState.idempotencyKeys.add(idempotencyKey);
3707
- }
3708
- return {
3709
- appendedUpdates: nextAppendedUpdates,
3710
- userMessagesPresent: nextUserMessagesPresent
3711
- };
3712
- });
3713
- for (const update of appendedUpdates) await publishSessionTranscriptUpdateByIdentity({
3714
- ...transcriptTarget,
3715
- update: {
3716
- ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3717
- ...params.agentId ? { agentId: params.agentId } : {},
3718
- message: update.message,
3719
- messageId: update.messageId,
3720
- messageSeq: update.messageSeq
3721
- }
3722
- });
3723
- return { userMessagesPresent };
3724
- }
3725
- function resolveCodexMirrorTranscriptTarget(params) {
3726
- return {
3727
- ...params.agentId ? { agentId: params.agentId } : {},
3728
- sessionFile: params.sessionFile,
3729
- sessionId: params.sessionId,
3730
- sessionKey: params.sessionKey ?? ""
3731
- };
3732
- }
3733
- function readTranscriptMirrorState(events) {
3734
- const idempotencyKeys = /* @__PURE__ */ new Set();
3735
- const userMessagesByIdempotencyKey = /* @__PURE__ */ new Map();
3736
- let messageCount = 0;
3737
- for (const event of events) {
3738
- if (!event || typeof event !== "object" || Array.isArray(event)) continue;
3739
- const parsed = event;
3740
- if (parsed.type === "message") messageCount += 1;
3741
- if (typeof parsed.message?.idempotencyKey === "string") {
3742
- idempotencyKeys.add(parsed.message.idempotencyKey);
3743
- if (parsed.message.role === "user") userMessagesByIdempotencyKey.set(parsed.message.idempotencyKey, parsed.message);
3744
- }
3745
- }
3746
- return {
3747
- idempotencyKeys,
3748
- messageCount,
3749
- userMessagesByIdempotencyKey
3750
- };
3751
- }
3752
- //#endregion
3753
- //#region extensions/codex/src/app-server/event-projector.ts
3754
- const ZERO_USAGE = {
3755
- input: 0,
3756
- output: 0,
3757
- cacheRead: 0,
3758
- cacheWrite: 0,
3759
- totalTokens: 0,
3760
- cost: {
3761
- input: 0,
3762
- output: 0,
3763
- cacheRead: 0,
3764
- cacheWrite: 0,
3765
- total: 0
3766
- }
3767
- };
3768
- const CURRENT_TOKEN_USAGE_KEYS = [
3769
- "last",
3770
- "current",
3771
- "lastCall",
3772
- "lastCallUsage",
3773
- "lastTokenUsage",
3774
- "last_token_usage"
3775
- ];
3776
- const CODEX_PROMPT_TOTAL_INPUT_KEYS = [
3777
- "inputTokens",
3778
- "input_tokens",
3779
- "promptTokens",
3780
- "prompt_tokens"
3781
- ];
3782
- const MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM = 20;
3783
- const TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS = 12e3;
3784
- const MISSING_TOOL_RESULT_ERROR = "OpenClaw recorded a native Codex tool.call without a matching tool.result before the turn completed.";
3785
- const GENERATED_IMAGE_MEDIA_SUBDIR = "tool-image-generation";
3786
- const BYTES_PER_MB = 1024 * 1024;
3787
- const DEFAULT_GENERATED_IMAGE_MAX_BYTES = 6 * BYTES_PER_MB;
3788
- const TRANSCRIPT_PROGRESS_SUPPRESSED_TOOL_NAMES = new Set([
3789
- "message",
3790
- "messages",
3791
- "reply",
3792
- "send",
3793
- "reaction",
3794
- "react",
3795
- "typing"
3796
- ]);
3797
- function shouldEmitTranscriptToolProgress(toolName, _args) {
3798
- const normalized = typeof toolName === "string" ? toolName.trim().toLowerCase() : "";
3799
- return Boolean(normalized && !TRANSCRIPT_PROGRESS_SUPPRESSED_TOOL_NAMES.has(normalized));
3800
- }
3801
- var CodexAppServerEventProjector = class {
3802
- constructor(params, threadId, turnId, options = {}) {
3803
- this.params = params;
3804
- this.threadId = threadId;
3805
- this.turnId = turnId;
3806
- this.options = options;
3807
- this.assistantTextByItem = /* @__PURE__ */ new Map();
3808
- this.assistantItemOrder = [];
3809
- this.assistantPhaseByItem = /* @__PURE__ */ new Map();
3810
- this.lastCommentaryProgressTextByItem = /* @__PURE__ */ new Map();
3811
- this.pendingRawCommentaryEchoes = 0;
3812
- this.reasoningTextByGroup = /* @__PURE__ */ new Map();
3813
- this.reasoningItemOrder = /* @__PURE__ */ new Map();
3814
- this.planTextByItem = /* @__PURE__ */ new Map();
3815
- this.activeItemIds = /* @__PURE__ */ new Set();
3816
- this.completedItemIds = /* @__PURE__ */ new Set();
3817
- this.activeCompactionItemIds = /* @__PURE__ */ new Set();
3818
- this.toolProgressTexts = /* @__PURE__ */ new Set();
3819
- this.toolResultSummaryItemIds = /* @__PURE__ */ new Set();
3820
- this.toolResultOutputItemIds = /* @__PURE__ */ new Set();
3821
- this.toolResultOutputStreamedItemIds = /* @__PURE__ */ new Set();
3822
- this.transcriptToolProgressSuppressedIds = /* @__PURE__ */ new Set();
3823
- this.toolTranscriptArgumentsById = /* @__PURE__ */ new Map();
3824
- this.toolResultOutputDeltaState = /* @__PURE__ */ new Map();
3825
- this.toolResultOutputTextByItem = /* @__PURE__ */ new Map();
3826
- this.toolMetas = /* @__PURE__ */ new Map();
3827
- this.terminalPresentationClearedItemIds = /* @__PURE__ */ new Set();
3828
- this.nativeToolOutcomeOrdinals = /* @__PURE__ */ new Map();
3829
- this.sideEffectingToolItemIds = /* @__PURE__ */ new Set();
3830
- this.sideEffectingDynamicToolCallIds = /* @__PURE__ */ new Set();
3831
- this.toolTranscriptMessages = [];
3832
- this.toolTranscriptCallIds = /* @__PURE__ */ new Set();
3833
- this.toolTranscriptResultIds = /* @__PURE__ */ new Set();
3834
- this.toolTranscriptNamesById = /* @__PURE__ */ new Map();
3835
- this.toolTrajectoryCallIds = /* @__PURE__ */ new Set();
3836
- this.toolTrajectoryResultIds = /* @__PURE__ */ new Set();
3837
- this.toolTrajectoryNamesById = /* @__PURE__ */ new Map();
3838
- this.toolTrajectoryItemsById = /* @__PURE__ */ new Map();
3839
- this.transcriptToolProgressCallIds = /* @__PURE__ */ new Set();
3840
- this.nativeGeneratedMediaItemIds = /* @__PURE__ */ new Set();
3841
- this.nativeGeneratedMediaUrlsByItemId = /* @__PURE__ */ new Map();
3842
- this.diagnosticToolStartedAtByItem = /* @__PURE__ */ new Map();
3843
- this.afterToolCallObservedItemIds = /* @__PURE__ */ new Set();
3844
- this.assistantStarted = false;
3845
- this.reasoningStarted = false;
3846
- this.reasoningEnded = false;
3847
- this.streamedPartialAssistantItemReplaceable = false;
3848
- this.promptErrorSource = null;
3849
- this.synthesizedMissingToolResultError = null;
3850
- this.aborted = false;
3851
- this.guardianReviewCount = 0;
3852
- this.completedCompactionCount = 0;
3853
- }
3854
- getCompletedTurnStatus() {
3855
- return this.completedTurn?.status;
3856
- }
3857
- hasCompletedTerminalAssistantText() {
3858
- const finalItem = this.resolveFinalAssistantTextItem();
3859
- return finalItem !== void 0 && this.completedItemIds.has(finalItem.itemId);
3860
- }
3861
- /** Resolves the shared model-order position for a native tool item. */
3862
- recordNativeToolOutcome(item) {
3863
- if (!item || this.nativeToolOutcomeOrdinals.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
3864
- const ordinal = this.params.allocateToolOutcomeOrdinal?.(item.id);
3865
- if (ordinal !== void 0) this.nativeToolOutcomeOrdinals.set(item.id, ordinal);
3866
- }
3867
- async handleNotification(notification) {
3868
- const params = isJsonObject(notification.params) ? notification.params : void 0;
3869
- if (!params) return;
3870
- if (notification.method === "account/rateLimits/updated") {
3871
- this.latestRateLimits = params;
3872
- rememberCodexRateLimits(params);
3873
- return;
3874
- }
3875
- if (isHookNotificationMethod(notification.method)) {
3876
- if (!this.isHookNotificationForCurrentThread(params)) return;
3877
- } else if (!this.isNotificationForTurn(params)) return;
3878
- switch (notification.method) {
3879
- case "item/agentMessage/delta":
3880
- await this.handleAssistantDelta(params);
3881
- break;
3882
- case "item/reasoning/summaryTextDelta":
3883
- case "item/reasoning/textDelta":
3884
- await this.handleReasoningDelta(notification.method, params);
3885
- break;
3886
- case "item/plan/delta":
3887
- this.handlePlanDelta(params);
3888
- break;
3889
- case "turn/plan/updated":
3890
- this.handleTurnPlanUpdated(params);
3891
- break;
3892
- case "item/started":
3893
- await this.handleItemStarted(params);
3894
- break;
3895
- case "item/completed":
3896
- await this.handleItemCompleted(params);
3897
- break;
3898
- case "item/commandExecution/outputDelta":
3899
- this.handleOutputDelta(params, "bash");
3900
- break;
3901
- case "item/fileChange/outputDelta":
3902
- this.handleOutputDelta(params, "apply_patch");
3903
- break;
3904
- case "item/autoApprovalReview/started":
3905
- case "item/autoApprovalReview/completed":
3906
- this.handleGuardianReviewNotification(notification.method, params);
3907
- break;
3908
- case "hook/started":
3909
- case "hook/completed":
3910
- this.handleHookNotification(notification.method, params);
3911
- break;
3912
- case "thread/tokenUsage/updated":
3913
- this.handleTokenUsage(params);
3914
- break;
3915
- case "turn/completed":
3916
- await this.handleTurnCompleted(params);
3917
- break;
3918
- case "rawResponseItem/completed":
3919
- await this.handleRawResponseItemCompleted(params);
3920
- break;
3921
- case "error":
3922
- if (readBooleanAlias(params, ["willRetry", "will_retry"]) === true) break;
3923
- this.promptError = this.formatCodexErrorMessage(params) ?? "codex app-server error";
3924
- this.promptErrorSource = "prompt";
3925
- break;
3926
- default: break;
3927
- }
3928
- }
3929
- buildResult(toolTelemetry, options) {
3930
- const assistantTexts = this.collectAssistantTexts();
3931
- const reasoningText = collectReasoningTextValues(this.reasoningTextByGroup, this.reasoningItemOrder).join("\n\n");
3932
- const planText = collectTextValues(this.planTextByItem).join("\n\n");
3933
- const hasAssistantItemText = this.hasAssistantItemTextForSynthesis();
3934
- const legacyFailClosed = !this.completedTurn || this.completedTurn.status !== "completed" || hasAssistantItemText;
3935
- const hasDeliverableAssistantOnCompletedTurn = this.completedTurn?.status === "completed" && assistantTexts.some((text) => text.trim().length > 0);
3936
- this.synthesizeMissingToolResults({
3937
- synthesize: legacyFailClosed,
3938
- recordPromptError: legacyFailClosed && !hasDeliverableAssistantOnCompletedTurn
3939
- });
3940
- const lastAssistant = assistantTexts.length > 0 ? this.createAssistantMessage(assistantTexts.join("\n\n")) : void 0;
3941
- const currentAttemptAssistant = this.createCurrentAttemptAssistantMessage();
3942
- const turnId = this.turnId;
3943
- const messagesSnapshot = this.params.suppressNextUserMessagePersistence ? [] : [attachCodexMirrorIdentity(buildCodexUserPromptMessage(this.params), `${turnId}:prompt`)];
3944
- if (reasoningText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex reasoning", reasoningText), `${turnId}:reasoning`));
3945
- if (planText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex plan", planText), `${turnId}:plan`));
3946
- messagesSnapshot.push(...this.toolTranscriptMessages);
3947
- if (lastAssistant) messagesSnapshot.push(attachCodexMirrorIdentity(lastAssistant, `${turnId}:assistant`));
3948
- const turnFailed = this.completedTurn?.status === "failed";
3949
- const promptError = this.promptError ?? this.synthesizedMissingToolResultError ?? (turnFailed ? this.completedTurn?.error?.message ?? "codex app-server turn failed" : null);
3950
- const agentHarnessResultClassification = classifyAgentHarnessTerminalOutcome({
3951
- assistantTexts,
3952
- reasoningText,
3953
- planText,
3954
- promptError,
3955
- turnCompleted: Boolean(this.completedTurn)
3956
- });
3957
- const toolMetas = [...this.toolMetas.values()];
3958
- const hadPotentialSideEffects = toolTelemetry.didSendViaMessagingTool || (toolTelemetry.successfulCronAdds ?? 0) > 0 || this.nativeGeneratedMediaItemIds.size > 0 || this.sideEffectingToolItemIds.size > 0 || this.sideEffectingDynamicToolCallIds.size > 0;
3959
- return {
3960
- aborted: this.aborted,
3961
- externalAbort: false,
3962
- timedOut: false,
3963
- idleTimedOut: false,
3964
- timedOutDuringCompaction: false,
3965
- timedOutDuringToolExecution: false,
3966
- promptError,
3967
- promptErrorSource: promptError ? this.promptErrorSource || "prompt" : null,
3968
- sessionIdUsed: this.params.sessionId,
3969
- ...agentHarnessResultClassification ? { agentHarnessResultClassification } : {},
3970
- bootstrapPromptWarningSignaturesSeen: this.params.bootstrapPromptWarningSignaturesSeen,
3971
- bootstrapPromptWarningSignature: this.params.bootstrapPromptWarningSignature,
3972
- messagesSnapshot,
3973
- assistantTexts,
3974
- toolMetas,
3975
- lastAssistant,
3976
- currentAttemptAssistant,
3977
- ...this.lastNativeToolError ? { lastToolError: this.lastNativeToolError } : {},
3978
- didSendViaMessagingTool: toolTelemetry.didSendViaMessagingTool,
3979
- didDeliverSourceReplyViaMessageTool: toolTelemetry.didDeliverSourceReplyViaMessageTool === true,
3980
- messagingToolSentTexts: toolTelemetry.messagingToolSentTexts,
3981
- messagingToolSentMediaUrls: toolTelemetry.messagingToolSentMediaUrls,
3982
- messagingToolSentTargets: toolTelemetry.messagingToolSentTargets,
3983
- messagingToolSourceReplyPayloads: toolTelemetry.messagingToolSourceReplyPayloads ?? [],
3984
- heartbeatToolResponse: toolTelemetry.heartbeatToolResponse,
3985
- toolMediaUrls: this.buildToolMediaUrls(toolTelemetry),
3986
- toolAudioAsVoice: toolTelemetry.toolAudioAsVoice,
3987
- successfulCronAdds: toolTelemetry.successfulCronAdds,
3988
- cloudCodeAssistFormatError: false,
3989
- attemptUsage: this.tokenUsage,
3990
- replayMetadata: {
3991
- hadPotentialSideEffects,
3992
- replaySafe: !hadPotentialSideEffects
3993
- },
3994
- itemLifecycle: {
3995
- startedCount: this.activeItemIds.size + this.completedItemIds.size,
3996
- completedCount: this.completedItemIds.size,
3997
- activeCount: this.activeItemIds.size,
3998
- ...this.completedCompactionCount > 0 ? { compactionCount: this.completedCompactionCount } : {}
3999
- },
4000
- yieldDetected: options?.yieldDetected || false,
4001
- didSendDeterministicApprovalPrompt: this.guardianReviewCount > 0 ? false : void 0
4002
- };
4003
- }
4004
- recordDynamicToolCall(params) {
4005
- const args = sanitizeCodexToolArguments(params.arguments);
4006
- this.recordToolTranscriptCall({
4007
- id: params.callId,
4008
- name: params.tool,
4009
- arguments: args
4010
- });
4011
- }
4012
- recordDynamicToolResult(params) {
4013
- const resultText = collectDynamicToolContentText(params.contentItems);
4014
- if (params.asyncStarted === true) {
4015
- const existing = this.toolMetas.get(params.callId);
4016
- this.toolMetas.set(params.callId, {
4017
- toolName: existing?.toolName ?? params.tool,
4018
- ...existing?.meta ? { meta: existing.meta } : {},
4019
- asyncStarted: true
4020
- });
4021
- }
4022
- this.recordToolTranscriptResult({
4023
- id: params.callId,
4024
- name: params.tool,
4025
- text: resultText,
4026
- isError: !params.success
4027
- });
4028
- if (!params.success && params.terminalType === "blocked") this.lastNativeToolError = {
4029
- toolName: params.tool,
4030
- error: resultText || "codex dynamic tool blocked"
4031
- };
4032
- else if (params.success && this.lastNativeToolError && !this.lastNativeToolError.mutatingAction) this.lastNativeToolError = void 0;
4033
- if (params.sideEffectEvidence === true) this.sideEffectingDynamicToolCallIds.add(params.callId);
4034
- }
4035
- markTimedOut() {
4036
- this.aborted = true;
4037
- this.promptError = "codex app-server attempt timed out";
4038
- this.promptErrorSource = "prompt";
4039
- }
4040
- markAborted() {
4041
- this.aborted = true;
4042
- }
4043
- isCompacting() {
4044
- return this.activeCompactionItemIds.size > 0;
4045
- }
4046
- async handleAssistantDelta(params) {
4047
- const itemId = readString$5(params, "itemId") ?? readString$5(params, "id") ?? "assistant";
4048
- const delta = readString$5(params, "delta") ?? "";
4049
- if (!delta) return;
4050
- this.rememberAssistantPhase(readItem(params.item));
4051
- const phase = readString$5(params, "phase");
4052
- if (phase) this.assistantPhaseByItem.set(itemId, phase);
4053
- if (!this.assistantStarted) {
4054
- this.assistantStarted = true;
4055
- await this.params.onAssistantMessageStart?.();
4056
- }
4057
- this.rememberAssistantItem(itemId);
4058
- const text = `${this.assistantTextByItem.get(itemId) ?? ""}${delta}`;
4059
- this.assistantTextByItem.set(itemId, text);
4060
- if (this.isCommentaryAssistantItem(itemId)) this.emitCommentaryProgress({
4061
- itemId,
4062
- text
4063
- });
4064
- else {
4065
- const knownFinalAnswer = this.shouldStreamAssistantPartial(itemId);
4066
- const replace = this.streamedPartialAssistantItemId !== void 0 && this.streamedPartialAssistantItemId !== itemId;
4067
- if (replace && (!knownFinalAnswer || this.streamedPartialAssistantItemReplaceable)) this.streamedPartialAssistantItemReplaceable = true;
4068
- else if (this.streamedPartialAssistantItemId === void 0) this.streamedPartialAssistantItemReplaceable = !knownFinalAnswer;
4069
- this.streamedPartialAssistantItemId = itemId;
4070
- const replaceable = this.streamedPartialAssistantItemReplaceable;
4071
- const replacement = replace && replaceable;
4072
- const streamPayload = {
4073
- text,
4074
- delta: replacement ? "" : delta,
4075
- ...replacement ? { replace: true } : {}
4076
- };
4077
- this.emitAgentEvent({
4078
- stream: "assistant",
4079
- data: {
4080
- ...streamPayload,
4081
- ...replaceable ? { replaceable: true } : {}
4082
- }
4083
- });
4084
- if (knownFinalAnswer && !replaceable) await this.params.onPartialReply?.(streamPayload);
4085
- }
4086
- }
4087
- async handleReasoningDelta(method, params) {
4088
- const itemId = readString$5(params, "itemId") ?? readString$5(params, "id") ?? "reasoning";
4089
- const delta = readString$5(params, "delta") ?? "";
4090
- if (!delta) return;
4091
- this.reasoningStarted = true;
4092
- if (!this.reasoningItemOrder.has(itemId)) this.reasoningItemOrder.set(itemId, this.reasoningItemOrder.size);
4093
- const groupIndex = method === "item/reasoning/textDelta" ? readNonNegativeInteger(params, "contentIndex") ?? 0 : readNonNegativeInteger(params, "summaryIndex") ?? 0;
4094
- const groupKey = `${method}\0${itemId}\0${groupIndex}`;
4095
- const current = this.reasoningTextByGroup.get(groupKey);
4096
- this.reasoningTextByGroup.set(groupKey, {
4097
- itemId,
4098
- method,
4099
- index: groupIndex,
4100
- text: `${current?.text ?? ""}${delta}`
4101
- });
4102
- await this.params.onReasoningStream?.({
4103
- text: collectReasoningTextValues(this.reasoningTextByGroup, this.reasoningItemOrder).join("\n\n"),
4104
- isReasoningSnapshot: true
4105
- });
4106
- }
4107
- handlePlanDelta(params) {
4108
- const itemId = readString$5(params, "itemId") ?? readString$5(params, "id") ?? "plan";
4109
- const delta = readString$5(params, "delta") ?? "";
4110
- if (!delta) return;
4111
- const text = `${this.planTextByItem.get(itemId) ?? ""}${delta}`;
4112
- this.planTextByItem.set(itemId, text);
4113
- this.emitPlanUpdate({
4114
- explanation: void 0,
4115
- steps: splitPlanText(text)
4116
- });
4117
- }
4118
- handleTurnPlanUpdated(params) {
4119
- const plan = Array.isArray(params.plan) ? params.plan.flatMap((entry) => {
4120
- if (!isJsonObject(entry)) return [];
4121
- const step = readString$5(entry, "step");
4122
- const status = readString$5(entry, "status");
4123
- if (!step) return [];
4124
- return status ? [`${step} (${status})`] : [step];
4125
- }) : void 0;
4126
- this.emitPlanUpdate({
4127
- explanation: readNullableString$1(params, "explanation"),
4128
- steps: plan
4129
- });
4130
- }
4131
- async handleItemStarted(params) {
4132
- const item = readItem(params.item);
4133
- const itemId = item?.id ?? readString$5(params, "itemId") ?? readString$5(params, "id");
4134
- this.rememberAssistantPhase(item);
4135
- if (itemId) this.activeItemIds.add(itemId);
4136
- this.recordNativeToolOutcome(item);
4137
- if (item?.type === "contextCompaction" && itemId) {
4138
- this.activeCompactionItemIds.add(itemId);
4139
- await runAgentHarnessBeforeCompactionHook({
4140
- sessionFile: this.params.sessionFile,
4141
- messages: await this.readMirroredSessionMessages(),
4142
- ctx: {
4143
- runId: this.params.runId,
4144
- agentId: this.params.agentId,
4145
- sessionKey: this.params.sessionKey,
4146
- sessionId: this.params.sessionId,
4147
- workspaceDir: this.params.workspaceDir,
4148
- messageProvider: this.params.messageProvider ?? void 0,
4149
- trigger: this.params.trigger,
4150
- channelId: this.params.messageChannel ?? this.params.messageProvider ?? void 0
4151
- }
4152
- });
4153
- this.emitAgentEvent({
4154
- stream: "compaction",
4155
- data: {
4156
- phase: "start",
4157
- backend: "codex-app-server",
4158
- threadId: this.threadId,
4159
- turnId: this.turnId,
4160
- itemId
4161
- }
4162
- });
4163
- }
4164
- this.recordToolMeta(item);
4165
- this.emitStandardItemEvent({
4166
- phase: "start",
4167
- item
4168
- });
4169
- await this.emitNormalizedToolItemEvent({
4170
- phase: "start",
4171
- item
4172
- });
4173
- this.recordNativeToolTranscriptCall(item);
4174
- this.emitToolResultSummary(item);
4175
- this.emitAgentEvent({
4176
- stream: "codex_app_server.item",
4177
- data: {
4178
- phase: "started",
4179
- itemId,
4180
- type: item?.type
4181
- }
4182
- });
4183
- }
4184
- async handleItemCompleted(params) {
4185
- const item = readItem(params.item);
4186
- this.recordNativeToolOutcome(item);
4187
- this.clearTerminalPresentationForNativeItem(item);
4188
- const itemId = item?.id ?? readString$5(params, "itemId") ?? readString$5(params, "id");
4189
- if (itemId) {
4190
- this.activeItemIds.delete(itemId);
4191
- this.completedItemIds.add(itemId);
4192
- }
4193
- this.rememberAssistantPhase(item);
4194
- if (item?.type === "agentMessage" && typeof item.text === "string") {
4195
- this.rememberAssistantItem(item.id);
4196
- this.assistantTextByItem.set(item.id, item.text);
4197
- if (item.text && this.isCommentaryAssistantItem(item.id)) {
4198
- this.emitCommentaryProgress({
4199
- itemId: item.id,
4200
- text: item.text
4201
- });
4202
- this.pendingRawCommentaryEchoes += 1;
4203
- }
4204
- }
4205
- this.recordNativeGeneratedMedia(item);
4206
- if (item?.type === "plan" && typeof item.text === "string" && item.text) {
4207
- this.planTextByItem.set(item.id, item.text);
4208
- this.emitPlanUpdate({
4209
- explanation: void 0,
4210
- steps: splitPlanText(item.text)
4211
- });
4212
- }
4213
- if (item?.type === "contextCompaction" && itemId) {
4214
- this.activeCompactionItemIds.delete(itemId);
4215
- this.completedCompactionCount += 1;
4216
- await runAgentHarnessAfterCompactionHook({
4217
- sessionFile: this.params.sessionFile,
4218
- messages: await this.readMirroredSessionMessages(),
4219
- compactedCount: -1,
4220
- ctx: {
4221
- runId: this.params.runId,
4222
- agentId: this.params.agentId,
4223
- sessionKey: this.params.sessionKey,
4224
- sessionId: this.params.sessionId,
4225
- workspaceDir: this.params.workspaceDir,
4226
- messageProvider: this.params.messageProvider ?? void 0,
4227
- trigger: this.params.trigger,
4228
- channelId: this.params.messageChannel ?? this.params.messageProvider ?? void 0
4229
- }
4230
- });
4231
- this.emitAgentEvent({
4232
- stream: "compaction",
4233
- data: {
4234
- phase: "end",
4235
- backend: "codex-app-server",
4236
- completed: true,
4237
- threadId: this.threadId,
4238
- turnId: this.turnId,
4239
- itemId
4240
- }
4241
- });
4242
- }
4243
- this.recordToolMeta(item);
4244
- this.emitStandardItemEvent({
4245
- phase: "end",
4246
- item
4247
- });
4248
- await this.emitNormalizedToolItemEvent({
4249
- phase: "result",
4250
- item
4251
- });
4252
- this.recordNativeToolTranscriptCall(item);
4253
- this.recordNativeToolTranscriptResult(item);
4254
- this.emitToolResultSummary(item);
4255
- this.emitToolResultOutput(item);
4256
- this.emitAgentEvent({
4257
- stream: "codex_app_server.item",
4258
- data: {
4259
- phase: "completed",
4260
- itemId,
4261
- type: item?.type
4262
- }
4263
- });
4264
- }
4265
- handleTokenUsage(params) {
4266
- const tokenUsage = isJsonObject(params.tokenUsage) ? params.tokenUsage : void 0;
4267
- const current = (tokenUsage ? readFirstJsonObject(tokenUsage, CURRENT_TOKEN_USAGE_KEYS) : void 0) ?? readFirstJsonObject(params, CURRENT_TOKEN_USAGE_KEYS);
4268
- if (!current) return;
4269
- const usage = normalizeCodexTokenUsage(current);
4270
- if (usage) this.tokenUsage = usage;
4271
- }
4272
- handleGuardianReviewNotification(method, params) {
4273
- this.guardianReviewCount += 1;
4274
- const review = isJsonObject(params.review) ? params.review : void 0;
4275
- const action = isJsonObject(params.action) ? params.action : void 0;
4276
- this.emitAgentEvent({
4277
- stream: "codex_app_server.guardian",
4278
- data: {
4279
- method,
4280
- phase: method.endsWith("/started") ? "started" : "completed",
4281
- reviewId: readString$5(params, "reviewId"),
4282
- targetItemId: readNullableString$1(params, "targetItemId"),
4283
- decisionSource: readString$5(params, "decisionSource"),
4284
- status: review ? readString$5(review, "status") : void 0,
4285
- riskLevel: review ? readString$5(review, "riskLevel") : void 0,
4286
- userAuthorization: review ? readString$5(review, "userAuthorization") : void 0,
4287
- rationale: review ? readNullableString$1(review, "rationale") : void 0,
4288
- actionType: action ? readString$5(action, "type") : void 0
4289
- }
4290
- });
4291
- }
4292
- handleHookNotification(method, params) {
4293
- const run = isJsonObject(params.run) ? params.run : void 0;
4294
- if (!run) return;
4295
- const durationMs = readNumber$1(run, "durationMs");
4296
- const entries = readHookOutputEntries(run.entries);
4297
- const hookTurnId = readNullableString$1(params, "turnId");
4298
- this.emitAgentEvent({
4299
- stream: "codex_app_server.hook",
4300
- data: {
4301
- phase: method === "hook/started" ? "started" : "completed",
4302
- threadId: this.threadId,
4303
- turnId: hookTurnId === void 0 ? this.turnId : hookTurnId,
4304
- hookRunId: readString$5(run, "id"),
4305
- eventName: readString$5(run, "eventName"),
4306
- handlerType: readString$5(run, "handlerType"),
4307
- executionMode: readString$5(run, "executionMode"),
4308
- scope: readString$5(run, "scope"),
4309
- source: readString$5(run, "source"),
4310
- sourcePath: readString$5(run, "sourcePath"),
4311
- status: readString$5(run, "status"),
4312
- statusMessage: readNullableString$1(run, "statusMessage"),
4313
- ...durationMs !== void 0 ? { durationMs } : {},
4314
- ...entries.length > 0 ? { entries } : {}
4315
- }
4316
- });
4317
- }
4318
- async handleTurnCompleted(params) {
4319
- const turn = readTurn(params.turn);
4320
- if (!turn || turn.id !== this.turnId) return;
4321
- this.completedTurn = turn;
4322
- if (turn.status === "failed") {
4323
- this.promptError = formatCodexUsageLimitErrorMessage({
4324
- message: turn.error?.message,
4325
- codexErrorInfo: turn.error?.codexErrorInfo,
4326
- rateLimits: this.latestRateLimits ?? readRecentCodexRateLimits()
4327
- }) ?? turn.error?.message ?? "codex app-server turn failed";
4328
- this.promptErrorSource = "prompt";
4329
- }
4330
- const turnItems = turn.items ?? [];
4331
- for (let index = turnItems.length - 1; index >= 0; index -= 1) {
4332
- const item = turnItems[index];
4333
- if (!item || !this.isCurrentTurnSnapshotItem(item)) continue;
4334
- if (item?.type === "dynamicToolCall") break;
4335
- if (shouldClearTerminalPresentationForNativeItem(item)) {
4336
- this.clearTerminalPresentationForNativeItem(item);
4337
- break;
4338
- }
4339
- }
4340
- for (const item of turnItems) {
4341
- this.rememberAssistantPhase(item);
4342
- if (item.type === "agentMessage" && typeof item.text === "string") {
4343
- this.rememberAssistantItem(item.id);
4344
- this.assistantTextByItem.set(item.id, item.text);
4345
- }
4346
- this.recordNativeGeneratedMedia(item);
4347
- if (item.type === "plan" && typeof item.text === "string" && item.text) {
4348
- this.planTextByItem.set(item.id, item.text);
4349
- this.emitPlanUpdate({
4350
- explanation: void 0,
4351
- steps: splitPlanText(item.text)
4352
- });
4353
- }
4354
- this.recordToolMeta(item);
4355
- await this.emitSnapshotOnlyNativeToolProgress(item);
4356
- this.recordNativeToolTranscriptCall(item);
4357
- this.recordNativeToolTranscriptResult(item);
4358
- this.emitAfterToolCallObservation(item);
4359
- this.emitToolResultSummary(item);
4360
- this.emitToolResultOutput(item);
4361
- }
4362
- this.activeCompactionItemIds.clear();
4363
- await this.maybeEndReasoning();
4364
- }
4365
- async emitSnapshotOnlyNativeToolProgress(item) {
4366
- if (!shouldSynthesizeToolProgressForItem(item) || !this.isCurrentTurnSnapshotItem(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
4367
- if (!this.activeItemIds.has(item.id)) {
4368
- this.emitStandardItemEvent({
4369
- phase: "start",
4370
- item
4371
- });
4372
- await this.emitNormalizedToolItemEvent({
4373
- phase: "start",
4374
- item
4375
- });
4376
- }
4377
- this.activeItemIds.delete(item.id);
4378
- this.emitStandardItemEvent({
4379
- phase: "end",
4380
- item
4381
- });
4382
- await this.emitNormalizedToolItemEvent({
4383
- phase: "result",
4384
- item
4385
- });
4386
- this.completedItemIds.add(item.id);
4387
- }
4388
- isCurrentTurnSnapshotItem(item) {
4389
- const itemTurnId = readItemString(item, "turnId") ?? readItemString(item, "turn_id");
4390
- return itemTurnId === void 0 || itemTurnId === this.turnId;
4391
- }
4392
- handleOutputDelta(params, toolName) {
4393
- const itemId = readString$5(params, "itemId");
4394
- const delta = readString$5(params, "delta");
4395
- if (!itemId || !delta) return;
4396
- appendToolOutputDeltaText(this.toolResultOutputTextByItem, itemId, delta);
4397
- if (!this.shouldEmitToolOutput()) return;
4398
- if (this.transcriptToolProgressSuppressedIds.has(itemId) || !shouldEmitTranscriptToolProgress(toolName, this.toolTranscriptArgumentsById.get(itemId))) return;
4399
- const state = this.toolResultOutputDeltaState.get(itemId) ?? {
4400
- chars: 0,
4401
- messages: 0,
4402
- truncated: false
4403
- };
4404
- if (state.truncated) return;
4405
- const remainingChars = Math.max(0, TOOL_PROGRESS_OUTPUT_MAX_CHARS - state.chars);
4406
- const remainingMessages = Math.max(0, MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM - state.messages);
4407
- if (remainingChars === 0 || remainingMessages === 0) {
4408
- state.truncated = true;
4409
- this.toolResultOutputDeltaState.set(itemId, state);
4410
- this.emitToolResultMessage({
4411
- itemId,
4412
- text: formatToolOutput(toolName, void 0, "(output truncated)")
4413
- });
4414
- return;
4415
- }
4416
- const chunk = delta.length > remainingChars ? delta.slice(0, remainingChars) : delta;
4417
- state.chars += chunk.length;
4418
- state.messages += 1;
4419
- const reachedLimit = delta.length > remainingChars || state.chars >= TOOL_PROGRESS_OUTPUT_MAX_CHARS || state.messages >= MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM;
4420
- if (reachedLimit) state.truncated = true;
4421
- this.toolResultOutputDeltaState.set(itemId, state);
4422
- this.toolResultOutputStreamedItemIds.add(itemId);
4423
- this.emitToolResultMessage({
4424
- itemId,
4425
- text: formatToolOutput(toolName, void 0, reachedLimit ? `${chunk}\n...(truncated)...` : chunk)
4426
- });
4427
- }
4428
- async handleRawResponseItemCompleted(params) {
4429
- const item = isJsonObject(params.item) ? params.item : void 0;
4430
- if (!item) return;
4431
- await this.recordRawGeneratedImageMedia(item);
4432
- if (readString$5(item, "role") !== "assistant") return;
4433
- const phase = readString$5(item, "phase");
4434
- if (phase === "commentary" && this.pendingRawCommentaryEchoes > 0) {
4435
- this.pendingRawCommentaryEchoes -= 1;
4436
- return;
4437
- }
4438
- const text = extractRawAssistantText(item);
4439
- if (!text) return;
4440
- const itemId = readString$5(item, "id") ?? `raw-assistant-${this.assistantItemOrder.length + 1}`;
4441
- if (phase) this.assistantPhaseByItem.set(itemId, phase);
4442
- this.rememberAssistantItem(itemId);
4443
- this.assistantTextByItem.set(itemId, text);
4444
- if (phase === "commentary") this.emitCommentaryProgress({
4445
- itemId,
4446
- text
4447
- });
4448
- }
4449
- recordNativeGeneratedMedia(item) {
4450
- if (item?.type !== "imageGeneration") return;
4451
- const savedPath = readItemString(item, "savedPath")?.trim();
4452
- if (savedPath) this.recordNativeGeneratedMediaUrl({
4453
- itemId: item.id,
4454
- mediaUrl: savedPath
4455
- });
4456
- }
4457
- async recordRawGeneratedImageMedia(item) {
4458
- if (readString$5(item, "type") !== "image_generation_call") return;
4459
- const result = readString$5(item, "result");
4460
- if (!result) return;
4461
- const itemId = readString$5(item, "id") ?? `raw-image-${this.nativeGeneratedMediaItemIds.size}`;
4462
- this.nativeGeneratedMediaItemIds.add(itemId);
4463
- const maxBytes = resolveGeneratedImageMaxBytes(this.params.config);
4464
- const estimatedDecodedBytes = estimateBase64DecodedBytes(result);
4465
- if (estimatedDecodedBytes !== void 0 && estimatedDecodedBytes > maxBytes) {
4466
- embeddedAgentLog.warn("codex app-server raw image generation result exceeds media limit", {
4467
- itemId,
4468
- estimatedDecodedBytes,
4469
- maxBytes
4470
- });
4471
- return;
4472
- }
4473
- const asset = generatedImageAssetFromBase64({
4474
- base64: result,
4475
- index: this.nativeGeneratedMediaItemIds.size,
4476
- revisedPrompt: readString$5(item, "revised_prompt") ?? readString$5(item, "revisedPrompt"),
4477
- fileNamePrefix: "codex-image-generation",
4478
- sniffMimeType: true
4479
- });
4480
- if (!asset) return;
4481
- try {
4482
- const saved = await saveMediaBuffer(asset.buffer, asset.mimeType, GENERATED_IMAGE_MEDIA_SUBDIR, maxBytes, asset.fileName);
4483
- this.recordNativeGeneratedMediaUrl({
4484
- itemId,
4485
- mediaUrl: saved.path,
4486
- replaceExisting: true
4487
- });
4488
- } catch (error) {
4489
- embeddedAgentLog.warn("codex app-server raw image generation result save failed", {
4490
- itemId,
4491
- error
4492
- });
4493
- }
4494
- }
4495
- recordNativeGeneratedMediaUrl(params) {
4496
- if (this.nativeGeneratedMediaUrlsByItemId.has(params.itemId) && params.replaceExisting !== true) {
4497
- this.nativeGeneratedMediaItemIds.add(params.itemId);
4498
- return;
4499
- }
4500
- this.nativeGeneratedMediaUrlsByItemId.set(params.itemId, params.mediaUrl);
4501
- this.nativeGeneratedMediaItemIds.add(params.itemId);
4502
- }
4503
- buildToolMediaUrls(toolTelemetry) {
4504
- const mediaUrls = new Set(toolTelemetry.toolMediaUrls?.map((url) => url.trim()).filter(Boolean) ?? []);
4505
- if ((toolTelemetry.messagingToolSentMediaUrls?.length ?? 0) === 0) for (const mediaUrl of this.nativeGeneratedMediaUrlsByItemId.values()) mediaUrls.add(mediaUrl);
4506
- return mediaUrls.size > 0 ? [...mediaUrls] : toolTelemetry.toolMediaUrls;
4507
- }
4508
- async maybeEndReasoning() {
4509
- if (!this.reasoningStarted || this.reasoningEnded) return;
4510
- this.reasoningEnded = true;
4511
- await this.params.onReasoningEnd?.();
4512
- }
4513
- emitPlanUpdate(params) {
4514
- if (!params.explanation && (!params.steps || params.steps.length === 0)) return;
4515
- this.emitAgentEvent({
4516
- stream: "plan",
4517
- data: {
4518
- phase: "update",
4519
- title: "Plan updated",
4520
- source: "codex-app-server",
4521
- ...params.explanation ? { explanation: params.explanation } : {},
4522
- ...params.steps && params.steps.length > 0 ? { steps: params.steps } : {}
4523
- }
4524
- });
4525
- }
4526
- rememberAssistantPhase(item) {
4527
- if (item?.type !== "agentMessage") return;
4528
- const phase = readItemString(item, "phase");
4529
- if (phase) this.assistantPhaseByItem.set(item.id, phase);
4530
- }
4531
- isCommentaryAssistantItem(itemId) {
4532
- return this.assistantPhaseByItem.get(itemId) === "commentary";
4533
- }
4534
- shouldStreamAssistantPartial(itemId) {
4535
- return this.assistantPhaseByItem.get(itemId) === "final_answer";
4536
- }
4537
- emitCommentaryProgress(params) {
4538
- const progressText = params.text.replace(/\s+/g, " ").trim();
4539
- if (!progressText || this.lastCommentaryProgressTextByItem.get(params.itemId) === progressText) return;
4540
- this.lastCommentaryProgressTextByItem.set(params.itemId, progressText);
4541
- this.emitAgentEvent({
4542
- stream: "item",
4543
- data: {
4544
- itemId: params.itemId,
4545
- kind: "preamble",
4546
- title: "Preamble",
4547
- phase: "update",
4548
- progressText,
4549
- source: "codex-app-server"
4550
- }
4551
- });
4552
- }
4553
- emitStandardItemEvent(params) {
4554
- const { item } = params;
4555
- if (!item) return;
4556
- const kind = itemKind(item);
4557
- if (!kind) return;
4558
- const meta = itemMeta(item, this.toolProgressDetailMode());
4559
- const suppressChannelProgress = shouldSuppressChannelProgressForItem(item);
4560
- this.emitAgentEvent({
4561
- stream: "item",
4562
- data: {
4563
- itemId: item.id,
4564
- phase: params.phase,
4565
- kind,
4566
- title: itemTitle(item),
4567
- status: params.phase === "start" ? "running" : itemStatus(item),
4568
- ...itemName(item) ? { name: itemName(item) } : {},
4569
- ...meta ? { meta } : {},
4570
- ...suppressChannelProgress ? { suppressChannelProgress: true } : {}
4571
- }
4572
- });
4573
- }
4574
- async emitNormalizedToolItemEvent(params) {
4575
- const { item } = params;
4576
- if (!item || !shouldSynthesizeToolProgressForItem(item)) return;
4577
- const name = itemName(item);
4578
- if (!name) return;
4579
- const status = params.phase === "result" ? itemStatus(item) : "running";
4580
- const args = itemToolArgs(item);
4581
- const meta = itemMeta(item, this.toolProgressDetailMode());
4582
- this.recordToolTrajectoryEvent({
4583
- phase: params.phase,
4584
- item,
4585
- name,
4586
- args,
4587
- status
4588
- });
4589
- this.emitDiagnosticToolExecutionEvent({
4590
- phase: params.phase,
4591
- item,
4592
- name,
4593
- status
4594
- });
4595
- if (params.phase === "result") this.recordNativeToolError({
4596
- item,
4597
- name,
4598
- meta,
4599
- status
4600
- });
4601
- if (!shouldEmitTranscriptToolProgress(name, args)) {
4602
- if (params.phase === "result") {
4603
- this.emitAfterToolCallObservation(item);
4604
- await this.options.onNativeToolResultRecorded?.();
4605
- }
4606
- return;
4607
- }
4608
- this.emitAgentEvent({
4609
- stream: "tool",
4610
- data: {
4611
- phase: params.phase,
4612
- name,
4613
- itemId: item.id,
4614
- toolCallId: item.id,
4615
- ...meta ? { meta } : {},
4616
- ...params.phase === "start" && args ? { args } : {},
4617
- ...params.phase === "result" ? {
4618
- status,
4619
- isError: isNonSuccessItemStatus(status),
4620
- ...itemToolResult(item)
4621
- } : {}
4622
- }
4623
- });
4624
- if (params.phase === "result") {
4625
- this.emitAfterToolCallObservation(item);
4626
- await this.options.onNativeToolResultRecorded?.();
4627
- }
4628
- }
4629
- clearTerminalPresentationForNativeItem(item) {
4630
- if (!item || this.terminalPresentationClearedItemIds.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
4631
- const toolCallOrdinal = this.nativeToolOutcomeOrdinals.get(item.id);
4632
- this.terminalPresentationClearedItemIds.add(item.id);
4633
- this.params.onToolOutcome?.({
4634
- toolName: itemName(item) ?? item.type,
4635
- argsHash: "",
4636
- resultHash: "",
4637
- ...toolCallOrdinal !== void 0 ? { toolCallOrdinal } : {},
4638
- terminalPresentation: void 0,
4639
- presentationOnly: true
4640
- });
4641
- }
4642
- recordNativeToolError(params) {
4643
- if (!isNonSuccessItemStatus(params.status)) {
4644
- if (!this.lastNativeToolError) return;
4645
- if (!this.lastNativeToolError.mutatingAction) {
4646
- this.lastNativeToolError = void 0;
4647
- return;
4648
- }
4649
- const actionFingerprint = nativeToolActionFingerprint(params.item);
4650
- if (this.lastNativeToolError.actionFingerprint && actionFingerprint && this.lastNativeToolError.actionFingerprint === actionFingerprint) this.lastNativeToolError = void 0;
4651
- return;
4652
- }
4653
- const error = itemToolError(params.item, params.status, this.toolResultOutputTextByItem);
4654
- const actionFingerprint = nativeToolActionFingerprint(params.item);
4655
- this.lastNativeToolError = {
4656
- toolName: params.name,
4657
- ...params.meta ? { meta: params.meta } : {},
4658
- ...error ? { error } : {},
4659
- ...isMutatingNativeToolItem(params.item) ? { mutatingAction: true } : {},
4660
- ...actionFingerprint ? { actionFingerprint } : {}
4661
- };
4662
- }
4663
- recordToolTrajectoryEvent(params) {
4664
- if (params.phase === "start") {
4665
- this.toolTrajectoryCallIds.add(params.item.id);
4666
- this.toolTrajectoryNamesById.set(params.item.id, params.name);
4667
- this.toolTrajectoryItemsById.set(params.item.id, params.item);
4668
- this.options.trajectoryRecorder?.recordEvent("tool.call", {
4669
- threadId: this.threadId,
4670
- turnId: this.turnId,
4671
- itemId: params.item.id,
4672
- toolCallId: params.item.id,
4673
- name: params.name,
4674
- arguments: params.args
4675
- });
4676
- return;
4677
- }
4678
- this.toolTrajectoryResultIds.add(params.item.id);
4679
- const toolResult = itemToolResult(params.item).result;
4680
- const output = itemOutputText(params.item, this.toolResultOutputTextByItem);
4681
- this.options.trajectoryRecorder?.recordEvent("tool.result", {
4682
- threadId: this.threadId,
4683
- turnId: this.turnId,
4684
- itemId: params.item.id,
4685
- toolCallId: params.item.id,
4686
- name: params.name,
4687
- status: params.status,
4688
- isError: isNonSuccessItemStatus(params.status),
4689
- ...toolResult ? { result: toolResult } : {},
4690
- ...output ? { output } : {}
4691
- });
4692
- }
4693
- emitDiagnosticToolExecutionEvent(params) {
4694
- const base = {
4695
- runId: this.params.runId,
4696
- sessionId: this.params.sessionId,
4697
- sessionKey: this.params.sessionKey,
4698
- toolName: params.name,
4699
- toolCallId: params.item.id
4700
- };
4701
- if (params.phase === "start") {
4702
- this.diagnosticToolStartedAtByItem.set(params.item.id, Date.now());
4703
- emitTrustedDiagnosticEvent({
4704
- type: "tool.execution.started",
4705
- ...base
4706
- });
4707
- return;
4708
- }
4709
- const startedAt = this.diagnosticToolStartedAtByItem.get(params.item.id);
4710
- this.diagnosticToolStartedAtByItem.delete(params.item.id);
4711
- const durationMs = (typeof params.item.durationMs === "number" ? params.item.durationMs : void 0) ?? (startedAt === void 0 ? 0 : Math.max(0, Date.now() - startedAt));
4712
- const terminalEvent = params.status === "blocked" ? {
4713
- type: "tool.execution.blocked",
4714
- reason: "codex_native_tool_blocked",
4715
- deniedReason: "codex_native_tool_blocked"
4716
- } : params.status === "failed" ? {
4717
- type: "tool.execution.error",
4718
- durationMs,
4719
- errorCategory: "codex_native_tool_error"
4720
- } : {
4721
- type: "tool.execution.completed",
4722
- durationMs
4723
- };
4724
- emitTrustedDiagnosticEvent({
4725
- ...base,
4726
- ...terminalEvent
4727
- });
4728
- }
4729
- emitAfterToolCallObservation(item) {
4730
- if (!this.shouldEmitAfterToolCallObservation(item)) return;
4731
- const name = itemName(item);
4732
- if (!name) return;
4733
- const status = itemStatus(item);
4734
- if (status === "running") return;
4735
- this.afterToolCallObservedItemIds.add(item.id);
4736
- const result = itemToolResult(item).result;
4737
- const error = itemToolError(item, status, this.toolResultOutputTextByItem);
4738
- const startedAt = resolveStartedAtFromDurationMs(item.durationMs);
4739
- const hookParams = {
4740
- toolName: name,
4741
- toolCallId: item.id,
4742
- runId: this.params.runId,
4743
- agentId: this.params.agentId,
4744
- sessionId: this.params.sessionId,
4745
- sessionKey: this.params.sessionKey,
4746
- startArgs: itemToolArgs(item) ?? {},
4747
- ...result !== void 0 ? { result } : {},
4748
- ...error ? { error } : {},
4749
- ...startedAt !== void 0 ? { startedAt } : {}
4750
- };
4751
- setImmediate(() => {
4752
- runAgentHarnessAfterToolCallHook(hookParams);
4753
- });
4754
- }
4755
- shouldEmitAfterToolCallObservation(item) {
4756
- if (!shouldSynthesizeToolProgressForItem(item) || this.afterToolCallObservedItemIds.has(item.id)) return false;
4757
- if (this.options.nativePostToolUseRelayEnabled && isNativePostToolUseRelayItem(item)) return false;
4758
- return true;
4759
- }
4760
- emitToolResultSummary(item) {
4761
- if (!item || !this.params.onToolResult || !this.shouldEmitToolResult()) return;
4762
- const itemId = item.id;
4763
- if (this.toolResultSummaryItemIds.has(itemId)) return;
4764
- const toolName = itemName(item);
4765
- if (!toolName) return;
4766
- if (!shouldEmitTranscriptToolProgress(toolName, itemToolArgs(item))) return;
4767
- this.toolResultSummaryItemIds.add(itemId);
4768
- const meta = itemMeta(item, this.toolProgressDetailMode());
4769
- this.emitToolResultMessage({
4770
- itemId,
4771
- text: formatToolSummary(toolName, meta)
4772
- });
4773
- }
4774
- emitToolResultOutput(item) {
4775
- if (!item || !this.params.onToolResult || !this.shouldEmitToolOutput()) return;
4776
- const itemId = item.id;
4777
- if (this.toolResultOutputItemIds.has(itemId)) return;
4778
- if (this.toolResultOutputStreamedItemIds.has(itemId)) return;
4779
- const toolName = itemName(item);
4780
- const output = itemOutputText(item, this.toolResultOutputTextByItem);
4781
- if (!toolName || !output) return;
4782
- if (!shouldEmitTranscriptToolProgress(toolName, itemToolArgs(item))) return;
4783
- this.emitToolResultMessage({
4784
- itemId,
4785
- text: formatToolOutput(toolName, itemMeta(item, this.toolProgressDetailMode()), output),
4786
- finalOutput: true,
4787
- isError: isNonSuccessItemStatus(itemStatus(item))
4788
- });
4789
- }
4790
- emitToolResultMessage(params) {
4791
- const text = params.text.trim();
4792
- if (!text) return;
4793
- this.toolProgressTexts.add(text);
4794
- if (params.finalOutput) this.toolResultOutputItemIds.add(params.itemId);
4795
- try {
4796
- Promise.resolve(this.params.onToolResult?.({
4797
- text,
4798
- ...params.isError === true ? { isError: true } : {}
4799
- })).catch(() => {});
4800
- } catch {}
4801
- }
4802
- shouldEmitToolResult() {
4803
- return typeof this.params.shouldEmitToolResult === "function" ? this.params.shouldEmitToolResult() : this.params.verboseLevel === "on" || this.params.verboseLevel === "full";
4804
- }
4805
- shouldEmitToolOutput() {
4806
- return typeof this.params.shouldEmitToolOutput === "function" ? this.params.shouldEmitToolOutput() : this.params.verboseLevel === "full";
4807
- }
4808
- toolProgressDetailMode() {
4809
- return resolveCodexToolProgressDetailMode(this.params.toolProgressDetail);
4810
- }
4811
- recordToolMeta(item) {
4812
- if (!item) return;
4813
- if (isSideEffectingNativeToolItem(item)) this.sideEffectingToolItemIds.add(item.id);
4814
- else this.sideEffectingToolItemIds.delete(item.id);
4815
- const toolName = itemName(item);
4816
- if (!toolName) return;
4817
- const meta = itemMeta(item, this.toolProgressDetailMode());
4818
- const existing = this.toolMetas.get(item.id);
4819
- this.toolMetas.set(item.id, {
4820
- toolName,
4821
- ...meta ? { meta } : {},
4822
- ...existing?.asyncStarted ? { asyncStarted: true } : {}
4823
- });
4824
- }
4825
- recordNativeToolTranscriptCall(item) {
4826
- if (!item || !shouldRecordNativeToolTranscript(item)) return;
4827
- const name = itemName(item);
4828
- if (!name) return;
4829
- this.recordToolTranscriptCall({
4830
- id: item.id,
4831
- name,
4832
- arguments: itemToolArgs(item)
4833
- });
4834
- }
4835
- recordNativeToolTranscriptResult(item) {
4836
- if (!item || !shouldRecordNativeToolTranscript(item)) return;
4837
- const name = itemName(item);
4838
- if (!name) return;
4839
- this.recordToolTranscriptResult({
4840
- id: item.id,
4841
- name,
4842
- text: itemTranscriptResultText(item, this.toolResultOutputTextByItem),
4843
- isError: isNonSuccessItemStatus(itemStatus(item))
4844
- });
4845
- }
4846
- recordToolTranscriptCall(params) {
4847
- if (!params.id || !params.name || this.toolTranscriptCallIds.has(params.id)) return;
4848
- this.toolTranscriptCallIds.add(params.id);
4849
- this.toolTranscriptNamesById.set(params.id, params.name);
4850
- this.toolTranscriptArgumentsById.set(params.id, params.arguments);
4851
- if (!shouldEmitTranscriptToolProgress(params.name, params.arguments)) this.transcriptToolProgressSuppressedIds.add(params.id);
4852
- else this.transcriptToolProgressSuppressedIds.delete(params.id);
4853
- this.emitTranscriptToolCallProgress(params);
4854
- this.toolTranscriptMessages.push(attachCodexMirrorIdentity(this.createToolCallMessage(params), `${this.turnId}:tool:${params.id}:call`));
4855
- }
4856
- recordToolTranscriptResult(params) {
4857
- if (!params.id || !params.name || this.toolTranscriptResultIds.has(params.id)) return;
4858
- this.toolTranscriptResultIds.add(params.id);
4859
- this.emitTranscriptToolResultProgress(params);
4860
- this.toolTranscriptMessages.push(attachCodexMirrorIdentity(this.createToolResultMessage(params), `${this.turnId}:tool:${params.id}:result`));
4861
- }
4862
- synthesizeMissingToolResults(params) {
4863
- if (!params.synthesize) return;
4864
- const missingTranscriptIds = [...this.toolTranscriptCallIds].filter((id) => !this.toolTranscriptResultIds.has(id));
4865
- const missingTrajectoryIds = [...this.toolTrajectoryCallIds].filter((id) => !this.toolTrajectoryResultIds.has(id));
4866
- if (missingTranscriptIds.length === 0 && missingTrajectoryIds.length === 0) return;
4867
- for (const id of missingTranscriptIds) {
4868
- const name = this.toolTranscriptNamesById.get(id) ?? this.toolTrajectoryNamesById.get(id);
4869
- if (!name) continue;
4870
- this.recordToolTranscriptResult({
4871
- id,
4872
- name,
4873
- text: formatMissingToolResultError({
4874
- id,
4875
- name
4876
- }),
4877
- isError: true
4878
- });
4879
- }
4880
- for (const id of missingTrajectoryIds) {
4881
- const name = this.toolTrajectoryNamesById.get(id) ?? this.toolTranscriptNamesById.get(id);
4882
- if (!name) continue;
4883
- this.toolTrajectoryResultIds.add(id);
4884
- const text = formatMissingToolResultError({
4885
- id,
4886
- name
4887
- });
4888
- this.options.trajectoryRecorder?.recordEvent("tool.result", {
4889
- threadId: this.threadId,
4890
- turnId: this.turnId,
4891
- itemId: id,
4892
- toolCallId: id,
4893
- name,
4894
- status: "failed",
4895
- isError: true,
4896
- result: {
4897
- status: "failed",
4898
- reason: "missing_tool_result"
4899
- },
4900
- output: text
4901
- });
4902
- }
4903
- if (!params.recordPromptError) {
4904
- const firstMissingId = missingTranscriptIds.find((id) => {
4905
- const name = this.toolTranscriptNamesById.get(id) ?? this.toolTrajectoryNamesById.get(id);
4906
- return Boolean(name);
4907
- }) ?? missingTrajectoryIds.find((id) => {
4908
- const name = this.toolTrajectoryNamesById.get(id) ?? this.toolTranscriptNamesById.get(id);
4909
- return Boolean(name);
4910
- });
4911
- if (firstMissingId) {
4912
- const name = this.toolTranscriptNamesById.get(firstMissingId) ?? this.toolTrajectoryNamesById.get(firstMissingId);
4913
- if (name) {
4914
- const item = this.toolTrajectoryItemsById.get(firstMissingId);
4915
- const meta = item ? itemMeta(item, this.toolProgressDetailMode()) : this.toolMetas.get(firstMissingId)?.meta;
4916
- const actionFingerprint = item ? nativeToolActionFingerprint(item) : void 0;
4917
- this.lastNativeToolError = {
4918
- toolName: name,
4919
- ...meta ? { meta } : {},
4920
- error: formatMissingToolResultError({
4921
- id: firstMissingId,
4922
- name
4923
- }),
4924
- ...item && isMutatingNativeToolItem(item) ? { mutatingAction: true } : {},
4925
- ...actionFingerprint ? { actionFingerprint } : {}
4926
- };
4927
- }
4928
- }
4929
- return;
4930
- }
4931
- const missingCount = new Set([...missingTranscriptIds, ...missingTrajectoryIds]).size;
4932
- this.synthesizedMissingToolResultError = missingCount === 1 ? MISSING_TOOL_RESULT_ERROR : `${MISSING_TOOL_RESULT_ERROR} missingToolResultCount=${missingCount}`;
4933
- this.promptErrorSource = this.promptErrorSource ?? "prompt";
4934
- }
4935
- emitTranscriptToolCallProgress(params) {
4936
- if (!shouldEmitTranscriptToolProgress(params.name, params.arguments)) return;
4937
- this.transcriptToolProgressCallIds.add(params.id);
4938
- const args = normalizeToolTranscriptArguments(params.arguments);
4939
- const meta = inferToolMetaFromArgs(params.name, args, { detailMode: this.toolProgressDetailMode() });
4940
- if (!this.params.onToolResult || !this.shouldEmitToolResult() || this.toolResultSummaryItemIds.has(params.id) || this.toolResultOutputStreamedItemIds.has(params.id)) return;
4941
- this.toolResultSummaryItemIds.add(params.id);
4942
- this.emitToolResultMessage({
4943
- itemId: params.id,
4944
- text: formatToolSummary(params.name, meta)
4945
- });
4946
- }
4947
- emitTranscriptToolResultProgress(params) {
4948
- if (this.transcriptToolProgressSuppressedIds.has(params.id) || !shouldEmitTranscriptToolProgress(params.name, this.toolTranscriptArgumentsById.get(params.id))) return;
4949
- if (!this.transcriptToolProgressCallIds.has(params.id)) this.emitTranscriptToolCallProgress({
4950
- id: params.id,
4951
- name: params.name,
4952
- arguments: {}
4953
- });
4954
- if (!this.params.onToolResult || !this.shouldEmitToolOutput() || this.toolResultOutputItemIds.has(params.id) || this.toolResultOutputStreamedItemIds.has(params.id)) return;
4955
- const text = params.text?.trim();
4956
- if (!text) return;
4957
- this.emitToolResultMessage({
4958
- itemId: params.id,
4959
- text: formatToolOutput(params.name, void 0, text),
4960
- finalOutput: true,
4961
- isError: params.isError
4962
- });
4963
- }
4964
- formatCodexErrorMessage(params) {
4965
- const error = isJsonObject(params.error) ? params.error : void 0;
4966
- return formatCodexUsageLimitErrorMessage({
4967
- message: error ? readString$5(error, "message") : void 0,
4968
- codexErrorInfo: error?.codexErrorInfo,
4969
- rateLimits: this.latestRateLimits ?? readRecentCodexRateLimits()
4970
- }) ?? readCodexErrorNotificationMessage(params);
4971
- }
4972
- emitAgentEvent(event) {
4973
- try {
4974
- emitAgentEvent({
4975
- runId: this.params.runId,
4976
- stream: event.stream,
4977
- data: event.data,
4978
- ...this.params.sessionKey ? { sessionKey: this.params.sessionKey } : {}
4979
- });
4980
- } catch (error) {
4981
- embeddedAgentLog.debug("codex app-server global agent event emit failed", { error });
4982
- }
4983
- try {
4984
- const maybePromise = this.params.onAgentEvent?.(event);
4985
- Promise.resolve(maybePromise).catch((error) => {
4986
- embeddedAgentLog.debug("codex app-server agent event handler rejected", { error });
4987
- });
4988
- } catch (error) {
4989
- embeddedAgentLog.debug("codex app-server agent event handler threw", { error });
4990
- }
4991
- }
4992
- collectAssistantTexts() {
4993
- const finalText = this.resolveFinalAssistantText();
4994
- return finalText ? [finalText] : [];
4995
- }
4996
- hasAssistantItemTextForSynthesis() {
4997
- for (let i = this.assistantItemOrder.length - 1; i >= 0; i -= 1) {
4998
- const itemId = this.assistantItemOrder[i];
4999
- if (!itemId) continue;
5000
- if (this.assistantPhaseByItem.get(itemId) === "commentary") continue;
5001
- const text = this.assistantTextByItem.get(itemId);
5002
- if (text && text.length > 0) return true;
5003
- }
5004
- return false;
5005
- }
5006
- resolveFinalAssistantText() {
5007
- return this.resolveFinalAssistantTextItem()?.text;
5008
- }
5009
- resolveFinalAssistantTextItem() {
5010
- for (let i = this.assistantItemOrder.length - 1; i >= 0; i -= 1) {
5011
- const itemId = this.assistantItemOrder[i];
5012
- if (!itemId) continue;
5013
- const text = this.assistantTextByItem.get(itemId)?.trim();
5014
- if (this.assistantPhaseByItem.get(itemId) === "commentary") continue;
5015
- if (text && !this.toolProgressTexts.has(text)) return {
5016
- itemId,
5017
- text
5018
- };
5019
- }
5020
- }
5021
- rememberAssistantItem(itemId) {
5022
- if (!itemId || this.assistantItemOrder.includes(itemId)) return;
5023
- this.assistantItemOrder.push(itemId);
5024
- }
5025
- createCurrentAttemptAssistantMessage() {
5026
- for (let i = this.assistantItemOrder.length - 1; i >= 0; i -= 1) {
5027
- const itemId = this.assistantItemOrder[i];
5028
- if (!itemId || this.isCommentaryAssistantItem(itemId) || !this.assistantTextByItem.has(itemId)) continue;
5029
- const text = this.assistantTextByItem.get(itemId) ?? "";
5030
- const normalizedText = text.trim();
5031
- if (normalizedText && this.toolProgressTexts.has(normalizedText)) continue;
5032
- return this.createAssistantMessage(text);
5033
- }
5034
- }
5035
- async readMirroredSessionMessages() {
5036
- return await readCodexMirroredSessionHistoryMessages({
5037
- agentId: this.params.agentId,
5038
- sessionFile: this.params.sessionFile,
5039
- sessionId: this.params.sessionId,
5040
- sessionKey: this.params.sessionKey
5041
- }) ?? [];
5042
- }
5043
- createAssistantMessage(text) {
5044
- const attribution = resolveCodexLocalRuntimeAttribution(this.params);
5045
- const usage = this.tokenUsage ? {
5046
- input: this.tokenUsage.input ?? 0,
5047
- output: this.tokenUsage.output ?? 0,
5048
- cacheRead: this.tokenUsage.cacheRead ?? 0,
5049
- cacheWrite: this.tokenUsage.cacheWrite ?? 0,
5050
- totalTokens: this.tokenUsage.total ?? (this.tokenUsage.input ?? 0) + (this.tokenUsage.output ?? 0) + (this.tokenUsage.cacheRead ?? 0) + (this.tokenUsage.cacheWrite ?? 0),
5051
- cost: ZERO_USAGE.cost
5052
- } : ZERO_USAGE;
5053
- return {
5054
- role: "assistant",
5055
- content: [{
5056
- type: "text",
5057
- text
5058
- }],
5059
- api: attribution.api ?? "openai-chatgpt-responses",
5060
- provider: attribution.provider,
5061
- model: this.params.modelId,
5062
- usage,
5063
- stopReason: this.aborted ? "aborted" : this.promptError ? "error" : "stop",
5064
- errorMessage: this.promptError ? formatErrorMessage(this.promptError) : void 0,
5065
- timestamp: Date.now()
5066
- };
5067
- }
5068
- createAssistantMirrorMessage(title, text) {
5069
- const attribution = resolveCodexLocalRuntimeAttribution(this.params);
5070
- return {
5071
- role: "assistant",
5072
- content: [{
5073
- type: "text",
5074
- text: `${title}:\n${text}`
5075
- }],
5076
- api: attribution.api ?? "openai-chatgpt-responses",
5077
- provider: attribution.provider,
5078
- model: this.params.modelId,
5079
- usage: ZERO_USAGE,
5080
- stopReason: "stop",
5081
- timestamp: Date.now()
5082
- };
5083
- }
5084
- createToolCallMessage(params) {
5085
- const args = normalizeToolTranscriptArguments(params.arguments);
5086
- const attribution = resolveCodexLocalRuntimeAttribution(this.params);
5087
- return {
5088
- role: "assistant",
5089
- content: [{
5090
- type: "toolCall",
5091
- id: params.id,
5092
- name: params.name,
5093
- arguments: args,
5094
- input: args
5095
- }],
5096
- api: attribution.api ?? "openai-chatgpt-responses",
5097
- provider: attribution.provider,
5098
- model: this.params.modelId,
5099
- usage: ZERO_USAGE,
5100
- stopReason: "toolUse",
5101
- timestamp: Date.now()
5102
- };
5103
- }
5104
- createToolResultMessage(params) {
5105
- const text = truncateToolTranscriptText(params.text?.trim() || toolResultStatusText(params));
5106
- return {
5107
- role: "toolResult",
5108
- toolCallId: params.id,
5109
- toolName: params.name,
5110
- isError: params.isError,
5111
- content: [{
5112
- type: "toolResult",
5113
- id: params.id,
5114
- name: params.name,
5115
- toolName: params.name,
5116
- toolCallId: params.id,
5117
- toolUseId: params.id,
5118
- tool_use_id: params.id,
5119
- content: text,
5120
- text
5121
- }],
5122
- timestamp: Date.now()
5123
- };
5124
- }
5125
- isNotificationForTurn(params) {
5126
- const threadId = readCodexNotificationThreadId(params);
5127
- const turnId = readNotificationTurnId(params);
5128
- return threadId === this.threadId && turnId === this.turnId;
5129
- }
5130
- isHookNotificationForCurrentThread(params) {
5131
- const threadId = readString$5(params, "threadId");
5132
- const turnId = params.turnId;
5133
- return threadId === this.threadId && (turnId === this.turnId || turnId === null);
5134
- }
5135
- };
5136
- function isHookNotificationMethod(method) {
5137
- return method === "hook/started" || method === "hook/completed";
5138
- }
5139
- function readNotificationTurnId(record) {
5140
- return readCodexNotificationTurnId(record);
5141
- }
5142
- function readString$5(record, key) {
5143
- const value = record[key];
5144
- return typeof value === "string" ? value : void 0;
5145
- }
5146
- function estimateBase64DecodedBytes(base64) {
5147
- let nonWhitespaceLength = 0;
5148
- let previousCode = -1;
5149
- let lastCode = -1;
5150
- for (let i = 0; i < base64.length; i += 1) {
5151
- const code = base64.charCodeAt(i);
5152
- if (isBase64WhitespaceCode(code)) continue;
5153
- nonWhitespaceLength += 1;
5154
- previousCode = lastCode;
5155
- lastCode = code;
5156
- }
5157
- if (nonWhitespaceLength === 0) return;
5158
- const equalsCode = "=".charCodeAt(0);
5159
- const padding = lastCode === equalsCode ? previousCode === equalsCode ? 2 : 1 : 0;
5160
- return Math.max(0, Math.floor(nonWhitespaceLength * 3 / 4) - padding);
5161
- }
5162
- function isBase64WhitespaceCode(code) {
5163
- return code === 32 || code === 9 || code === 10 || code === 13;
5164
- }
5165
- function resolveGeneratedImageMaxBytes(config) {
5166
- const configured = config?.agents?.defaults?.mediaMaxMb;
5167
- if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return Math.floor(configured * BYTES_PER_MB);
5168
- return DEFAULT_GENERATED_IMAGE_MAX_BYTES;
5169
- }
5170
- function normalizeNonEmptyString(value) {
5171
- if (typeof value !== "string") return;
5172
- return value.trim() || void 0;
5173
- }
5174
- function readNonEmptyString(record, key) {
5175
- return normalizeNonEmptyString(record[key]);
5176
- }
5177
- function readNonEmptyStringArray(record, key) {
5178
- const value = record[key];
5179
- if (!Array.isArray(value)) return [];
5180
- const entries = [];
5181
- for (const entry of value) {
5182
- const normalized = normalizeNonEmptyString(entry);
5183
- if (normalized) entries.push(normalized);
5184
- }
5185
- return entries;
5186
- }
5187
- function readNullableString$1(record, key) {
5188
- const value = record[key];
5189
- if (value === null) return null;
5190
- return typeof value === "string" ? value : void 0;
5191
- }
5192
- function readNumber$1(record, key) {
5193
- const value = record[key];
5194
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5195
- }
5196
- function resolveStartedAtFromDurationMs(durationMs) {
5197
- if (typeof durationMs !== "number" || !Number.isFinite(durationMs)) return;
5198
- return asDateTimestampMs(Date.now() - Math.max(0, durationMs));
5199
- }
5200
- function readNonNegativeInteger(record, key) {
5201
- const value = readNumber$1(record, key);
5202
- return value !== void 0 && Number.isInteger(value) && value >= 0 ? value : void 0;
5203
- }
5204
- function readBoolean(record, key) {
5205
- const value = record[key];
5206
- return typeof value === "boolean" ? value : void 0;
5207
- }
5208
- function readBooleanAlias(record, keys) {
5209
- for (const key of keys) {
5210
- const value = readBoolean(record, key);
5211
- if (value !== void 0) return value;
5212
- }
5213
- }
5214
- function readCodexErrorNotificationMessage(record) {
5215
- const error = record.error;
5216
- if (isJsonObject(error)) return readString$5(error, "message") ?? readString$5(error, "error");
5217
- return readString$5(record, "message");
5218
- }
5219
- function readHookOutputEntries(value) {
5220
- if (!Array.isArray(value)) return [];
5221
- return value.flatMap((entry) => {
5222
- if (!isJsonObject(entry)) return [];
5223
- const text = readString$5(entry, "text");
5224
- if (!text) return [];
5225
- const kind = readString$5(entry, "kind");
5226
- return [{
5227
- ...kind ? { kind } : {},
5228
- text
5229
- }];
5230
- });
5231
- }
5232
- function readFirstJsonObject(record, keys) {
5233
- for (const key of keys) {
5234
- const value = record[key];
5235
- if (isJsonObject(value)) return value;
5236
- }
5237
- }
5238
- function readNumberAlias(record, keys) {
5239
- for (const key of keys) {
5240
- const value = readNumber$1(record, key);
5241
- if (value !== void 0) return value;
5242
- }
5243
- }
5244
- function normalizeCodexTokenUsage(record) {
5245
- const promptTotalInput = readNumberAlias(record, CODEX_PROMPT_TOTAL_INPUT_KEYS);
5246
- const cacheRead = readNumberAlias(record, [
5247
- "cachedInputTokens",
5248
- "cached_input_tokens",
5249
- "cacheRead",
5250
- "cache_read",
5251
- "cache_read_input_tokens",
5252
- "cached_tokens"
5253
- ]);
5254
- return normalizeUsage({
5255
- input: promptTotalInput !== void 0 && cacheRead !== void 0 ? Math.max(0, promptTotalInput - cacheRead) : promptTotalInput ?? readNumber$1(record, "input"),
5256
- output: readNumberAlias(record, [
5257
- "outputTokens",
5258
- "output_tokens",
5259
- "output"
5260
- ]),
5261
- cacheRead,
5262
- cacheWrite: readNumberAlias(record, [
5263
- "cacheWrite",
5264
- "cache_write",
5265
- "cacheCreationInputTokens",
5266
- "cache_creation_input_tokens"
5267
- ]),
5268
- total: readNumberAlias(record, [
5269
- "totalTokens",
5270
- "total_tokens",
5271
- "total"
5272
- ])
5273
- });
5274
- }
5275
- function splitPlanText(text) {
5276
- return text.split(/\r?\n/).map((line) => line.trim().replace(/^[-*]\s+/, "")).filter((line) => line.length > 0);
5277
- }
5278
- function collectTextValues(map) {
5279
- return [...map.values()].filter((text) => text.trim().length > 0);
5280
- }
5281
- function collectReasoningTextValues(groups, itemOrder) {
5282
- return [...groups.values()].toSorted((left, right) => {
5283
- const itemDelta = (itemOrder.get(left.itemId) ?? Number.MAX_SAFE_INTEGER) - (itemOrder.get(right.itemId) ?? Number.MAX_SAFE_INTEGER);
5284
- if (itemDelta !== 0) return itemDelta;
5285
- const methodDelta = reasoningMethodOrder(left.method) - reasoningMethodOrder(right.method);
5286
- return methodDelta !== 0 ? methodDelta : left.index - right.index;
5287
- }).map((group) => group.text).filter((text) => text.trim().length > 0);
5288
- }
5289
- function reasoningMethodOrder(method) {
5290
- return method === "item/reasoning/summaryTextDelta" ? 0 : 1;
5291
- }
5292
- function extractRawAssistantText(item) {
5293
- return (Array.isArray(item.content) ? item.content : []).flatMap((entry) => {
5294
- if (!isJsonObject(entry)) return [];
5295
- const type = readString$5(entry, "type");
5296
- if (type !== "output_text" && type !== "text") return [];
5297
- const value = readString$5(entry, "text");
5298
- return value ? [value] : [];
5299
- }).join("").trim() || void 0;
5300
- }
5301
- function itemKind(item) {
5302
- switch (item.type) {
5303
- case "dynamicToolCall":
5304
- case "mcpToolCall": return "tool";
5305
- case "commandExecution": return "command";
5306
- case "fileChange": return "patch";
5307
- case "webSearch": return "search";
5308
- case "reasoning":
5309
- case "contextCompaction": return "analysis";
5310
- default: return;
5311
- }
5312
- }
5313
- function itemTitle(item) {
5314
- switch (item.type) {
5315
- case "commandExecution": return "Command";
5316
- case "fileChange": return "File change";
5317
- case "mcpToolCall": return "MCP tool";
5318
- case "dynamicToolCall": return "Tool";
5319
- case "webSearch": return "Web search";
5320
- case "contextCompaction": return "Context compaction";
5321
- case "reasoning": return "Reasoning";
5322
- default: return item.type;
5323
- }
5324
- }
5325
- function itemStatus(item) {
5326
- const status = readItemString(item, "status");
5327
- if (status === "failed") return "failed";
5328
- if (status === "declined") return "blocked";
5329
- if (status === "inProgress" || status === "running") return "running";
5330
- return "completed";
5331
- }
5332
- function formatMissingToolResultError(params) {
5333
- return `${MISSING_TOOL_RESULT_ERROR} toolCallId=${params.id}; toolName=${params.name}`;
5334
- }
5335
- function isNonSuccessItemStatus(status) {
5336
- return status === "failed" || status === "blocked";
5337
- }
5338
- function itemName(item) {
5339
- if (item.type === "dynamicToolCall" && typeof item.tool === "string") return item.tool;
5340
- if (item.type === "mcpToolCall" && typeof item.tool === "string") {
5341
- const server = typeof item.server === "string" ? item.server : void 0;
5342
- return server ? `${server}.${item.tool}` : item.tool;
5343
- }
5344
- if (item.type === "commandExecution") return "bash";
5345
- if (item.type === "fileChange") return "apply_patch";
5346
- if (item.type === "webSearch") return "web_search";
5347
- }
5348
- function isSideEffectingNativeToolItem(item) {
5349
- return itemStatus(item) !== "blocked" && (isMutatingNativeToolItem(item) || item.type === "mcpToolCall");
5350
- }
5351
- function shouldSynthesizeToolProgressForItem(item) {
5352
- switch (item.type) {
5353
- case "commandExecution":
5354
- case "fileChange":
5355
- case "webSearch":
5356
- case "mcpToolCall": return true;
5357
- default: return false;
5358
- }
5359
- }
5360
- function shouldRecordNativeToolTranscript(item) {
5361
- return shouldSynthesizeToolProgressForItem(item) && item.type !== "webSearch";
5362
- }
5363
- function isMutatingNativeToolItem(item) {
5364
- if (item.type === "commandExecution") return true;
5365
- return item.type === "fileChange" || item.type === "collabAgentToolCall" || item.type === "imageGeneration";
5366
- }
5367
- function shouldClearTerminalPresentationForNativeItem(item) {
5368
- switch (item.type) {
5369
- case "collabAgentToolCall":
5370
- case "commandExecution":
5371
- case "fileChange":
5372
- case "imageGeneration":
5373
- case "imageView":
5374
- case "mcpToolCall":
5375
- case "webSearch": return true;
5376
- default: return false;
5377
- }
5378
- }
5379
- function nativeToolActionFingerprint(item) {
5380
- if (item.type === "commandExecution" && typeof item.command === "string") return JSON.stringify({
5381
- type: item.type,
5382
- command: item.command,
5383
- cwd: typeof item.cwd === "string" ? item.cwd : ""
5384
- });
5385
- if (item.type === "fileChange") return JSON.stringify({
5386
- type: item.type,
5387
- changes: itemFileChanges(item)
5388
- });
5389
- }
5390
- function isNativePostToolUseRelayItem(item) {
5391
- switch (item.type) {
5392
- case "commandExecution":
5393
- case "fileChange":
5394
- case "mcpToolCall": return true;
5395
- default: return false;
5396
- }
5397
- }
5398
- function shouldSuppressChannelProgressForItem(item) {
5399
- if (shouldSynthesizeToolProgressForItem(item)) return true;
5400
- return item.type === "dynamicToolCall";
5401
- }
5402
- function itemToolArgs(item) {
5403
- if (item.type === "commandExecution") return sanitizeCodexAgentEventRecord({
5404
- command: item.command,
5405
- ...typeof item.cwd === "string" ? { cwd: item.cwd } : {}
5406
- });
5407
- if (item.type === "fileChange") return sanitizeCodexAgentEventRecord({ changes: itemFileChanges(item) });
5408
- if (item.type === "webSearch") return webSearchToolArgs(item);
5409
- if (item.type === "dynamicToolCall" || item.type === "mcpToolCall") return sanitizeCodexToolArguments(item.arguments);
5410
- }
5411
- function webSearchToolArgs(item) {
5412
- const action = isJsonObject(item.action) ? item.action : void 0;
5413
- const actionType = action ? readNonEmptyString(action, "type") : void 0;
5414
- const queries = action && actionType === "search" ? readNonEmptyStringArray(action, "queries") : [];
5415
- const query = normalizeNonEmptyString(item.query) ?? (action && actionType === "search" ? readNonEmptyString(action, "query") : void 0) ?? queries[0];
5416
- const url = action ? readNonEmptyString(action, "url") : void 0;
5417
- const pattern = action ? readNonEmptyString(action, "pattern") : void 0;
5418
- const args = {};
5419
- if (query) args.query = query;
5420
- if (queries.length > 0) args.queries = queries;
5421
- if (actionType && actionType !== "search") args.action = actionType;
5422
- if (url) args.url = url;
5423
- if (pattern) args.pattern = pattern;
5424
- if (!query && !url && !pattern) args.queryUnavailable = true;
5425
- return sanitizeCodexAgentEventRecord(args);
5426
- }
5427
- function itemToolResult(item) {
5428
- if (item.type === "commandExecution") return { result: sanitizeCodexAgentEventRecord({
5429
- status: item.status,
5430
- exitCode: item.exitCode,
5431
- durationMs: item.durationMs
5432
- }) };
5433
- if (item.type === "fileChange") return { result: sanitizeCodexAgentEventRecord({
5434
- status: item.status,
5435
- changes: itemFileChanges(item)
5436
- }) };
5437
- if (item.type === "mcpToolCall") return { result: sanitizeCodexAgentEventRecord({
5438
- status: item.status,
5439
- durationMs: item.durationMs,
5440
- ...item.error ? { error: item.error } : {},
5441
- ...item.result ? { result: item.result } : {}
5442
- }) };
5443
- if (item.type === "webSearch") return { result: webSearchToolResult(item) };
5444
- return {};
5445
- }
5446
- function webSearchToolResult(item) {
5447
- return sanitizeCodexAgentEventRecord({
5448
- status: itemStatus(item),
5449
- ...typeof item.durationMs === "number" ? { durationMs: item.durationMs } : {},
5450
- ...webSearchToolArgs(item)
5451
- });
5452
- }
5453
- function itemFileChanges(item) {
5454
- return Array.isArray(item.changes) ? item.changes.map((change) => ({
5455
- path: change.path,
5456
- kind: change.kind
5457
- })) : [];
5458
- }
5459
- function itemToolError(item, status, outputTextByItem) {
5460
- if (status === "blocked") return "codex native tool blocked";
5461
- if (status !== "failed") return;
5462
- return itemOutputText(item, outputTextByItem) ?? "codex native tool failed";
5463
- }
5464
- function itemMeta(item, detailMode = "explain") {
5465
- if (item.type === "commandExecution" && typeof item.command === "string") return inferToolMetaFromArgs("exec", {
5466
- command: item.command,
5467
- cwd: typeof item.cwd === "string" ? item.cwd : void 0
5468
- }, { detailMode });
5469
- if (item.type === "webSearch") return inferToolMetaFromArgs("web_search", webSearchToolArgs(item), { detailMode });
5470
- const toolName = itemName(item);
5471
- if ((item.type === "dynamicToolCall" || item.type === "mcpToolCall") && toolName) return inferToolMetaFromArgs(toolName, item.arguments, { detailMode });
5472
- }
5473
- function itemOutputText(item, outputTextByItem) {
5474
- if (item.type === "commandExecution") return item.aggregatedOutput?.trim() || outputTextByItem?.get(item.id)?.trim() || void 0;
5475
- if (item.type === "dynamicToolCall") return collectDynamicToolContentText(item.contentItems).trim() || void 0;
5476
- if (item.type === "mcpToolCall") {
5477
- if (item.error) return stringifyJsonValue(item.error);
5478
- return item.result ? stringifyJsonValue(item.result) : void 0;
5479
- }
5480
- }
5481
- function itemTranscriptResultText(item, outputTextByItem) {
5482
- const output = itemOutputText(item, outputTextByItem);
5483
- if (output) return output;
5484
- const result = itemToolResult(item).result;
5485
- return result ? stringifyJsonValue(result) : itemStatus(item);
5486
- }
5487
- function appendToolOutputDeltaText(outputTextByItem, itemId, delta) {
5488
- const current = outputTextByItem.get(itemId) ?? "";
5489
- if (current.length >= TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS) return;
5490
- const remaining = TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS - current.length;
5491
- const next = current + (delta.length > remaining ? delta.slice(0, remaining) : delta);
5492
- outputTextByItem.set(itemId, next);
5493
- }
5494
- function normalizeToolTranscriptArguments(value) {
5495
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
5496
- return value;
5497
- }
5498
- function collectDynamicToolContentText(contentItems) {
5499
- if (!Array.isArray(contentItems)) return "";
5500
- return contentItems.flatMap((entry) => {
5501
- if (!isJsonObject(entry)) return [];
5502
- const text = readString$5(entry, "text");
5503
- return text ? [text] : [];
5504
- }).join("\n");
5505
- }
5506
- function truncateToolTranscriptText(text) {
5507
- if (text.length <= TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS) return text;
5508
- return `${text.slice(0, TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS)}\n...(truncated)...`;
5509
- }
5510
- function toolResultStatusText(params) {
5511
- return params.isError ? `${params.name} failed` : `${params.name} completed`;
5512
- }
5513
- function stringifyJsonValue(value) {
5514
- try {
5515
- return JSON.stringify(value, null, 2);
5516
- } catch {
5517
- return;
5518
- }
5519
- }
5520
- function formatToolSummary(toolName, meta) {
5521
- const trimmedMeta = meta?.trim();
5522
- return formatToolAggregate(toolName, trimmedMeta ? [trimmedMeta] : void 0, { markdown: true });
5523
- }
5524
- function formatToolOutput(toolName, meta, output) {
5525
- const formattedOutput = formatToolProgressOutput(output);
5526
- if (!formattedOutput) return formatToolSummary(toolName, meta);
5527
- const fence = markdownFenceForText(formattedOutput);
5528
- return `${formatToolSummary(toolName, meta)}\n${fence}txt\n${formattedOutput}\n${fence}`;
5529
- }
5530
- function markdownFenceForText(text) {
5531
- return "`".repeat(Math.max(3, longestBacktickRun(text) + 1));
5532
- }
5533
- function longestBacktickRun(value) {
5534
- let longest = 0;
5535
- let current = 0;
5536
- for (const char of value) {
5537
- if (char === "`") {
5538
- current += 1;
5539
- longest = Math.max(longest, current);
5540
- continue;
5541
- }
5542
- current = 0;
5543
- }
5544
- return longest;
5545
- }
5546
- function readItemString(item, key) {
5547
- const value = item[key];
5548
- return typeof value === "string" ? value : void 0;
5549
- }
5550
- function readItem(value) {
5551
- if (!isJsonObject(value)) return;
5552
- const type = typeof value.type === "string" ? value.type : void 0;
5553
- const id = typeof value.id === "string" ? value.id : void 0;
5554
- if (!type || !id) return;
5555
- return value;
5556
- }
5557
- function readTurn(value) {
5558
- return readCodexTurn(value);
3395
+ if (options?.attemptProgress) recordAttemptProgress(completionLastActivityReason, options);
3396
+ },
3397
+ extendAttemptIdleWatch: (timeoutMs) => {
3398
+ attemptIdleTimeoutOverrideMs = resolveWatchTimeoutMs(timeoutMs);
3399
+ scheduleAttemptIdleWatch();
3400
+ },
3401
+ scheduleProgressWatches,
3402
+ clearCompletionIdleTimer,
3403
+ clearAssistantCompletionIdleTimer,
3404
+ clearTerminalIdleTimer,
3405
+ clearAttemptIdleTimer,
3406
+ clearAllTimers
3407
+ };
5559
3408
  }
5560
3409
  //#endregion
5561
3410
  //#region extensions/codex/src/app-server/native-subagent-notification.ts
@@ -5701,8 +3550,7 @@ var CodexNativeSubagentTaskMirror = class {
5701
3550
  constructor(params, runtime) {
5702
3551
  this.params = params;
5703
3552
  this.runtime = runtime;
5704
- this.mirroredThreadIds = /* @__PURE__ */ new Set();
5705
- this.failedMirrorThreadIds = /* @__PURE__ */ new Set();
3553
+ this.mirrorStateByThreadId = /* @__PURE__ */ new Map();
5706
3554
  this.terminalRunIds = /* @__PURE__ */ new Set();
5707
3555
  this.authoritativeRunIds = /* @__PURE__ */ new Set();
5708
3556
  this.expectedAuthoritativeRunIds = /* @__PURE__ */ new Set();
@@ -5736,8 +3584,8 @@ var CodexNativeSubagentTaskMirror = class {
5736
3584
  const spawn = readSubagentThreadSpawnSource(thread.source, this.params.parentThreadId);
5737
3585
  if (!spawn) return;
5738
3586
  const threadId = thread.id.trim();
5739
- if (!threadId || this.mirroredThreadIds.has(threadId)) return;
5740
- this.mirroredThreadIds.add(threadId);
3587
+ if (!threadId || this.mirrorStateByThreadId.get(threadId) === "mirrored") return;
3588
+ this.mirrorStateByThreadId.set(threadId, "mirrored");
5741
3589
  const runId = codexNativeSubagentRunId(threadId);
5742
3590
  const label = trimOptional(spawn.agent_nickname) ?? trimOptional(thread.agentNickname) ?? trimOptional(spawn.agent_role) ?? trimOptional(thread.agentRole) ?? "Codex subagent";
5743
3591
  const task = trimOptional(thread.preview) ?? `Codex native subagent${label === "Codex subagent" ? "" : ` ${label}`}`;
@@ -5755,11 +3603,9 @@ var CodexNativeSubagentTaskMirror = class {
5755
3603
  lastEventAt: this.now(),
5756
3604
  progressSummary: "Codex native subagent started."
5757
3605
  })) {
5758
- this.mirroredThreadIds.delete(threadId);
5759
- this.failedMirrorThreadIds.add(threadId);
3606
+ this.mirrorStateByThreadId.set(threadId, "failed");
5760
3607
  return;
5761
3608
  }
5762
- this.failedMirrorThreadIds.delete(threadId);
5763
3609
  this.terminalRunIds.delete(runId);
5764
3610
  this.authoritativeRunIds.delete(runId);
5765
3611
  this.applyStatus(threadId, thread.status);
@@ -5770,7 +3616,7 @@ var CodexNativeSubagentTaskMirror = class {
5770
3616
  this.applyStatus(notification.threadId, notification.status);
5771
3617
  }
5772
3618
  applyStatus(threadId, status) {
5773
- if (!this.mirroredThreadIds.has(threadId) && this.failedMirrorThreadIds.has(threadId)) return;
3619
+ if (this.mirrorStateByThreadId.get(threadId) === "failed") return;
5774
3620
  const statusType = status?.type;
5775
3621
  if (!statusType) return;
5776
3622
  const runId = codexNativeSubagentRunId(threadId);
@@ -5820,7 +3666,7 @@ var CodexNativeSubagentTaskMirror = class {
5820
3666
  const isSpawnAgentTool = normalizeToolName$1(readString$3(item, "tool")) === "spawnagent";
5821
3667
  const receiverThreadIds = readStringArray$1(item.receiverThreadIds);
5822
3668
  const agentsStates = readAgentsStates(item.agentsStates);
5823
- const spawnChildThreadIds = new Set([...receiverThreadIds, ...agentsStates.keys()]);
3669
+ const spawnChildThreadIds = /* @__PURE__ */ new Set([...receiverThreadIds, ...agentsStates.keys()]);
5824
3670
  if (isSpawnAgentTool) for (const childThreadId of spawnChildThreadIds) this.createTaskFromCollabSpawnItem(childThreadId, item);
5825
3671
  const toolCallStatus = normalizeCollabToolCallStatus(readString$3(item, "status"));
5826
3672
  const terminalToolCallThreadIds = /* @__PURE__ */ new Set();
@@ -5843,8 +3689,8 @@ var CodexNativeSubagentTaskMirror = class {
5843
3689
  }
5844
3690
  createTaskFromCollabSpawnItem(threadId, item) {
5845
3691
  const normalizedThreadId = threadId.trim();
5846
- if (!normalizedThreadId || this.mirroredThreadIds.has(normalizedThreadId)) return;
5847
- this.mirroredThreadIds.add(normalizedThreadId);
3692
+ if (!normalizedThreadId || this.mirrorStateByThreadId.get(normalizedThreadId) === "mirrored") return;
3693
+ this.mirrorStateByThreadId.set(normalizedThreadId, "mirrored");
5848
3694
  const prompt = trimOptional(readString$3(item, "prompt"));
5849
3695
  const runId = codexNativeSubagentRunId(normalizedThreadId);
5850
3696
  const createdAt = this.now();
@@ -5861,16 +3707,14 @@ var CodexNativeSubagentTaskMirror = class {
5861
3707
  lastEventAt: createdAt,
5862
3708
  progressSummary: "Codex native subagent spawned."
5863
3709
  })) {
5864
- this.mirroredThreadIds.delete(normalizedThreadId);
5865
- this.failedMirrorThreadIds.add(normalizedThreadId);
3710
+ this.mirrorStateByThreadId.set(normalizedThreadId, "failed");
5866
3711
  return;
5867
3712
  }
5868
- this.failedMirrorThreadIds.delete(normalizedThreadId);
5869
3713
  this.terminalRunIds.delete(runId);
5870
3714
  this.authoritativeRunIds.delete(runId);
5871
3715
  }
5872
3716
  applyCollabAgentStatus(threadId, status, message) {
5873
- if (!this.mirroredThreadIds.has(threadId) && this.failedMirrorThreadIds.has(threadId)) return;
3717
+ if (this.mirrorStateByThreadId.get(threadId) === "failed") return;
5874
3718
  const normalizedStatus = normalizeAgentStateStatus(status);
5875
3719
  if (!normalizedStatus) return;
5876
3720
  const runId = codexNativeSubagentRunId(threadId);
@@ -6068,6 +3912,7 @@ function registerCodexNativeSubagentMonitor(params) {
6068
3912
  taskRuntimeScope: params.taskRuntimeScope,
6069
3913
  agentId: params.agentId
6070
3914
  });
3915
+ return monitor;
6071
3916
  }
6072
3917
  /** Tracks native subagent thread notifications, transcript completions, and task delivery. */
6073
3918
  var CodexNativeSubagentMonitor = class {
@@ -6094,6 +3939,13 @@ var CodexNativeSubagentMonitor = class {
6094
3939
  this.childThreadIdsByAgentPath.clear();
6095
3940
  this.transcriptPathsByChildThreadId.clear();
6096
3941
  }
3942
+ deferUntilParentSettles(parentThreadId, callback) {
3943
+ const normalizedParentThreadId = parentThreadId.trim();
3944
+ const state = this.parentStates.get(normalizedParentThreadId);
3945
+ if (!state || !this.hasUnsettledChildren(normalizedParentThreadId)) return false;
3946
+ state.deferredSettlement = callback;
3947
+ return true;
3948
+ }
6097
3949
  configure(options) {
6098
3950
  const codexHome = normalizeOptionalString(options.codexHome);
6099
3951
  if (codexHome) this.codexHome = codexHome;
@@ -6131,10 +3983,29 @@ var CodexNativeSubagentMonitor = class {
6131
3983
  error: formatErrorMessage(error)
6132
3984
  });
6133
3985
  }
3986
+ this.markChildTurnStarted(notification);
3987
+ await this.handleChildSystemError(notification);
6134
3988
  this.captureChildAssistantMessage(notification);
6135
3989
  await this.handleChildTurnCompletion(notification);
6136
3990
  await this.handleCompletionNotification(notification);
6137
3991
  }
3992
+ markChildTurnStarted(notification) {
3993
+ if (notification.method !== "turn/started") return;
3994
+ const childThreadId = readString$2(isJsonObject(notification.params) ? notification.params : void 0, "threadId")?.trim();
3995
+ const childState = childThreadId ? this.childStates.get(childThreadId) : void 0;
3996
+ if (childState) childState.settledWithoutCompletion = false;
3997
+ }
3998
+ async handleChildSystemError(notification) {
3999
+ if (notification.method !== "thread/status/changed") return;
4000
+ const params = isJsonObject(notification.params) ? notification.params : void 0;
4001
+ if (readString$2(isJsonObject(params?.status) ? params.status : void 0, "type") !== "systemError") return;
4002
+ const childThreadId = readString$2(params, "threadId")?.trim();
4003
+ const childState = childThreadId ? this.childStates.get(childThreadId) : void 0;
4004
+ if (childState) {
4005
+ childState.settledWithoutCompletion = true;
4006
+ await this.flushDeferredParentSettlements(childState.parentThreadId);
4007
+ }
4008
+ }
6138
4009
  ensureParentTaskRuntime(state) {
6139
4010
  if (state.taskRuntime || !state.requesterSessionKey || !state.taskRuntimeScope) return;
6140
4011
  state.taskRuntime = this.runtime.createAgentHarnessTaskRuntime({
@@ -6171,7 +4042,7 @@ var CodexNativeSubagentMonitor = class {
6171
4042
  const parentThreadId = item ? (readString$2(item, "senderThreadId") ?? readString$2(params, "threadId"))?.trim() : void 0;
6172
4043
  const state = parentThreadId ? this.parentStates.get(parentThreadId) : void 0;
6173
4044
  if (state && parentThreadId) {
6174
- const childThreadIds = normalizeToolName(readString$2(item, "tool")) === "spawnagent" ? new Set([...readStringArray(item?.receiverThreadIds), ...readObjectStringKeys(item?.agentsStates)]) : new Set(readStringArray(item?.receiverThreadIds));
4045
+ const childThreadIds = normalizeToolName(readString$2(item, "tool")) === "spawnagent" ? /* @__PURE__ */ new Set([...readStringArray(item?.receiverThreadIds), ...readObjectStringKeys(item?.agentsStates)]) : new Set(readStringArray(item?.receiverThreadIds));
6175
4046
  for (const childThreadId of childThreadIds) this.registerChildThread(parentThreadId, childThreadId);
6176
4047
  }
6177
4048
  return state;
@@ -6257,6 +4128,8 @@ var CodexNativeSubagentMonitor = class {
6257
4128
  if (childState && turn && readString$2(turn, "status") === "interrupted") {
6258
4129
  const turnId = readString$2(turn, "id");
6259
4130
  if (turnId) childState.assistantMessagesByTurn.delete(turnId);
4131
+ childState.settledWithoutCompletion = true;
4132
+ await this.flushDeferredParentSettlements(childState.parentThreadId);
6260
4133
  return;
6261
4134
  }
6262
4135
  if (childState && turn) this.captureChildTurnAssistantMessages(childState, turn);
@@ -6310,7 +4183,10 @@ var CodexNativeSubagentMonitor = class {
6310
4183
  childState.noFinalCompletionFallbackTimer = void 0;
6311
4184
  }
6312
4185
  }
6313
- if (!state.requesterSessionKey) return;
4186
+ if (!state.requesterSessionKey) {
4187
+ await this.flushDeferredParentSettlements(state.parentThreadId);
4188
+ return;
4189
+ }
6314
4190
  const completionKey = buildCompletionDedupeKey(state.parentThreadId, completion);
6315
4191
  if (state.deliveredCompletionKeys.has(completionKey)) return;
6316
4192
  const deliveryState = childState ?? this.ensureChildState(state.parentThreadId, completion.childThreadId);
@@ -6363,6 +4239,7 @@ var CodexNativeSubagentMonitor = class {
6363
4239
  });
6364
4240
  } finally {
6365
4241
  childState.deliveringCompletionKey = void 0;
4242
+ await this.flushDeferredParentSettlements(state.parentThreadId);
6366
4243
  }
6367
4244
  }
6368
4245
  markCompletionDeliveryPending(completion, error) {
@@ -6395,6 +4272,28 @@ var CodexNativeSubagentMonitor = class {
6395
4272
  }, delayMs);
6396
4273
  unrefTimer(childState.completionDeliveryTimer);
6397
4274
  }
4275
+ hasUnsettledChildren(parentThreadId) {
4276
+ for (const childState of this.childStates.values()) if (childState.parentThreadId === parentThreadId && (childState.pendingCompletion !== void 0 || childState.deliveringCompletionKey !== void 0 || !childState.transcriptTerminal && !childState.settledWithoutCompletion)) return true;
4277
+ return false;
4278
+ }
4279
+ async flushDeferredParentSettlements(parentThreadId) {
4280
+ if (this.hasUnsettledChildren(parentThreadId)) return;
4281
+ const state = this.parentStates.get(parentThreadId);
4282
+ const callback = state?.deferredSettlement;
4283
+ if (!state || !callback) return;
4284
+ state.deferredSettlement = void 0;
4285
+ await this.runDeferredParentSettlement(parentThreadId, callback);
4286
+ }
4287
+ async runDeferredParentSettlement(parentThreadId, callback) {
4288
+ try {
4289
+ await callback();
4290
+ } catch (error) {
4291
+ embeddedAgentLog.warn("Failed to finish deferred Codex app-server cleanup", {
4292
+ parentThreadId,
4293
+ error: formatErrorMessage(error)
4294
+ });
4295
+ }
4296
+ }
6398
4297
  finalizeCompletionTask(completion, eventAt) {
6399
4298
  const taskRuntime = this.getTaskRuntimeForChild(completion.childThreadId);
6400
4299
  if (!taskRuntime) return;
@@ -6435,7 +4334,8 @@ var CodexNativeSubagentMonitor = class {
6435
4334
  assistantMessagesByTurn: /* @__PURE__ */ new Map(),
6436
4335
  transcriptPollAttempt: 0,
6437
4336
  transcriptTerminal: false,
6438
- completionDeliveryAttempt: 0
4337
+ completionDeliveryAttempt: 0,
4338
+ settledWithoutCompletion: false
6439
4339
  };
6440
4340
  this.childStates.set(normalizedChildThreadId, childState);
6441
4341
  }
@@ -6874,7 +4774,8 @@ function parseCodexAppServerByteLimit(value) {
6874
4774
  if (!match) return;
6875
4775
  const amount = Number(match[1]);
6876
4776
  if (!Number.isFinite(amount) || amount <= 0) return;
6877
- const multiplier = CODEX_APP_SERVER_BYTE_UNITS[(match[2] ?? "b").toLowerCase()];
4777
+ const unit = (match[2] ?? "b").toLowerCase();
4778
+ const multiplier = CODEX_APP_SERVER_BYTE_UNITS[unit];
6878
4779
  if (multiplier === void 0) return;
6879
4780
  return Math.max(1, Math.round(amount * multiplier));
6880
4781
  }
@@ -7058,7 +4959,10 @@ async function rotateOversizedCodexAppServerStartupBinding(params) {
7058
4959
  bytes: file.bytes
7059
4960
  }))
7060
4961
  });
7061
- await clearCodexAppServerBinding(params.sessionFile);
4962
+ await params.bindingStore.mutate(params.identity, {
4963
+ kind: "clear",
4964
+ threadId: binding.threadId
4965
+ });
7062
4966
  return;
7063
4967
  }
7064
4968
  }
@@ -7087,7 +4991,10 @@ async function rotateOversizedCodexAppServerStartupBinding(params) {
7087
4991
  reserveTokens,
7088
4992
  projectedTurnTokens: params.projectedTurnTokens
7089
4993
  });
7090
- await clearCodexAppServerBinding(params.sessionFile);
4994
+ await params.bindingStore.mutate(params.identity, {
4995
+ kind: "clear",
4996
+ threadId: binding.threadId
4997
+ });
7091
4998
  return;
7092
4999
  }
7093
5000
  if (compaction?.truncateAfterCompaction !== true) return binding;
@@ -7371,7 +5278,7 @@ async function markCodexAuthProfileBlockedFromRateLimits(params) {
7371
5278
  async function formatCodexTurnStartUsageLimitError(params) {
7372
5279
  return refreshCodexUsageLimitError({
7373
5280
  client: params.client,
7374
- source: readCodexTurnStartUsageLimitErrorSource(params.error, params.pendingNotifications),
5281
+ source: readCodexTurnStartUsageLimitErrorSource(params.client, params.error, params.errorNotification, params.rateLimitsRevisionBeforeTurnStart),
7375
5282
  timeoutMs: params.timeoutMs,
7376
5283
  signal: params.signal
7377
5284
  });
@@ -7384,7 +5291,7 @@ async function refreshCodexUsageLimitPromptError(params) {
7384
5291
  source: {
7385
5292
  message: params.message,
7386
5293
  codexErrorInfo: "usageLimitExceeded",
7387
- rateLimits: readRecentCodexRateLimits()
5294
+ rateLimits: readRecentCodexRateLimits(params.client)
7388
5295
  },
7389
5296
  timeoutMs: params.timeoutMs,
7390
5297
  signal: params.signal
@@ -7422,7 +5329,7 @@ async function readCodexRateLimitsFromAppServerForUsageLimitError(params) {
7422
5329
  timeoutMs: resolveCodexUsageLimitRateLimitRefreshTimeoutMs(params.timeoutMs),
7423
5330
  signal: params.signal
7424
5331
  });
7425
- rememberCodexRateLimits(rateLimits);
5332
+ rememberCodexRateLimitsRead(params.client, rateLimits);
7426
5333
  return rateLimits;
7427
5334
  } catch (error) {
7428
5335
  embeddedAgentLog.debug("codex app-server rate-limit refresh failed after usage-limit error", { error: formatErrorMessage(error) });
@@ -7433,38 +5340,25 @@ function resolveCodexUsageLimitRateLimitRefreshTimeoutMs(timeoutMs) {
7433
5340
  if (timeoutMs === void 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) return CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS;
7434
5341
  return Math.max(100, Math.min(timeoutMs, CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS));
7435
5342
  }
7436
- function readCodexTurnStartUsageLimitErrorSource(error, pendingNotifications) {
7437
- const notificationError = readLatestCodexErrorNotification(pendingNotifications);
7438
- const notificationRateLimits = readLatestRateLimitNotificationPayload(pendingNotifications);
5343
+ function readCodexTurnStartUsageLimitErrorSource(client, error, errorNotification, rateLimitsRevisionBeforeTurnStart) {
5344
+ const notificationError = readCodexErrorNotification(errorNotification);
7439
5345
  const errorPayload = readCodexErrorPayload(error);
7440
- const rateLimits = notificationRateLimits ?? errorPayload.rateLimits ?? readRecentCodexRateLimits();
5346
+ const rateLimits = errorPayload.rateLimits ?? readRecentCodexRateLimits(client);
5347
+ const cacheUpdatedDuringTurnStart = rateLimitsRevisionBeforeTurnStart !== void 0 && readCodexRateLimitsRevision(client) > rateLimitsRevisionBeforeTurnStart;
7441
5348
  return {
7442
5349
  message: notificationError?.message ?? errorPayload.message ?? formatErrorMessage(error),
7443
5350
  codexErrorInfo: notificationError?.codexErrorInfo ?? errorPayload.codexErrorInfo,
7444
5351
  rateLimits,
7445
- rateLimitsTrustedForProfile: notificationRateLimits !== void 0 || errorPayload.rateLimits !== void 0
5352
+ rateLimitsTrustedForProfile: errorPayload.rateLimits !== void 0 || cacheUpdatedDuringTurnStart
7446
5353
  };
7447
5354
  }
7448
- function readLatestRateLimitNotificationPayload(notifications) {
7449
- for (let index = notifications.length - 1; index >= 0; index -= 1) {
7450
- const notification = notifications[index];
7451
- if (notification?.method === "account/rateLimits/updated") {
7452
- rememberCodexRateLimits(notification.params);
7453
- return notification.params;
7454
- }
7455
- }
7456
- }
7457
- function readLatestCodexErrorNotification(notifications) {
7458
- for (let index = notifications.length - 1; index >= 0; index -= 1) {
7459
- const notification = notifications[index];
7460
- if (notification?.method !== "error" || !isJsonObject(notification.params)) continue;
7461
- const error = notification.params.error;
7462
- if (!isJsonObject(error)) continue;
7463
- return {
7464
- message: readString$1(error, "message"),
7465
- codexErrorInfo: error.codexErrorInfo
7466
- };
7467
- }
5355
+ function readCodexErrorNotification(notification) {
5356
+ if (notification?.method !== "error" || !isJsonObject(notification.params)) return;
5357
+ const error = notification.params.error;
5358
+ return isJsonObject(error) ? {
5359
+ message: readString$1(error, "message"),
5360
+ codexErrorInfo: error.codexErrorInfo
5361
+ } : void 0;
7468
5362
  }
7469
5363
  function readCodexErrorPayload(error) {
7470
5364
  const message = error instanceof Error ? error.message : void 0;
@@ -7473,7 +5367,6 @@ function readCodexErrorPayload(error) {
7473
5367
  if (!isJsonObject(data)) return { message };
7474
5368
  const nestedError = isJsonObject(data.error) ? data.error : data;
7475
5369
  const rateLimits = nestedError.rateLimits ?? data.rateLimits;
7476
- if (rateLimits !== void 0) rememberCodexRateLimits(rateLimits);
7477
5370
  return {
7478
5371
  message: readString$1(nestedError, "message") ?? message,
7479
5372
  codexErrorInfo: nestedError.codexErrorInfo,
@@ -7613,7 +5506,10 @@ function readRequestId(record) {
7613
5506
  //#region extensions/codex/src/app-server/run-attempt.ts
7614
5507
  const CODEX_NATIVE_HOOK_RELAY_RENEW_INTERVAL_MS = 6e4;
7615
5508
  const CODEX_APP_SERVER_PROJECTED_CHARS_PER_TOKEN = 4;
7616
- const CODEX_APP_SERVER_ACTIVE_NATIVE_TURN_WAIT_TIMEOUT_MS = 3e4;
5509
+ function shouldKeepCodexSharedAbortOpen(params) {
5510
+ if (params.explicitCancellationObserved || params.result.aborted || params.result.externalAbort) return false;
5511
+ return params.trigger === "memory" || !params.attemptSucceeded;
5512
+ }
7617
5513
  const ensuredCodexWorkspaceDirs = /* @__PURE__ */ new Set();
7618
5514
  function withCodexAppServerFastModeServiceTier(appServer, params) {
7619
5515
  const fastMode = typeof params.fastMode === "function" ? params.fastMode() : params.fastMode;
@@ -7697,7 +5593,7 @@ function toTranscriptToolResultContentItem(item) {
7697
5593
  }
7698
5594
  function formatUnsupportedCodexDynamicToolOutput(type) {
7699
5595
  const rawType = typeof type === "string" ? type.replace(/\s+/g, " ").trim() : "";
7700
- return `[Unsupported Codex dynamic tool output: ${rawType ? rawType.slice(0, 80) : "unknown"}${rawType.length > 80 ? "..." : ""}]`;
5596
+ return `[Unsupported Codex dynamic tool output: ${rawType ? truncateUtf16Safe(rawType, 80) : "unknown"}${rawType.length > 80 ? "..." : ""}]`;
7701
5597
  }
7702
5598
  function shouldAwaitCodexAgentEndHook(params) {
7703
5599
  return !params.messageChannel && !params.messageProvider;
@@ -7716,7 +5612,7 @@ async function runCodexAgentEndHook(params, hookParams) {
7716
5612
  }
7717
5613
  runAgentEndSideEffects(sideEffectParams);
7718
5614
  }
7719
- async function runCodexAppServerAttempt(params, options = {}) {
5615
+ async function runCodexAppServerAttempt(params, options) {
7720
5616
  const attemptStartedAt = Date.now();
7721
5617
  const profilerEnabled = isCodexAppServerProfilerEnabled(params.config);
7722
5618
  const codexModelCallTrace = freezeDiagnosticTraceContext(createDiagnosticTraceContextFromActiveScope());
@@ -7728,7 +5624,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
7728
5624
  resetAnnounced: false
7729
5625
  };
7730
5626
  const preDynamicStartupStages = createCodexDynamicToolBuildStageTracker({ enabled: profilerEnabled });
7731
- const attemptClientFactory = options.clientFactory ?? defaultLeasedCodexAppServerClientFactory;
5627
+ const attemptClientFactory = options.clientFactory ?? getLeasedSharedCodexAppServerClient;
7732
5628
  const pluginConfig = readCodexPluginConfig(options.pluginConfig);
7733
5629
  const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
7734
5630
  const { sessionAgentId } = resolveSessionAgentIds$1({
@@ -7756,10 +5652,17 @@ async function runCodexAppServerAttempt(params, options = {}) {
7756
5652
  agentId: sessionAgentId
7757
5653
  });
7758
5654
  const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId);
5655
+ const bindingIdentity = sessionBindingIdentity({
5656
+ sessionId: params.sessionId,
5657
+ sessionKey: params.sessionKey,
5658
+ agentId: params.agentId,
5659
+ config: params.config
5660
+ });
5661
+ const bindingStore = options.bindingStore;
7759
5662
  preDynamicStartupStages.mark("session-agent");
7760
5663
  const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
7761
5664
  const isInactiveThreadBootstrapBinding = (binding) => !activeContextEngine && binding?.contextEngine?.projection?.mode === "thread_bootstrap";
7762
- let startupBinding = await readCodexAppServerBinding(params.sessionFile);
5665
+ let startupBinding = await bindingStore.read(bindingIdentity);
7763
5666
  preDynamicStartupStages.mark("read-binding");
7764
5667
  const startupBindingAuthProfileId = startupBinding?.authProfileId;
7765
5668
  const initialStartupBindingHadInactiveThreadBootstrap = isInactiveThreadBootstrapBinding(startupBinding);
@@ -7831,13 +5734,35 @@ async function runCodexAppServerAttempt(params, options = {}) {
7831
5734
  });
7832
5735
  preDynamicStartupStages.mark("native-hook-relay");
7833
5736
  const runAbortController = new AbortController();
5737
+ let explicitCancellationObserved = false;
5738
+ let explicitCancellationReason;
5739
+ let terminalOutcomeFrozen = false;
5740
+ let sharedAbortAllowedAfterTerminalOutcome = false;
5741
+ let attemptAbortNotified = false;
5742
+ const notifyAttemptAbort = () => {
5743
+ if (attemptAbortNotified) return;
5744
+ attemptAbortNotified = true;
5745
+ params.onAttemptAbort?.();
5746
+ };
5747
+ const abortExplicitly = (reason) => {
5748
+ if (terminalOutcomeFrozen) {
5749
+ if (sharedAbortAllowedAfterTerminalOutcome) notifyAttemptAbort();
5750
+ return;
5751
+ }
5752
+ notifyAttemptAbort();
5753
+ explicitCancellationObserved = true;
5754
+ explicitCancellationReason ??= reason;
5755
+ runAbortController.abort(reason);
5756
+ };
7834
5757
  const abortFromUpstream = () => {
7835
- runAbortController.abort(params.abortSignal?.reason ?? "upstream_abort");
5758
+ abortExplicitly(params.abortSignal?.reason ?? "upstream_abort");
7836
5759
  };
7837
5760
  if (params.abortSignal?.aborted) abortFromUpstream();
7838
5761
  else params.abortSignal?.addEventListener("abort", abortFromUpstream, { once: true });
7839
5762
  startupBinding = await rotateOversizedCodexAppServerStartupBinding({
7840
5763
  binding: startupBinding,
5764
+ bindingStore,
5765
+ identity: bindingIdentity,
7841
5766
  sessionFile: params.sessionFile,
7842
5767
  agentDir,
7843
5768
  codexHome: appServer.start.env?.CODEX_HOME,
@@ -8043,6 +5968,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
8043
5968
  hookContext: {
8044
5969
  agentId: sessionAgentId,
8045
5970
  config: params.config,
5971
+ workspaceDir: effectiveWorkspace,
8046
5972
  sessionId: params.sessionId,
8047
5973
  sessionKey: sandboxSessionKey,
8048
5974
  runId: params.runId,
@@ -8291,8 +6217,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8291
6217
  codexTurnPromptText = decorateCodexTurnPromptText(nextPromptBuild);
8292
6218
  };
8293
6219
  const selectNewerVisibleHistoryAfterBinding = (binding) => {
8294
- const bindingUpdatedAt = Date.parse(binding.updatedAt);
8295
- if (!Number.isFinite(bindingUpdatedAt)) return [];
6220
+ const historyCoveredThrough = Date.parse(binding.historyCoveredThrough ?? "");
6221
+ const cutoff = Number.isFinite(historyCoveredThrough) ? historyCoveredThrough : 0;
8296
6222
  return historyMessages.filter((message) => {
8297
6223
  if (message.role !== "user" && message.role !== "assistant") return false;
8298
6224
  const record = message;
@@ -8300,9 +6226,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
8300
6226
  if (typeof idempotencyKey === "string" && idempotencyKey.startsWith("codex-app-server:")) return false;
8301
6227
  const meta = record["__openclaw"];
8302
6228
  const mirrorIdentity = meta && typeof meta === "object" && !Array.isArray(meta) ? meta.mirrorIdentity : void 0;
6229
+ if ((meta && typeof meta === "object" && !Array.isArray(meta) ? meta.mirrorOrigin : void 0) === "codex-app-server") return false;
8303
6230
  if (typeof mirrorIdentity === "string" && mirrorIdentity.startsWith("codex-app-server:")) return false;
8304
6231
  const timestamp = typeof message.timestamp === "number" ? message.timestamp : typeof message.timestamp === "string" ? Date.parse(message.timestamp) : NaN;
8305
- return Number.isFinite(timestamp) && timestamp > bindingUpdatedAt;
6232
+ return Number.isFinite(timestamp) && timestamp > cutoff;
8306
6233
  });
8307
6234
  };
8308
6235
  const applyResumeStaleBindingContinuityProjection = (binding) => {
@@ -8354,6 +6281,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8354
6281
  });
8355
6282
  startupBinding = await rotateOversizedCodexAppServerStartupBinding({
8356
6283
  binding: startupBinding,
6284
+ bindingStore,
6285
+ identity: bindingIdentity,
8357
6286
  sessionFile: params.sessionFile,
8358
6287
  agentDir,
8359
6288
  codexHome: appServer.start.env?.CODEX_HOME,
@@ -8401,11 +6330,41 @@ async function runCodexAppServerAttempt(params, options = {}) {
8401
6330
  });
8402
6331
  let client;
8403
6332
  let thread;
6333
+ let turnRouter;
6334
+ let turnRoute;
6335
+ let routeActivated = false;
6336
+ let detachRouteAbort = () => void 0;
8404
6337
  let trajectoryEndRecorded = false;
8405
6338
  const markTrajectoryEndRecorded = () => {
8406
6339
  trajectoryEndRecorded = true;
8407
6340
  };
8408
6341
  let nativeHookRelay;
6342
+ const nativeSubagentMonitorRef = {};
6343
+ const pendingNativePreToolUseFailures = [];
6344
+ const projectorRef = {};
6345
+ let nativePreToolUseFailureFallbackActive = false;
6346
+ let nativePreToolUseFailureFallbackTerminalReason;
6347
+ const emitNativePreToolUseFailure = (failure) => {
6348
+ emitCodexNativePreToolUseFailureDiagnostic({
6349
+ agentId: sessionAgentId,
6350
+ sessionId: params.sessionId,
6351
+ sessionKey: sandboxSessionKey,
6352
+ runId: params.runId,
6353
+ signal: runAbortController.signal,
6354
+ failure,
6355
+ ...nativePreToolUseFailureFallbackActive ? { terminalReason: nativePreToolUseFailureFallbackTerminalReason ?? failure.disposition } : {}
6356
+ });
6357
+ };
6358
+ const flushPendingNativePreToolUseFailures = () => {
6359
+ for (const failure of pendingNativePreToolUseFailures.splice(0)) emitNativePreToolUseFailure(failure);
6360
+ };
6361
+ const activateNativePreToolUseFailureFallback = () => {
6362
+ if (!nativePreToolUseFailureFallbackActive) {
6363
+ nativePreToolUseFailureFallbackTerminalReason = runAbortController.signal.aborted ? resolveCodexToolAbortTerminalReason(runAbortController.signal) : void 0;
6364
+ nativePreToolUseFailureFallbackActive = true;
6365
+ }
6366
+ flushPendingNativePreToolUseFailures();
6367
+ };
8409
6368
  let releaseSharedClientLease;
8410
6369
  let sharedCodexClientRetiredForOneShotCleanup = false;
8411
6370
  const releaseSharedClientLeaseOnce = () => {
@@ -8443,6 +6402,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
8443
6402
  await releaseCodexSandboxExecServerEnvironment(sandbox);
8444
6403
  }
8445
6404
  };
6405
+ const releaseCurrentRoute = () => {
6406
+ detachRouteAbort();
6407
+ detachRouteAbort = () => void 0;
6408
+ turnRoute?.release();
6409
+ turnRoute = void 0;
6410
+ routeActivated = false;
6411
+ };
8446
6412
  let codexEnvironmentSelection;
8447
6413
  let codexExecutionCwd = effectiveCwd;
8448
6414
  let codexSandboxPolicy;
@@ -8467,7 +6433,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
8467
6433
  attemptTimeoutMs: params.timeoutMs,
8468
6434
  startupTimeoutMs,
8469
6435
  turnStartTimeoutMs: params.timeoutMs,
8470
- signal: runAbortController.signal
6436
+ signal: runAbortController.signal,
6437
+ onPreToolUseFailure: (failure) => {
6438
+ const projector = projectorRef.current;
6439
+ if (projector) projector.recordNativeToolPreToolUseFailure(failure);
6440
+ else if (nativePreToolUseFailureFallbackActive) emitNativePreToolUseFailure(failure);
6441
+ else pendingNativePreToolUseFailures.push(failure);
6442
+ }
8471
6443
  });
8472
6444
  return {
8473
6445
  configPatch: nativeHookRelay ? buildCodexNativeHookRelayConfig({
@@ -8487,6 +6459,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
8487
6459
  pluginAppServer = attemptAppServer;
8488
6460
  const startupResult = await startCodexAttemptThread({
8489
6461
  attemptClientFactory,
6462
+ bindingStore,
8490
6463
  appServer: attemptAppServer,
8491
6464
  pluginConfig,
8492
6465
  computerUseConfig,
@@ -8519,6 +6492,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8519
6492
  });
8520
6493
  client = startupResult.client;
8521
6494
  thread = startupResult.thread;
6495
+ turnRouter = startupResult.turnRouter;
6496
+ turnRoute = startupResult.turnRoute;
8522
6497
  pluginAppServer = startupResult.pluginAppServer;
8523
6498
  if (thread.lifecycle.action === "started") {
8524
6499
  const activeThreadReviewerPolicyContext = resolveCodexModelBackedReviewerPolicyContext({
@@ -8574,6 +6549,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8574
6549
  }
8575
6550
  });
8576
6551
  } catch (error) {
6552
+ activateNativePreToolUseFailureFallback();
6553
+ releaseCurrentRoute();
8577
6554
  nativeHookRelay?.unregister();
8578
6555
  await releaseSandboxExecEnvironment();
8579
6556
  params.abortSignal?.removeEventListener("abort", abortFromUpstream);
@@ -8594,7 +6571,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8594
6571
  prompt: codexTurnPromptText,
8595
6572
  tools: toolBridge.availableSpecs
8596
6573
  });
8597
- const pendingNotifications = [];
6574
+ let latestStartupErrorNotification;
6575
+ let rateLimitsRevisionBeforeLastTurnStart;
8598
6576
  let completed = false;
8599
6577
  let terminalTurnNotificationQueued = false;
8600
6578
  let timedOut = false;
@@ -8614,7 +6592,6 @@ async function runCodexAppServerAttempt(params, options = {}) {
8614
6592
  const completion = new Promise((resolve) => {
8615
6593
  resolveCompletion = resolve;
8616
6594
  });
8617
- let notificationQueue = Promise.resolve();
8618
6595
  const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs(options.turnCompletionIdleTimeoutMs ?? appServer.turnCompletionIdleTimeoutMs);
8619
6596
  const turnAssistantCompletionIdleTimeoutMs = resolveCodexTurnAssistantCompletionIdleTimeoutMs(options.turnAssistantCompletionIdleTimeoutMs);
8620
6597
  const postToolRawAssistantCompletionIdleTimeoutMs = resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs(options.postToolRawAssistantCompletionIdleTimeoutMs ?? appServer.postToolRawAssistantCompletionIdleTimeoutMs, turnAssistantCompletionIdleTimeoutMs);
@@ -8625,12 +6602,15 @@ async function runCodexAppServerAttempt(params, options = {}) {
8625
6602
  const pendingOpenClawDynamicToolCompletionIds = /* @__PURE__ */ new Set();
8626
6603
  const activeTurnItemIds = /* @__PURE__ */ new Set();
8627
6604
  const activeCompletionBlockerItemIds = /* @__PURE__ */ new Set();
6605
+ const activeFinalizationHookRunIds = /* @__PURE__ */ new Set();
6606
+ const finalizationHookBatchStatuses = /* @__PURE__ */ new Map();
6607
+ let unsettledFinalizationHookCount = 0;
6608
+ let rejectedFinalizationHookAssistant;
8628
6609
  let turnCrossedToolHandoff = false;
8629
6610
  let pendingTerminalDynamicToolRelease;
8630
6611
  let terminalDynamicToolReleaseCheckScheduled = false;
8631
6612
  let currentTurnHadNonTerminalDynamicToolResult = false;
8632
6613
  const turnIdRef = {};
8633
- const projectorRef = {};
8634
6614
  const userInputBridgeRef = {};
8635
6615
  const steeringQueueRef = {};
8636
6616
  const renewNativeHookRelayForTurnProgress = () => {
@@ -8656,6 +6636,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8656
6636
  getActiveAppServerTurnRequests: () => activeAppServerTurnRequests,
8657
6637
  getActiveTurnItemCount: () => activeTurnItemIds.size,
8658
6638
  getActiveCompletionBlockerItemCount: () => activeCompletionBlockerItemIds.size,
6639
+ getActiveFinalizationHookCount: () => unsettledFinalizationHookCount,
6640
+ canReleaseAssistantCompletionIdle: () => projectorRef.current?.hasLatestTerminalAssistantCandidateText() === true,
8659
6641
  turnCompletionIdleTimeoutMs,
8660
6642
  turnAssistantCompletionIdleTimeoutMs,
8661
6643
  turnAttemptIdleTimeoutMs,
@@ -8780,6 +6762,21 @@ async function runCodexAppServerAttempt(params, options = {}) {
8780
6762
  });
8781
6763
  lifecycleTerminalEmitted = true;
8782
6764
  };
6765
+ const buildLifecycleTerminalMeta = (input) => {
6766
+ const abortFields = input.aborted ? resolveAgentRunAbortLifecycleFields(runAbortController.signal) : void 0;
6767
+ if (input.timedOut || abortFields?.stopReason === "timeout") return {
6768
+ aborted: true,
6769
+ status: "timed_out",
6770
+ stopReason: "timeout",
6771
+ timeoutPhase: "provider",
6772
+ providerStarted: true
6773
+ };
6774
+ return input.aborted ? {
6775
+ aborted: true,
6776
+ status: "cancelled",
6777
+ stopReason: "stop"
6778
+ } : void 0;
6779
+ };
8783
6780
  const executionPhaseKeys = /* @__PURE__ */ new Set();
8784
6781
  const emitExecutionPhaseOnce = (key, info) => {
8785
6782
  if (executionPhaseKeys.has(key)) return;
@@ -8857,7 +6854,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
8857
6854
  const steeringQueue = steeringQueueRef.current;
8858
6855
  userInputBridge?.handleNotification(notification);
8859
6856
  if (!projector || !turnId) {
8860
- pendingNotifications.push(notification);
6857
+ if (notification.method === "error") latestStartupErrorNotification = notification;
8861
6858
  return;
8862
6859
  }
8863
6860
  const notificationState = applyCodexTurnNotificationState({
@@ -8876,10 +6873,23 @@ async function runCodexAppServerAttempt(params, options = {}) {
8876
6873
  onReportExecutionNotification: reportExecutionNotification
8877
6874
  });
8878
6875
  turnCrossedToolHandoff = notificationState.turnCrossedToolHandoff;
6876
+ const finalizationHookNotification = readCodexFinalizationHookNotification(notification, thread.threadId, turnId);
6877
+ if (finalizationHookNotification?.phase === "started") {
6878
+ if (activeFinalizationHookRunIds.size === 0) finalizationHookBatchStatuses.clear();
6879
+ activeFinalizationHookRunIds.add(finalizationHookNotification.runId);
6880
+ turnWatches.disarmAssistantCompletionIdleWatch();
6881
+ }
8879
6882
  if (notificationState.isTurnTerminal) terminalTurnNotificationQueued = true;
8880
6883
  try {
8881
6884
  await waitForCodexNotificationDispatchTurn();
8882
6885
  await projector.handleNotification(notification);
6886
+ const projectedAssistantCompletionCanRelease = isAssistantCompletionReleaseNotification(notification, turnCrossedToolHandoff) || notificationState.isCurrentTurnNotification && turnCrossedToolHandoff && notification.method === "rawResponseItem/completed" && projector.canReleaseLatestTerminalAssistantAfterToolHandoff();
6887
+ if (notificationState.isCurrentTurnNotification && projectedAssistantCompletionCanRelease) {
6888
+ const completedAssistantItemId = projector.getLatestTerminalAssistantCandidate()?.itemId;
6889
+ if (rejectedFinalizationHookAssistant !== void 0 && completedAssistantItemId !== void 0 && completedAssistantItemId !== rejectedFinalizationHookAssistant.itemId) rejectedFinalizationHookAssistant = void 0;
6890
+ else if (rejectedFinalizationHookAssistant !== void 0) turnWatches.disarmAssistantCompletionIdleWatch();
6891
+ else if (activeFinalizationHookRunIds.size === 0 && !terminalTurnNotificationQueued && activeAppServerTurnRequests === 0 && activeTurnItemIds.size === 0 && activeCompletionBlockerItemIds.size === 0 && pendingOpenClawDynamicToolCompletionIds.size === 0 && projector.hasLatestTerminalAssistantCandidateText()) turnWatches.armAssistantCompletionIdleWatch(describeNotificationActivity(notification));
6892
+ }
8883
6893
  if (notificationState.isCurrentTurnNotification && activeTurnItemIds.size === 0 && isRawFunctionToolOutputCompletionNotification(notification)) await maybeAnnounceFastModeAutoOff();
8884
6894
  } catch (error) {
8885
6895
  embeddedAgentLog.debug("codex app-server projector notification threw", {
@@ -8887,6 +6897,24 @@ async function runCodexAppServerAttempt(params, options = {}) {
8887
6897
  error
8888
6898
  });
8889
6899
  } finally {
6900
+ if (finalizationHookNotification?.phase === "completed") {
6901
+ unsettledFinalizationHookCount = Math.max(0, unsettledFinalizationHookCount - 1);
6902
+ activeFinalizationHookRunIds.delete(finalizationHookNotification.runId);
6903
+ finalizationHookBatchStatuses.set(finalizationHookNotification.runId, finalizationHookNotification.status);
6904
+ if (activeFinalizationHookRunIds.size === 0) {
6905
+ const statuses = new Set(finalizationHookBatchStatuses.values());
6906
+ if (statuses.has("blocked") && !statuses.has("stopped")) {
6907
+ const itemId = projector.getLatestTerminalAssistantCandidate()?.itemId;
6908
+ rejectedFinalizationHookAssistant = itemId ? { itemId } : {};
6909
+ turnWatches.disarmAssistantCompletionIdleWatch();
6910
+ } else rejectedFinalizationHookAssistant = void 0;
6911
+ }
6912
+ if (activeFinalizationHookRunIds.size === 0 && rejectedFinalizationHookAssistant === void 0 && !terminalTurnNotificationQueued && activeAppServerTurnRequests === 0 && activeTurnItemIds.size === 0 && activeCompletionBlockerItemIds.size === 0 && pendingOpenClawDynamicToolCompletionIds.size === 0 && projector.hasLatestTerminalAssistantCandidateText()) turnWatches.armAssistantCompletionIdleWatch({
6913
+ lastNotificationMethod: notification.method,
6914
+ hookRunId: finalizationHookNotification.runId,
6915
+ hookStatus: finalizationHookNotification.status
6916
+ });
6917
+ }
8890
6918
  if (notificationState.isTurnTerminal) {
8891
6919
  if (notificationState.isTurnAbortMarker) projector.markAborted();
8892
6920
  if (!timedOut && !runAbortController.signal.aborted) await steeringQueue?.flushPending();
@@ -8898,72 +6926,43 @@ async function runCodexAppServerAttempt(params, options = {}) {
8898
6926
  }
8899
6927
  }
8900
6928
  };
8901
- let activeNativeTurnCompletionWaiter;
8902
- const waitForActiveNativeTurnCompletion = async (turnIds) => {
8903
- const turnIdSet = turnIds?.length ? new Set(turnIds) : void 0;
8904
- const matchesCompletion = (notification) => isCodexThreadTurnCompletedNotification(notification, thread.threadId, turnIdSet);
8905
- if (pendingNotifications.some((notification) => matchesCompletion(notification))) return true;
8906
- return await new Promise((resolve) => {
8907
- let settled = false;
8908
- const timeoutRef = {};
8909
- const finish = (completedNativeTurn) => {
8910
- if (settled) return;
8911
- settled = true;
8912
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
8913
- runAbortController.signal.removeEventListener("abort", abortListener);
8914
- if (activeNativeTurnCompletionWaiter?.resolve === finishComplete) activeNativeTurnCompletionWaiter = void 0;
8915
- resolve(completedNativeTurn);
8916
- };
8917
- const finishComplete = () => finish(true);
8918
- const abortListener = () => finish(false);
8919
- timeoutRef.current = setTimeout(() => finish(false), Math.min(appServer.requestTimeoutMs, CODEX_APP_SERVER_ACTIVE_NATIVE_TURN_WAIT_TIMEOUT_MS));
8920
- activeNativeTurnCompletionWaiter = {
8921
- matches: matchesCompletion,
8922
- resolve: finishComplete
8923
- };
8924
- runAbortController.signal.addEventListener("abort", abortListener, { once: true });
6929
+ const waitForActiveNativeTurnCompletion = async () => {
6930
+ const route = turnRoute;
6931
+ if (!route) return false;
6932
+ return await route.waitForTurnCompletion({
6933
+ timeoutMs: Math.min(appServer.requestTimeoutMs, CODEX_APP_SERVER_NATIVE_TURN_WAIT_TIMEOUT_MS),
6934
+ signal: runAbortController.signal
8925
6935
  });
8926
6936
  };
8927
- const enqueueNotification = (notification) => {
6937
+ const noteNotificationReceived = (notification, scope, receivedAtMs) => {
8928
6938
  const projector = projectorRef.current;
8929
6939
  const turnId = turnIdRef.current;
8930
- const userInputBridge = userInputBridgeRef.current;
8931
- const correlation = describeCodexNotificationCorrelation(notification, {
8932
- threadId: thread.threadId,
8933
- ...turnId ? { turnId } : {}
8934
- });
8935
- embeddedAgentLog.trace("codex app-server raw notification received", correlation);
8936
- if (notification.method === "turn/completed" && correlation.matchesActiveTurn === false) if (correlation.matchesActiveThread) embeddedAgentLog.warn("codex app-server turn/completed did not match active turn", correlation);
8937
- else embeddedAgentLog.debug("codex app-server turn/completed ignored for other subscribed thread", correlation);
8938
- if (notification.method === "turn/completed" && correlation.matchesActiveThread) {
8939
- if (activeNativeTurnCompletionWaiter?.matches(notification)) activeNativeTurnCompletionWaiter.resolve();
8940
- }
8941
- if (isCodexNotificationOutsideActiveRun(correlation)) return Promise.resolve();
8942
- if (!projector || !turnId) {
8943
- userInputBridge?.handleNotification(notification);
8944
- pendingNotifications.push(notification);
8945
- return Promise.resolve();
8946
- }
6940
+ if (!projector || !turnId) return;
8947
6941
  if (isTerminalTurnNotificationForTurn(notification, turnId)) terminalTurnNotificationQueued = true;
8948
- const isNativeResponseStreamDelta = isNativeResponseStreamDeltaNotification(notification);
8949
- const nativeResponseStreamDeltaMatchesActiveTurn = isNativeResponseStreamDelta && (correlation.matchesActiveTurn === true || isUnscopedCodexNotification(correlation) && canAttributeUnscopedNativeResponseDeltaToThisTurn(client));
8950
- const notificationMatchesActiveTurn = correlation.matchesActiveTurn === true || !isNativeResponseStreamDelta && correlation.matchesActiveTurn !== false || nativeResponseStreamDeltaMatchesActiveTurn;
8951
- if (correlation.matchesActiveTurn === true) {
6942
+ if (scope.turnId === turnId) {
8952
6943
  const modelToolCallId = readRawResponseToolCallId(notification);
8953
6944
  if (modelToolCallId) allocateCodexToolOutcomeOrdinal?.(modelToolCallId);
8954
6945
  const nativeItem = readCodexNotificationItem(notification.params);
8955
6946
  if (nativeItem?.type === "webSearch") projector.recordNativeToolOutcome(nativeItem);
8956
6947
  }
8957
- if (notificationMatchesActiveTurn) turnWatches.noteNotificationReceived(notification.method, isNativeResponseStreamDelta ? {
8958
- attemptProgress: true,
8959
- ...turnCrossedToolHandoff ? { attemptTimeoutMs: postToolRawAssistantCompletionIdleTimeoutMs } : {},
8960
- details: { lastNotificationMethod: notification.method }
8961
- } : void 0);
8962
- notificationQueue = notificationQueue.then(() => handleNotification(notification), () => handleNotification(notification));
8963
- return notificationQueue;
6948
+ if (readCodexFinalizationHookNotification(notification, thread.threadId, turnId)?.phase === "started") {
6949
+ unsettledFinalizationHookCount += 1;
6950
+ turnWatches.disarmAssistantCompletionIdleWatch();
6951
+ }
6952
+ turnWatches.noteNotificationReceived(notification.method, { receivedAtMs });
6953
+ };
6954
+ const enqueueNotification = async (notification, scope) => {
6955
+ embeddedAgentLog.trace("codex app-server raw notification received", {
6956
+ method: notification.method,
6957
+ ...scope
6958
+ });
6959
+ await handleNotification(notification);
6960
+ };
6961
+ const drainNotificationQueue = async () => {
6962
+ await turnRoute?.drain();
8964
6963
  };
8965
6964
  const nativeSubagentCodexHome = appServer.start.transport === "stdio" ? appServer.start.env?.CODEX_HOME ?? resolveCodexAppServerHomeDir(agentDir) : void 0;
8966
- registerCodexNativeSubagentMonitor({
6965
+ nativeSubagentMonitorRef.current = registerCodexNativeSubagentMonitor({
8967
6966
  client,
8968
6967
  parentThreadId: thread.threadId,
8969
6968
  requesterSessionKey: params.sessionKey,
@@ -8971,8 +6970,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
8971
6970
  agentId: params.agentId,
8972
6971
  codexHome: nativeSubagentCodexHome
8973
6972
  });
8974
- const notificationCleanup = client.addNotificationHandler(enqueueNotification);
8975
- const requestCleanup = client.addRequestHandler(async (request) => {
6973
+ const handleServerRequest = async (request, scope) => {
8976
6974
  const turnId = turnIdRef.current;
8977
6975
  const userInputBridge = userInputBridgeRef.current;
8978
6976
  const projector = projectorRef.current;
@@ -8986,14 +6984,9 @@ async function runCodexAppServerAttempt(params, options = {}) {
8986
6984
  turnWatches.touchActivity(`request:${request.method}:start`, { attemptProgress: true });
8987
6985
  };
8988
6986
  try {
8989
- if (request.method === "account/chatgptAuthTokens/refresh") return refreshCodexAppServerAuthTokens({
8990
- agentDir,
8991
- authProfileId: startupAuthProfileId,
8992
- config: params.config
8993
- });
8994
6987
  if (!turnId) return;
8995
6988
  if (request.method === "mcpServer/elicitation/request") {
8996
- if (isCurrentThreadOptionalTurnRequestParams(request.params, thread.threadId, turnId)) {
6989
+ if (!scope.turnId || scope.turnId === turnId) {
8997
6990
  armCompletionWatchOnResponse = true;
8998
6991
  markCurrentTurnRequestProgress();
8999
6992
  }
@@ -9008,7 +7001,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9008
7001
  });
9009
7002
  }
9010
7003
  if (request.method === "item/tool/requestUserInput") {
9011
- if (isCurrentThreadTurnRequestParams(request.params, thread.threadId, turnId)) {
7004
+ if (scope.turnId === turnId) {
9012
7005
  armCompletionWatchOnResponse = true;
9013
7006
  markCurrentTurnRequestProgress();
9014
7007
  }
@@ -9019,7 +7012,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9019
7012
  }
9020
7013
  if (request.method !== "item/tool/call") {
9021
7014
  if (isCodexAppServerApprovalRequest(request.method)) {
9022
- if (isCurrentApprovalTurnRequestParams(request.params, thread.threadId, turnId)) {
7015
+ if (scope.turnId === turnId) {
9023
7016
  armCompletionWatchOnResponse = true;
9024
7017
  markCurrentTurnRequestProgress();
9025
7018
  }
@@ -9034,7 +7027,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
9034
7027
  execReviewerAgentId: sessionAgentId,
9035
7028
  internalExecAutoReview: appServer.approvalsReviewer === "user",
9036
7029
  autoApprove: shouldAutoApproveCodexAppServerApprovals(appServer),
9037
- signal: runAbortController.signal
7030
+ signal: runAbortController.signal,
7031
+ onNativeToolFailureDisposition: (itemId, disposition) => projector?.recordNativeToolApprovalFailure(itemId, disposition)
9038
7032
  });
9039
7033
  }
9040
7034
  return;
@@ -9065,6 +7059,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9065
7059
  });
9066
7060
  emitDynamicToolStartedDiagnostic({
9067
7061
  call,
7062
+ agentId: sessionAgentId,
9068
7063
  runId: params.runId,
9069
7064
  sessionId: params.sessionId,
9070
7065
  sessionKey: params.sessionKey
@@ -9172,6 +7167,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9172
7167
  })) emitDynamicToolTerminalDiagnostic({
9173
7168
  response,
9174
7169
  call,
7170
+ agentId: sessionAgentId,
9175
7171
  runId: params.runId,
9176
7172
  sessionId: params.sessionId,
9177
7173
  sessionKey: params.sessionKey,
@@ -9198,6 +7194,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9198
7194
  sessionKey: params.sessionKey
9199
7195
  })) emitDynamicToolErrorDiagnostic({
9200
7196
  call,
7197
+ agentId: sessionAgentId,
9201
7198
  runId: params.runId,
9202
7199
  sessionId: params.sessionId,
9203
7200
  sessionKey: params.sessionKey,
@@ -9221,7 +7218,63 @@ async function runCodexAppServerAttempt(params, options = {}) {
9221
7218
  scheduleTerminalDynamicToolReleaseCheck();
9222
7219
  } else turnWatches.scheduleProgressWatches();
9223
7220
  }
9224
- });
7221
+ };
7222
+ const attachRouteAbort = (route) => {
7223
+ const onAbort = () => {
7224
+ if (completed || terminalTurnNotificationQueued || runAbortController.signal.aborted) return;
7225
+ const reasonText = formatErrorMessage(route.signal.reason);
7226
+ const closedClient = reasonText.includes("turn router closed");
7227
+ clientClosedPromptError = closedClient ? "codex app-server client closed before turn completed" : `codex app-server turn route closed before turn completed: ${reasonText}`;
7228
+ clientClosedAbort = closedClient;
7229
+ const activeTurnId = turnIdRef.current;
7230
+ if (activeTurnId) trajectoryRecorder?.recordEvent("turn.client_closed", {
7231
+ threadId: thread.threadId,
7232
+ turnId: activeTurnId
7233
+ });
7234
+ embeddedAgentLog.warn(clientClosedPromptError, {
7235
+ threadId: thread.threadId,
7236
+ turnId: activeTurnId
7237
+ });
7238
+ runAbortController.abort(closedClient ? "client_closed" : "turn_route_closed");
7239
+ completed = true;
7240
+ turnWatches.clearAllTimers();
7241
+ resolveCompletion?.();
7242
+ };
7243
+ route.signal.addEventListener("abort", onAbort, { once: true });
7244
+ if (route.signal.aborted) onAbort();
7245
+ return () => route.signal.removeEventListener("abort", onAbort);
7246
+ };
7247
+ const ensureCurrentThreadRoute = async () => {
7248
+ if (turnRoute?.threadId !== thread.threadId) {
7249
+ releaseCurrentRoute();
7250
+ turnRoute = turnRouter.reserveThread({
7251
+ threadId: thread.threadId,
7252
+ releaseOn: runAbortController.signal
7253
+ });
7254
+ }
7255
+ if (!turnRoute) throw new Error("codex app-server turn route was not reserved");
7256
+ if (!routeActivated) {
7257
+ detachRouteAbort = attachRouteAbort(turnRoute);
7258
+ await turnRoute.activate({
7259
+ onNotificationReceived: noteNotificationReceived,
7260
+ onNotification: enqueueNotification,
7261
+ onRequest: handleServerRequest
7262
+ });
7263
+ routeActivated = true;
7264
+ }
7265
+ return turnRoute;
7266
+ };
7267
+ try {
7268
+ await ensureCurrentThreadRoute();
7269
+ } catch (error) {
7270
+ activateNativePreToolUseFailureFallback();
7271
+ releaseCurrentRoute();
7272
+ nativeHookRelay?.unregister();
7273
+ await releaseSandboxExecEnvironment();
7274
+ releaseSharedClientLeaseOnce();
7275
+ params.abortSignal?.removeEventListener("abort", abortFromUpstream);
7276
+ throw error;
7277
+ }
9225
7278
  const buildLlmInputEvent = () => ({
9226
7279
  runId: params.runId,
9227
7280
  sessionId: params.sessionId,
@@ -9268,6 +7321,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9268
7321
  throw error;
9269
7322
  };
9270
7323
  const startCodexTurn = async () => {
7324
+ const activeTurnRoute = await ensureCurrentThreadRoute();
9271
7325
  const turnAppServer = withCodexAppServerFastModeServiceTier(pluginAppServer, params);
9272
7326
  pluginAppServer = turnAppServer;
9273
7327
  const turnStartParams = buildTurnStartParams(params, {
@@ -9285,31 +7339,42 @@ async function runCodexAppServerAttempt(params, options = {}) {
9285
7339
  heartbeatCollaborationInstructions: workspaceBootstrapContext.heartbeatCollaborationInstructions
9286
7340
  });
9287
7341
  codexModelCallDiagnostics.setRequestPayloadBytes(utf8JsonByteLength(turnStartParams));
9288
- const startedTurn = assertCodexTurnStartResponse(await client.request("turn/start", turnStartParams, {
9289
- timeoutMs: params.timeoutMs,
9290
- signal: runAbortController.signal
9291
- }));
9292
- throwIfTurnStartAcceptedAfterAbort();
9293
- return startedTurn;
7342
+ latestStartupErrorNotification = void 0;
7343
+ rateLimitsRevisionBeforeLastTurnStart = readCodexRateLimitsRevision(client);
7344
+ activeTurnRoute.armTurn();
7345
+ let acceptedTurnId;
7346
+ try {
7347
+ const startedTurn = assertCodexTurnStartResponse(await client.request("turn/start", turnStartParams, {
7348
+ timeoutMs: params.timeoutMs,
7349
+ signal: runAbortController.signal
7350
+ }));
7351
+ acceptedTurnId = startedTurn.turn.id;
7352
+ throwIfTurnStartAcceptedAfterAbort();
7353
+ return startedTurn;
7354
+ } catch (error) {
7355
+ if (acceptedTurnId) {
7356
+ interruptCodexTurnBestEffort(client, {
7357
+ threadId: thread.threadId,
7358
+ turnId: acceptedTurnId,
7359
+ timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
7360
+ });
7361
+ releaseCurrentRoute();
7362
+ } else await activeTurnRoute.cancelTurn();
7363
+ throw error;
7364
+ }
9294
7365
  };
9295
- const activeNativeTurnIds = thread.lifecycle.action === "resumed" ? thread.lifecycle.activeTurnIds ?? [] : [];
9296
- if (activeNativeTurnIds.length > 0) {
9297
- embeddedAgentLog.info("codex app-server resumed thread has active native turn; waiting before turn/start", {
9298
- threadId: thread.threadId,
9299
- activeTurnIds: activeNativeTurnIds
9300
- });
7366
+ if (thread.lifecycle.action === "resumed" && (thread.lifecycle.activeTurnIds?.length ?? 0) > 0) {
7367
+ embeddedAgentLog.info("codex app-server resumed thread has active native turn; waiting before turn/start", { threadId: thread.threadId });
9301
7368
  emitCodexAppServerEvent(params, {
9302
7369
  stream: "codex_app_server.lifecycle",
9303
7370
  data: {
9304
7371
  phase: "turn_start_waiting_for_native_turn",
9305
- threadId: thread.threadId,
9306
- activeTurnIds: activeNativeTurnIds
7372
+ threadId: thread.threadId
9307
7373
  }
9308
7374
  });
9309
- if (!await waitForActiveNativeTurnCompletion(activeNativeTurnIds) && !runAbortController.signal.aborted) embeddedAgentLog.warn("codex app-server active native turn did not complete before turn/start wait timed out", {
9310
- threadId: thread.threadId,
9311
- activeTurnIds: activeNativeTurnIds
9312
- });
7375
+ const nativeTurnCompleted = await waitForActiveNativeTurnCompletion();
7376
+ if (nativeTurnCompleted) await turnRoute?.drain();
7377
+ if (!nativeTurnCompleted && !runAbortController.signal.aborted) embeddedAgentLog.warn("codex app-server active native turn did not complete before turn/start wait timed out", { threadId: thread.threadId });
9313
7378
  }
9314
7379
  try {
9315
7380
  codexModelCallDiagnostics.emitStarted();
@@ -9355,25 +7420,25 @@ async function runCodexAppServerAttempt(params, options = {}) {
9355
7420
  error: formatErrorMessage(turnStartError)
9356
7421
  });
9357
7422
  try {
9358
- const preRetrySessionFile = activeSessionFile;
9359
- const clearedPreRetryBinding = await clearCodexAppServerBindingForThread(preRetrySessionFile, thread.threadId);
9360
- const clearedActiveBinding = activeSessionFile !== preRetrySessionFile ? await clearCodexAppServerBindingForThread(activeSessionFile, thread.threadId) : false;
9361
- if (!clearedPreRetryBinding && !clearedActiveBinding) embeddedAgentLog.warn("codex app-server preserved newer context-engine binding after resume overflow; skipping fresh retry", {
7423
+ if (!await bindingStore.mutate(bindingIdentity, {
7424
+ kind: "clear",
7425
+ threadId: thread.threadId
7426
+ })) embeddedAgentLog.warn("codex app-server preserved newer context-engine binding after resume overflow; skipping fresh retry", {
9362
7427
  threadId: thread.threadId,
9363
7428
  error: formatErrorMessage(turnStartError)
9364
7429
  });
9365
7430
  else {
9366
7431
  thread = await restartContextEngineCodexThread();
9367
7432
  {
9368
- const retryBinding = await readCodexAppServerBinding(activeSessionFile);
7433
+ const retryBinding = await bindingStore.read(bindingIdentity);
9369
7434
  if (retryBinding && retryBinding.threadId === thread.threadId && retryBinding.contextEngine?.projection) {
9370
- const { schemaVersion: _schemaVersion, sessionFile: _boundSessionFile, updatedAt: _updatedAt, ...bindingForWrite } = retryBinding;
9371
- await writeCodexAppServerBinding(activeSessionFile, {
9372
- ...bindingForWrite,
9373
- contextEngine: bindingForWrite.contextEngine ? {
9374
- ...bindingForWrite.contextEngine,
7435
+ await bindingStore.mutate(bindingIdentity, {
7436
+ kind: "patch",
7437
+ threadId: retryBinding.threadId,
7438
+ patch: { contextEngine: retryBinding.contextEngine ? {
7439
+ ...retryBinding.contextEngine,
9375
7440
  projection: void 0
9376
- } : void 0
7441
+ } : void 0 }
9377
7442
  });
9378
7443
  embeddedAgentLog.info("codex app-server cleared stale context-engine projection after overflow retry", {
9379
7444
  threadId: thread.threadId,
@@ -9402,12 +7467,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
9402
7467
  const usageLimitError = await formatCodexTurnStartUsageLimitError({
9403
7468
  client,
9404
7469
  error: turnStartError,
9405
- pendingNotifications,
7470
+ errorNotification: latestStartupErrorNotification,
7471
+ rateLimitsRevisionBeforeTurnStart: rateLimitsRevisionBeforeLastTurnStart,
9406
7472
  timeoutMs: appServer.requestTimeoutMs,
9407
7473
  signal: runAbortController.signal
9408
7474
  });
9409
7475
  const turnStartErrorMessage = usageLimitError?.message ?? formatErrorMessage(turnStartError);
9410
- if (isInvalidCodexImagePayloadError(turnStartErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(activeSessionFile, {
7476
+ if (isInvalidCodexImagePayloadError(turnStartErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(bindingStore, bindingIdentity, {
9411
7477
  phase: "turn_start",
9412
7478
  threadId: thread.threadId,
9413
7479
  error: turnStartErrorMessage
@@ -9469,8 +7535,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
9469
7535
  threadId: thread.threadId,
9470
7536
  timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
9471
7537
  });
9472
- notificationCleanup();
9473
- requestCleanup();
7538
+ releaseCurrentRoute();
7539
+ activateNativePreToolUseFailureFallback();
9474
7540
  nativeHookRelay?.unregister();
9475
7541
  await releaseSandboxExecEnvironment();
9476
7542
  await runAgentCleanupStep({
@@ -9501,6 +7567,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9501
7567
  }
9502
7568
  }
9503
7569
  if (!turn) {
7570
+ activateNativePreToolUseFailureFallback();
9504
7571
  await releaseSharedClientLeaseAndRetireOneShotClient();
9505
7572
  throw new Error("codex app-server turn/start failed without an error");
9506
7573
  }
@@ -9532,27 +7599,12 @@ async function runCodexAppServerAttempt(params, options = {}) {
9532
7599
  }
9533
7600
  }, thread.threadId, activeTurnId, {
9534
7601
  nativePostToolUseRelayEnabled: nativeHookRelay?.allowedEvents.includes("post_tool_use") === true && nativeHookRelay.shouldRelayEvent("post_tool_use"),
7602
+ readRecentRateLimits: () => readRecentCodexRateLimits(client),
7603
+ runAbortSignal: runAbortController.signal,
9535
7604
  trajectoryRecorder,
9536
7605
  onNativeToolResultRecorded: maybeAnnounceFastModeAutoOff
9537
7606
  });
9538
- if (isTerminalTurnStatus(turn.turn.status) || pendingNotifications.some((notification) => isTerminalTurnNotificationForTurn(notification, activeTurnId))) terminalTurnNotificationQueued = true;
9539
- const closeCleanup = client.addCloseHandler?.(() => {
9540
- if (completed || terminalTurnNotificationQueued || runAbortController.signal.aborted) return;
9541
- clientClosedPromptError = "codex app-server client closed before turn completed";
9542
- trajectoryRecorder?.recordEvent("turn.client_closed", {
9543
- threadId: thread.threadId,
9544
- turnId: activeTurnId
9545
- });
9546
- embeddedAgentLog.warn("codex app-server client closed before turn completed", {
9547
- threadId: thread.threadId,
9548
- turnId: activeTurnId
9549
- });
9550
- clientClosedAbort = true;
9551
- runAbortController.abort("client_closed");
9552
- completed = true;
9553
- turnWatches.clearAllTimers();
9554
- resolveCompletion?.();
9555
- });
7607
+ if (isTerminalTurnStatus(turn.turn.status)) terminalTurnNotificationQueued = true;
9556
7608
  emitLifecycleStart();
9557
7609
  const activeProjector = projectorRef.current;
9558
7610
  if (!activeProjector) throw new Error("codex app-server projector was not initialized");
@@ -9560,7 +7612,17 @@ async function runCodexAppServerAttempt(params, options = {}) {
9560
7612
  turnWatches.touchActivity("turn:start", { arm: true });
9561
7613
  turnWatches.armAttemptIdleWatch();
9562
7614
  turnWatches.touchActivity("turn:start", { attemptProgress: true });
9563
- for (const notification of pendingNotifications.splice(0)) await enqueueNotification(notification);
7615
+ for (const failure of pendingNativePreToolUseFailures.splice(0)) activeProjector.recordNativeToolPreToolUseFailure(failure);
7616
+ if (turnRoute) try {
7617
+ await turnRoute.bindTurn(activeTurnId);
7618
+ } catch (error) {
7619
+ if (!terminalTurnNotificationQueued) throw error;
7620
+ await turnRoute.drain();
7621
+ if (!completed) {
7622
+ turnWatches.clearAllTimers();
7623
+ throw error;
7624
+ }
7625
+ }
9564
7626
  if (!completed && isTerminalTurnStatus(turn.turn.status)) await enqueueNotification({
9565
7627
  method: "turn/completed",
9566
7628
  params: {
@@ -9568,6 +7630,9 @@ async function runCodexAppServerAttempt(params, options = {}) {
9568
7630
  turnId: activeTurnId,
9569
7631
  turn: turn.turn
9570
7632
  }
7633
+ }, {
7634
+ threadId: thread.threadId,
7635
+ turnId: activeTurnId
9571
7636
  });
9572
7637
  const activeSteeringQueue = createCodexSteeringQueue({
9573
7638
  client,
@@ -9579,15 +7644,23 @@ async function runCodexAppServerAttempt(params, options = {}) {
9579
7644
  steeringQueueRef.current = activeSteeringQueue;
9580
7645
  const handle = {
9581
7646
  kind: "embedded",
7647
+ runId: params.runId,
9582
7648
  queueMessage: async (text, optionsLocal) => activeSteeringQueue.queue(text, optionsLocal),
9583
7649
  isStreaming: () => !completed && !runAbortController.signal.aborted,
9584
7650
  isStopped: () => completed || timedOut || runAbortController.signal.aborted,
7651
+ isAbortable: () => !terminalOutcomeFrozen || sharedAbortAllowedAfterTerminalOutcome,
9585
7652
  isCompacting: () => projectorRef.current?.isCompacting() ?? false,
9586
7653
  sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
9587
- cancel: () => runAbortController.abort("cancelled"),
9588
- abort: () => runAbortController.abort("aborted")
7654
+ cancel: () => abortExplicitly("cancelled"),
7655
+ abort: () => abortExplicitly("aborted")
9589
7656
  };
7657
+ params.replyOperation?.attachBackend(handle);
9590
7658
  setActiveEmbeddedRun(params.sessionId, handle, params.sessionKey, params.sessionFile);
7659
+ const freezeRunTerminalOutcome = () => {
7660
+ if (terminalOutcomeFrozen) return;
7661
+ terminalOutcomeFrozen = true;
7662
+ params.abortSignal?.removeEventListener("abort", abortFromUpstream);
7663
+ };
9591
7664
  const notifyUserMessagePersisted = createCodexAppServerUserMessagePersistenceNotifier(params);
9592
7665
  mirrorPromptAtTurnStartBestEffort({
9593
7666
  params,
@@ -9601,7 +7674,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
9601
7674
  const abortListener = () => {
9602
7675
  if (timedOut) {
9603
7676
  (async () => {
9604
- await clearCodexAppServerBindingForThread(activeSessionFile, thread.threadId);
7677
+ await bindingStore.mutate(bindingIdentity, {
7678
+ kind: "clear",
7679
+ threadId: thread.threadId
7680
+ });
9605
7681
  await retireCodexAppServerClientAfterTimedOutTurn(client, {
9606
7682
  threadId: thread.threadId,
9607
7683
  turnId: activeTurnId,
@@ -9622,14 +7698,34 @@ async function runCodexAppServerAttempt(params, options = {}) {
9622
7698
  if (runAbortController.signal.aborted) abortListener();
9623
7699
  try {
9624
7700
  await completion;
9625
- await notificationQueue;
7701
+ await drainNotificationQueue();
7702
+ const hasQuiescentCompletedAssistant = activeProjector.hasCompletedTerminalAssistantText() && activeAppServerTurnRequests === 0 && activeTurnItemIds.size === 0 && activeCompletionBlockerItemIds.size === 0 && pendingOpenClawDynamicToolCompletionIds.size === 0 && activeFinalizationHookRunIds.size === 0 && unsettledFinalizationHookCount === 0 && rejectedFinalizationHookAssistant === void 0;
7703
+ const hasRecoverableCompletedAssistant = !turnWatches.isCompletionIdleWatchPinnedByTerminalError() && turnWatches.isAssistantCompletionIdleWatchArmed() && hasQuiescentCompletedAssistant;
7704
+ const recoveredTurnWatchTimeout = turnCompletionIdleTimedOut && !explicitCancellationObserved && !terminalTurnNotificationQueued && hasRecoverableCompletedAssistant && activeProjector.recoverCompletedTerminalAssistantAfterTurnWatchTimeout();
7705
+ if (recoveredTurnWatchTimeout) {
7706
+ embeddedAgentLog.warn("codex app-server recovered completed assistant output after missing turn completion", {
7707
+ threadId: thread.threadId,
7708
+ turnId: activeTurnId,
7709
+ timeoutKind: turnWatchTimeoutKind,
7710
+ idleMs: turnWatchTimeoutIdleMs,
7711
+ timeoutMs: turnWatchTimeoutMs
7712
+ });
7713
+ trajectoryRecorder?.recordEvent("turn.watch_timeout_recovered", {
7714
+ threadId: thread.threadId,
7715
+ turnId: activeTurnId,
7716
+ timeoutKind: turnWatchTimeoutKind,
7717
+ idleMs: turnWatchTimeoutIdleMs,
7718
+ timeoutMs: turnWatchTimeoutMs
7719
+ });
7720
+ }
9626
7721
  const result = activeProjector.buildResult(toolBridge.telemetry, { yieldDetected });
9627
- const finalAborted = result.aborted || runAbortController.signal.aborted && !clientClosedAbort;
9628
- const canUseCompletedAssistantTextAfterClientClose = activeProjector.hasCompletedTerminalAssistantText() && activeAppServerTurnRequests === 0 && activeTurnItemIds.size === 0 && pendingOpenClawDynamicToolCompletionIds.size === 0;
9629
- const clientClosedPromptErrorForFinal = clientClosedPromptError && canUseCompletedAssistantTextAfterClientClose ? void 0 : clientClosedPromptError;
9630
- let finalPromptError = clientClosedPromptErrorForFinal ?? (turnCompletionIdleTimedOut ? turnCompletionIdleTimeoutMessage : timedOut ? "codex app-server attempt timed out" : result.promptError);
7722
+ const effectiveTimedOut = timedOut && !recoveredTurnWatchTimeout;
7723
+ const effectiveTurnCompletionIdleTimedOut = turnCompletionIdleTimedOut && !recoveredTurnWatchTimeout;
7724
+ const isFinalAborted = () => result.aborted || explicitCancellationObserved || runAbortController.signal.aborted && !clientClosedAbort && !recoveredTurnWatchTimeout;
7725
+ const clientClosedPromptErrorForFinal = clientClosedPromptError && hasRecoverableCompletedAssistant ? void 0 : clientClosedPromptError;
7726
+ let finalPromptError = clientClosedPromptErrorForFinal ?? (effectiveTurnCompletionIdleTimedOut ? turnCompletionIdleTimeoutMessage : effectiveTimedOut ? "codex app-server attempt timed out" : result.promptError);
9631
7727
  const finalPromptErrorMessage = typeof finalPromptError === "string" ? finalPromptError : finalPromptError ? formatErrorMessage(finalPromptError) : void 0;
9632
- if (isInvalidCodexImagePayloadError(finalPromptErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(activeSessionFile, {
7728
+ if (isInvalidCodexImagePayloadError(finalPromptErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(bindingStore, bindingIdentity, {
9633
7729
  phase: "turn_completed",
9634
7730
  threadId: thread.threadId,
9635
7731
  turnId: activeTurnId,
@@ -9645,9 +7741,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
9645
7741
  turnId: activeTurnId,
9646
7742
  error: finalPromptErrorMessage
9647
7743
  });
9648
- const preClearSessionFile = activeSessionFile;
9649
- await clearCodexAppServerBindingForThread(preClearSessionFile, thread.threadId);
9650
- if (activeSessionFile !== preClearSessionFile) await clearCodexAppServerBindingForThread(activeSessionFile, thread.threadId);
7744
+ await bindingStore.mutate(bindingIdentity, {
7745
+ kind: "clear",
7746
+ threadId: thread.threadId
7747
+ });
9651
7748
  }
9652
7749
  const refreshedUsageLimitPromptError = await refreshCodexUsageLimitPromptError({
9653
7750
  client,
@@ -9656,12 +7753,12 @@ async function runCodexAppServerAttempt(params, options = {}) {
9656
7753
  signal: runAbortController.signal
9657
7754
  });
9658
7755
  if (refreshedUsageLimitPromptError) finalPromptError = refreshedUsageLimitPromptError;
9659
- const finalPromptErrorSource = timedOut || clientClosedPromptErrorForFinal ? "prompt" : result.promptErrorSource;
9660
- const codexAppServerFailureKind = clientClosedPromptErrorForFinal ? "client_closed_before_turn_completed" : turnCompletionIdleTimedOut ? "turn_completion_idle_timeout" : void 0;
7756
+ const finalPromptErrorSource = effectiveTimedOut || clientClosedPromptErrorForFinal ? "prompt" : result.promptErrorSource;
7757
+ const codexAppServerFailureKind = clientClosedPromptErrorForFinal ? "client_closed_before_turn_completed" : effectiveTurnCompletionIdleTimedOut ? "turn_completion_idle_timeout" : void 0;
9661
7758
  const codexAppServerReplayBlockedReason = codexAppServerFailureKind ? resolveCodexAppServerReplayBlockedReason(result) : void 0;
9662
7759
  const promptTimeoutOutcome = buildCodexAppServerPromptTimeoutOutcome({
9663
7760
  result,
9664
- turnCompletionIdleTimedOut,
7761
+ turnCompletionIdleTimedOut: effectiveTurnCompletionIdleTimedOut,
9665
7762
  turnWatchTimeoutKind
9666
7763
  });
9667
7764
  const codexAppServerFailureDiagnostics = codexAppServerFailureKind === "turn_completion_idle_timeout" && turnWatchTimeoutKind === "completion" ? buildCodexAppServerTimeoutDiagnostics({
@@ -9670,36 +7767,40 @@ async function runCodexAppServerAttempt(params, options = {}) {
9670
7767
  lastActivityReason: turnWatchTimeoutLastActivityReason,
9671
7768
  details: turnWatchTimeoutDetails
9672
7769
  }) : void 0;
7770
+ const codexAppServerFailure = codexAppServerFailureKind ? {
7771
+ kind: codexAppServerFailureKind,
7772
+ ...codexAppServerFailureKind === "turn_completion_idle_timeout" && turnWatchTimeoutKind ? { turnWatchTimeoutKind } : {},
7773
+ transport: appServer.start.transport,
7774
+ threadId: thread.threadId,
7775
+ turnId: activeTurnId,
7776
+ replaySafe: codexAppServerReplayBlockedReason === void 0,
7777
+ ...codexAppServerReplayBlockedReason ? { replayBlockedReason: codexAppServerReplayBlockedReason } : {},
7778
+ ...codexAppServerFailureDiagnostics ? { diagnostics: codexAppServerFailureDiagnostics } : {}
7779
+ } : void 0;
7780
+ const finalAborted = isFinalAborted();
7781
+ const completedTurnStatus = activeProjector.getCompletedTurnStatus();
7782
+ const completedWithoutTerminalNotification = completed && !terminalTurnNotificationQueued && !timedOut && clientClosedPromptErrorForFinal === void 0;
7783
+ const attemptSucceeded = !finalAborted && !effectiveTimedOut && (finalPromptError === null || finalPromptError === void 0) && result.agentHarnessResultClassification === void 0 && (completedTurnStatus === "completed" || recoveredTurnWatchTimeout || completedWithoutTerminalNotification);
7784
+ sharedAbortAllowedAfterTerminalOutcome = shouldKeepCodexSharedAbortOpen({
7785
+ trigger: params.trigger,
7786
+ result,
7787
+ attemptSucceeded,
7788
+ explicitCancellationObserved
7789
+ });
7790
+ freezeRunTerminalOutcome();
9673
7791
  const modelCallFailureKind = classifyCodexModelCallFailureKind({
9674
7792
  error: finalPromptError,
9675
- timedOut,
9676
- turnCompletionIdleTimedOut,
9677
- runAborted: runAbortController.signal.aborted,
9678
- abortReason: runAbortController.signal.reason,
7793
+ timedOut: effectiveTimedOut,
7794
+ turnCompletionIdleTimedOut: effectiveTurnCompletionIdleTimedOut,
7795
+ runAborted: finalAborted,
7796
+ abortReason: explicitCancellationReason ?? runAbortController.signal.reason,
9679
7797
  clientClosedAbort,
9680
7798
  formatError: formatErrorMessage
9681
7799
  }) ?? (finalAborted ? "aborted" : void 0);
9682
7800
  if (modelCallFailureKind) codexModelCallDiagnostics.emitError(finalPromptError ?? "codex app-server attempt interrupted", { failureKind: modelCallFailureKind });
9683
7801
  else if (finalPromptError) codexModelCallDiagnostics.emitError(finalPromptError);
9684
7802
  else codexModelCallDiagnostics.emitCompleted(result);
9685
- recordCodexTrajectoryCompletion(trajectoryRecorder, {
9686
- attempt: params,
9687
- result,
9688
- threadId: thread.threadId,
9689
- turnId: activeTurnId,
9690
- timedOut,
9691
- yieldDetected
9692
- });
9693
- trajectoryRecorder?.recordEvent("session.ended", {
9694
- status: finalPromptError ? "error" : finalAborted || timedOut ? "interrupted" : "success",
9695
- threadId: thread.threadId,
9696
- turnId: activeTurnId,
9697
- timedOut,
9698
- yieldDetected,
9699
- promptError: normalizeCodexTrajectoryError(finalPromptError)
9700
- });
9701
- markTrajectoryEndRecorded();
9702
- await mirrorTranscriptBestEffort({
7803
+ const assistantTranscriptOwned = await mirrorTranscriptBestEffort({
9703
7804
  params,
9704
7805
  agentId: sessionAgentId,
9705
7806
  notifyUserMessagePersisted,
@@ -9709,19 +7810,6 @@ async function runCodexAppServerAttempt(params, options = {}) {
9709
7810
  threadId: thread.threadId,
9710
7811
  turnId: activeTurnId
9711
7812
  });
9712
- const terminalAssistantText = collectTerminalAssistantText(result);
9713
- if (terminalAssistantText && (!assistantStreamEventEmitted || assistantStreamNeedsTerminalSnapshot) && !finalAborted && !finalPromptError) emitCodexAppServerEvent(params, {
9714
- stream: "assistant",
9715
- data: { text: terminalAssistantText }
9716
- });
9717
- if (finalPromptError) emitLifecycleTerminal({
9718
- phase: "error",
9719
- error: formatErrorMessage(finalPromptError)
9720
- });
9721
- else emitLifecycleTerminal({
9722
- phase: "end",
9723
- ...finalAborted ? { aborted: true } : {}
9724
- });
9725
7813
  if (activeContextEngine) {
9726
7814
  const activeContextEnginePluginIdLocal = resolveContextEngineOwnerPluginId(activeContextEngine);
9727
7815
  const isHeartbeatLifecycleRun = params.bootstrapContextRunKind === "heartbeat" || params.bootstrapContextRunKind === "commitment-only";
@@ -9786,31 +7874,70 @@ async function runCodexAppServerAttempt(params, options = {}) {
9786
7874
  ctx: hookContext,
9787
7875
  hookRunner
9788
7876
  });
9789
- shouldDelayNativeHookRelayUnregister = activeProjector.getCompletedTurnStatus() === "completed" && !timedOut && !runAbortController.signal.aborted && !finalAborted && !finalPromptError;
9790
- if (shouldDelayNativeHookRelayUnregister) await markCodexAppServerBindingCoveredThroughTurn({
9791
- sessionFile: params.sessionFile,
7877
+ shouldDelayNativeHookRelayUnregister = completedTurnStatus === "completed" && !effectiveTimedOut && !runAbortController.signal.aborted && !finalAborted && !finalPromptError;
7878
+ if (shouldDelayNativeHookRelayUnregister) try {
7879
+ await markCodexAppServerBindingCoveredThroughTurn({
7880
+ bindingStore,
7881
+ identity: bindingIdentity,
7882
+ threadId: thread.threadId
7883
+ });
7884
+ } catch (error) {
7885
+ if (!await bindingStore.mutate(bindingIdentity, {
7886
+ kind: "clear",
7887
+ threadId: thread.threadId
7888
+ })) throw error;
7889
+ embeddedAgentLog.warn("codex app-server binding coverage update failed after completed turn; cleared stale binding", {
7890
+ threadId: thread.threadId,
7891
+ turnId: activeTurnId,
7892
+ error
7893
+ });
7894
+ }
7895
+ recordCodexTrajectoryCompletion(trajectoryRecorder, {
7896
+ attempt: params,
7897
+ result,
9792
7898
  threadId: thread.threadId,
9793
- authProfileStore: params.authProfileStore,
9794
- agentDir: params.agentDir,
9795
- config: params.config
7899
+ turnId: activeTurnId,
7900
+ timedOut: effectiveTimedOut,
7901
+ yieldDetected
7902
+ });
7903
+ trajectoryRecorder?.recordEvent("session.ended", {
7904
+ status: finalPromptError ? "error" : finalAborted || effectiveTimedOut ? "interrupted" : "success",
7905
+ threadId: thread.threadId,
7906
+ turnId: activeTurnId,
7907
+ timedOut: effectiveTimedOut,
7908
+ yieldDetected,
7909
+ promptError: normalizeCodexTrajectoryError(finalPromptError)
7910
+ });
7911
+ markTrajectoryEndRecorded();
7912
+ const terminalAssistantText = collectTerminalAssistantText(result);
7913
+ if (terminalAssistantText && (!assistantStreamEventEmitted || assistantStreamNeedsTerminalSnapshot) && !finalAborted && !finalPromptError) emitCodexAppServerEvent(params, {
7914
+ stream: "assistant",
7915
+ data: { text: terminalAssistantText }
7916
+ });
7917
+ if (finalPromptError) emitLifecycleTerminal({
7918
+ phase: "error",
7919
+ error: formatErrorMessage(finalPromptError),
7920
+ ...buildLifecycleTerminalMeta({
7921
+ aborted: finalAborted,
7922
+ timedOut: effectiveTimedOut
7923
+ })
7924
+ });
7925
+ else emitLifecycleTerminal({
7926
+ phase: "end",
7927
+ ...buildLifecycleTerminalMeta({
7928
+ aborted: finalAborted,
7929
+ timedOut: effectiveTimedOut
7930
+ })
9796
7931
  });
9797
7932
  return {
9798
7933
  ...result,
9799
- timedOut,
7934
+ timedOut: effectiveTimedOut,
9800
7935
  aborted: finalAborted,
9801
7936
  promptError: finalPromptError,
9802
7937
  promptErrorSource: finalPromptErrorSource,
9803
- ...codexAppServerFailureKind ? { codexAppServerFailure: {
9804
- kind: codexAppServerFailureKind,
9805
- ...codexAppServerFailureKind === "turn_completion_idle_timeout" && turnWatchTimeoutKind ? { turnWatchTimeoutKind } : {},
9806
- transport: appServer.start.transport,
9807
- threadId: thread.threadId,
9808
- turnId: activeTurnId,
9809
- replaySafe: codexAppServerReplayBlockedReason === void 0,
9810
- ...codexAppServerReplayBlockedReason ? { replayBlockedReason: codexAppServerReplayBlockedReason } : {},
9811
- ...codexAppServerFailureDiagnostics ? { diagnostics: codexAppServerFailureDiagnostics } : {}
9812
- } } : {},
7938
+ ...codexAppServerFailure ? { codexAppServerFailure } : {},
9813
7939
  ...promptTimeoutOutcome ? { promptTimeoutOutcome } : {},
7940
+ ...assistantTranscriptOwned ? { assistantTranscriptOwned: true } : {},
9814
7941
  systemPromptReport
9815
7942
  };
9816
7943
  } finally {
@@ -9818,7 +7945,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
9818
7945
  codexModelCallDiagnostics.emitError("codex app-server run completed without model-call terminal event");
9819
7946
  emitLifecycleTerminal({
9820
7947
  phase: "error",
9821
- error: "codex app-server run completed without lifecycle terminal event"
7948
+ error: "codex app-server run completed without lifecycle terminal event",
7949
+ ...buildLifecycleTerminalMeta({
7950
+ aborted: runAbortController.signal.aborted && !clientClosedAbort,
7951
+ timedOut
7952
+ })
9822
7953
  });
9823
7954
  if (trajectoryRecorder && !trajectoryEndRecorded) trajectoryRecorder.recordEvent("session.ended", {
9824
7955
  status: timedOut || runAbortController.signal.aborted && !clientClosedAbort ? "interrupted" : "cleanup",
@@ -9837,16 +7968,21 @@ async function runCodexAppServerAttempt(params, options = {}) {
9837
7968
  }
9838
7969
  });
9839
7970
  if (!timedOut && !runAbortController.signal.aborted) await steeringQueueRef.current?.flushPending();
9840
- if (!timedOut) await unsubscribeCodexThreadBestEffort(client, {
7971
+ const yieldedOneShotCleanupDeferred = !timedOut && params.cleanupBundleMcpOnRunEnd === true && yieldDetected && nativeSubagentMonitorRef.current?.deferUntilParentSettles(thread.threadId, async () => {
7972
+ await unsubscribeCodexThreadBestEffort(client, {
7973
+ threadId: thread.threadId,
7974
+ timeoutMs: 5e3
7975
+ });
7976
+ await releaseSharedClientLeaseAndRetireOneShotClient();
7977
+ });
7978
+ if (!timedOut && !yieldedOneShotCleanupDeferred) await unsubscribeCodexThreadBestEffort(client, {
9841
7979
  threadId: thread.threadId,
9842
7980
  timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
9843
7981
  });
9844
7982
  userInputBridgeRef.current?.cancelPending();
9845
7983
  turnWatches.clearAllTimers();
9846
- notificationCleanup();
9847
- requestCleanup();
9848
- closeCleanup?.();
9849
- await releaseSharedClientLeaseAndRetireOneShotClient();
7984
+ releaseCurrentRoute();
7985
+ if (!yieldedOneShotCleanupDeferred) await releaseSharedClientLeaseAndRetireOneShotClient();
9850
7986
  if (nativeHookRelay) if (shouldDelayNativeHookRelayUnregister) scheduleCodexNativeHookRelayUnregister({
9851
7987
  relay: nativeHookRelay,
9852
7988
  hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec
@@ -9854,17 +7990,20 @@ async function runCodexAppServerAttempt(params, options = {}) {
9854
7990
  else nativeHookRelay.unregister();
9855
7991
  await releaseSandboxExecEnvironment();
9856
7992
  runAbortController.signal.removeEventListener("abort", abortListener);
9857
- params.abortSignal?.removeEventListener("abort", abortFromUpstream);
9858
7993
  steeringQueueRef.current?.cancel();
7994
+ freezeRunTerminalOutcome();
7995
+ params.replyOperation?.detachBackend(handle);
9859
7996
  clearActiveEmbeddedRun(params.sessionId, handle, params.sessionKey, params.sessionFile);
9860
7997
  }
9861
7998
  }
9862
7999
  function readDynamicToolCallParams(value) {
9863
8000
  return readCodexDynamicToolCallParams(value);
9864
8001
  }
9865
- async function clearCodexBindingAfterInvalidImagePayload(sessionFile, fields) {
9866
- const currentBinding = await readCodexAppServerBinding(sessionFile);
9867
- if (fields.threadId && currentBinding && currentBinding.threadId !== fields.threadId) {
8002
+ async function clearCodexBindingAfterInvalidImagePayload(bindingStore, identity, fields) {
8003
+ const currentBinding = await bindingStore.read(identity);
8004
+ const expectedThreadId = fields.threadId ?? currentBinding?.threadId;
8005
+ if (!expectedThreadId) return;
8006
+ if (currentBinding && currentBinding.threadId !== expectedThreadId) {
9868
8007
  embeddedAgentLog.warn("codex app-server image payload error detected for unbound thread; preserving thread binding", {
9869
8008
  ...fields,
9870
8009
  boundThreadId: currentBinding.threadId
@@ -9872,32 +8011,21 @@ async function clearCodexBindingAfterInvalidImagePayload(sessionFile, fields) {
9872
8011
  return;
9873
8012
  }
9874
8013
  embeddedAgentLog.warn("codex app-server image payload error detected; clearing thread binding", fields);
9875
- await clearCodexAppServerBinding(sessionFile);
8014
+ await bindingStore.mutate(identity, {
8015
+ kind: "clear",
8016
+ threadId: expectedThreadId
8017
+ });
9876
8018
  }
9877
8019
  async function markCodexAppServerBindingCoveredThroughTurn(params) {
9878
- const currentBinding = await readCodexAppServerBinding(params.sessionFile, {
9879
- authProfileStore: params.authProfileStore,
9880
- agentDir: params.agentDir,
9881
- config: params.config
9882
- });
9883
- if (!currentBinding || currentBinding.threadId !== params.threadId) return;
9884
- const { schemaVersion: _schemaVersion, sessionFile: _boundSessionFile, updatedAt: _updatedAt, ...bindingForWrite } = currentBinding;
9885
- await writeCodexAppServerBinding(params.sessionFile, bindingForWrite, {
9886
- authProfileStore: params.authProfileStore,
9887
- agentDir: params.agentDir,
9888
- config: params.config
8020
+ await params.bindingStore.mutate(params.identity, {
8021
+ kind: "patch",
8022
+ threadId: params.threadId,
8023
+ patch: { historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString() }
9889
8024
  });
9890
8025
  }
9891
8026
  function isNonEmptyString(value) {
9892
8027
  return typeof value === "string" && value.length > 0;
9893
8028
  }
9894
- function canAttributeUnscopedNativeResponseDeltaToThisTurn(client) {
9895
- const activeLeases = client.getActiveSharedLeaseCountForUnscopedNotifications?.();
9896
- return activeLeases === void 0 || activeLeases <= 1;
9897
- }
9898
- function isUnscopedCodexNotification(correlation) {
9899
- return !correlation.threadId && !correlation.turnId && !correlation.nestedTurnThreadId && !correlation.nestedTurnId;
9900
- }
9901
8029
  function shouldUseFreshCodexThreadAfterContextEngineOverflow(params) {
9902
8030
  if (!params.contextEngineActive || params.thread.lifecycle.action !== "resumed") return false;
9903
8031
  return isCodexContextWindowError(params.error);
@@ -9912,12 +8040,20 @@ function isCodexActiveCompactTurnError(error) {
9912
8040
  const codexErrorInfo = isJsonObject(data?.codexErrorInfo) ? data.codexErrorInfo : void 0;
9913
8041
  return (isJsonObject(codexErrorInfo?.activeTurnNotSteerable) ? codexErrorInfo.activeTurnNotSteerable : void 0)?.turnKind === "compact";
9914
8042
  }
9915
- function isCodexThreadTurnCompletedNotification(notification, threadId, turnIds) {
9916
- if (notification.method !== "turn/completed") return false;
9917
- const correlation = describeCodexNotificationCorrelation(notification, { threadId });
9918
- if (!correlation.matchesActiveThread) return false;
9919
- const turnId = correlation.turnId ?? correlation.nestedTurnId;
9920
- return !turnIds || turnId !== void 0 && turnIds.has(turnId);
8043
+ function readCodexFinalizationHookNotification(notification, threadId, turnId) {
8044
+ if (notification.method !== "hook/started" && notification.method !== "hook/completed") return;
8045
+ const params = isJsonObject(notification.params) ? notification.params : void 0;
8046
+ const run = params && isJsonObject(params.run) ? params.run : void 0;
8047
+ if (params?.threadId !== threadId || params.turnId !== turnId || run?.eventName !== "stop" && run?.eventName !== "subagentStop" || typeof run.id !== "string" || !run.id) return;
8048
+ if (notification.method === "hook/started") return {
8049
+ phase: "started",
8050
+ runId: run.id
8051
+ };
8052
+ return {
8053
+ phase: "completed",
8054
+ runId: run.id,
8055
+ status: typeof run.status === "string" ? run.status : void 0
8056
+ };
9921
8057
  }
9922
8058
  function joinPresentSections(...sections) {
9923
8059
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
@@ -9973,12 +8109,15 @@ function handleApprovalRequest(params) {
9973
8109
  execReviewerAgentId: params.execReviewerAgentId,
9974
8110
  internalExecAutoReview: params.internalExecAutoReview,
9975
8111
  autoApprove: params.autoApprove,
9976
- signal: params.signal
8112
+ signal: params.signal,
8113
+ onNativeToolFailureDisposition: params.onNativeToolFailureDisposition
9977
8114
  });
9978
8115
  }
9979
8116
  function resolveCodexDynamicToolDirectNames(params) {
9980
- if (params.sourceReplyDeliveryMode !== "message_tool_only") return [];
9981
- return ["message"];
8117
+ const names = [];
8118
+ if (params.crestodianTool) names.push("crestodian");
8119
+ if (params.sourceReplyDeliveryMode === "message_tool_only") names.push("message");
8120
+ return names;
9982
8121
  }
9983
8122
  //#endregion
9984
8123
  export { runCodexAppServerAttempt };