@openclaw/codex 2026.7.1-beta.2 → 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-B7hqD3Xj.js → app-server-policy-BUk0GLMy.js} +5 -5
  2. package/dist/{attempt-notifications-tqyQ_Tdn.js → attempt-notifications-BGsEIIDI.js} +5 -40
  3. package/dist/{command-handlers-C9K-KRU-.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-CpurU-G9.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 +1117 -19
  12. package/dist/{media-understanding-provider-BOeoBhEw.js → media-understanding-provider-DgqRvU2_.js} +13 -8
  13. package/dist/media-understanding-provider.js +1 -1
  14. package/dist/{models-DpzY1xpp.js → models-DDmO1zwd.js} +2 -2
  15. package/dist/notification-correlation-Bo7KB3ks.js +35 -0
  16. package/dist/{plugin-app-cache-key-BrZUG9jj.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-CJiWigAJ.js → protocol-validators-dZQ-UTOa.js} +116 -59
  19. package/dist/{provider-capabilities-wqedK49Z.js → provider-capabilities-gTCwjfmh.js} +2916 -114
  20. package/dist/{provider-nmtMDcGH.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-DikbXia_.js → run-attempt-8SPLiDlT.js} +907 -3002
  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-Ca9wqmd0.js → side-question-DkjXvYv7.js} +132 -140
  29. package/dist/{thread-lifecycle-D6R40plk.js → thread-lifecycle-BskXnNP-.js} +609 -444
  30. package/dist/{web-search-provider.runtime-B6eZEFmd.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-DfFu9JH_.js +0 -15
  36. package/dist/compact-CILptrPS.js +0 -278
  37. package/dist/conversation-binding-DOJtUxOy.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-D6R40plk.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-tqyQ_Tdn.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-CJiWigAJ.js";
6
- import { l as resolveCodexUsageLimitResetAtMs, s as formatCodexUsageLimitErrorMessage, u as shouldRefreshCodexRateLimitsForUsageLimitMessage } from "./provider-nmtMDcGH.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-BrZUG9jj.js";
10
- import { a as formatCodexDisplayText, n as resolveCodexAppServerForOpenClawToolPolicy, t as resolveCodexAppServerForModelProvider } from "./app-server-policy-B7hqD3Xj.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-DfFu9JH_.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-wqedK49Z.js";
14
- import { t as ensureCodexComputerUse } from "./computer-use-CpurU-G9.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;
@@ -3079,2574 +3305,106 @@ function createCodexAttemptTurnWatchController(params) {
3079
3305
  });
3080
3306
  params.onAbort("turn_completion_idle_timeout");
3081
3307
  }
3082
- function fireTerminalIdleTimeout() {
3083
- if (params.isCompleted() || params.isTerminalTurnNotificationQueued() || params.signal.aborted || !terminalIdleWatchArmed || params.getActiveAppServerTurnRequests() > 0) return;
3084
- const idleMs = Math.max(0, Date.now() - completionLastActivityAt);
3085
- if (idleMs < turnTerminalIdleTimeoutMs) {
3086
- scheduleTerminalIdleWatch();
3087
- return;
3088
- }
3089
- const timeout = {
3090
- kind: "terminal",
3091
- idleMs,
3092
- timeoutMs: turnTerminalIdleTimeoutMs,
3093
- lastActivityReason: completionLastActivityReason,
3094
- details: completionLastActivityDetails
3095
- };
3096
- params.onTimeout(timeout);
3097
- params.onMarkTimedOut();
3098
- params.onRecordEvent("turn.terminal_idle_timeout", {
3099
- threadId: params.threadId,
3100
- turnId: params.getTurnId(),
3101
- idleMs,
3102
- timeoutMs: timeout.timeoutMs,
3103
- lastActivityReason: timeout.lastActivityReason,
3104
- ...timeout.details
3105
- });
3106
- embeddedAgentLog.warn("codex app-server turn idle timed out waiting for terminal event", {
3107
- threadId: params.threadId,
3108
- turnId: params.getTurnId(),
3109
- idleMs,
3110
- timeoutMs: timeout.timeoutMs,
3111
- lastActivityReason: timeout.lastActivityReason,
3112
- ...timeout.details
3113
- });
3114
- params.onAbort("turn_terminal_idle_timeout");
3115
- }
3116
- return {
3117
- isCompletionIdleWatchArmed: () => completionIdleWatchArmed,
3118
- isCompletionIdleWatchPinnedByTerminalError: () => completionIdleWatchPinnedByTerminalError,
3119
- isAssistantCompletionIdleWatchArmed: () => assistantCompletionIdleWatchArmed,
3120
- armAttemptIdleWatch: () => {
3121
- attemptIdleWatchArmed = true;
3122
- scheduleAttemptIdleWatch();
3123
- },
3124
- armTerminalIdleWatch: () => {
3125
- terminalIdleWatchArmed = true;
3126
- scheduleTerminalIdleWatch();
3127
- },
3128
- armCompletionIdleWatch: (options) => {
3129
- completionIdleWatchArmed = true;
3130
- completionIdleWatchPinnedByTerminalError = options?.pinnedByTerminalError === true;
3131
- completionIdleTimeoutOverrideMs = options?.timeoutMs !== void 0 ? resolveWatchTimeoutMs(options.timeoutMs) : void 0;
3132
- scheduleCompletionIdleWatch();
3133
- },
3134
- disarmCompletionIdleWatch: () => {
3135
- completionIdleWatchArmed = false;
3136
- completionIdleWatchPinnedByTerminalError = false;
3137
- completionIdleTimeoutOverrideMs = void 0;
3138
- clearCompletionIdleTimer();
3139
- },
3140
- armAssistantCompletionIdleWatch: (details) => {
3141
- assistantCompletionIdleWatchArmed = true;
3142
- assistantCompletionLastActivityAt = Date.now();
3143
- assistantCompletionLastActivityDetails = details;
3144
- scheduleAssistantCompletionIdleWatch();
3145
- },
3146
- disarmAssistantCompletionIdleWatch: () => {
3147
- assistantCompletionIdleWatchArmed = false;
3148
- assistantCompletionLastActivityDetails = void 0;
3149
- clearAssistantCompletionIdleTimer();
3150
- },
3151
- touchActivity: (reason, options) => {
3152
- completionLastActivityAt = Date.now();
3153
- completionLastActivityReason = reason;
3154
- completionLastActivityDetails = options?.details;
3155
- completionIdleTimeoutOverrideMs = void 0;
3156
- if (options?.attemptProgress) recordAttemptProgress(reason, options);
3157
- params.onProgressDiagnostic(reason);
3158
- if (options?.arm) {
3159
- completionIdleWatchArmed = true;
3160
- completionIdleWatchPinnedByTerminalError = false;
3161
- }
3162
- scheduleProgressWatches();
3163
- },
3164
- noteNotificationReceived: (method, options) => {
3165
- completionLastActivityAt = Date.now();
3166
- completionLastActivityReason = `notification:${method}`;
3167
- if (options?.details !== void 0) completionLastActivityDetails = options.details;
3168
- if (options?.attemptProgress) recordAttemptProgress(completionLastActivityReason, options);
3169
- },
3170
- extendAttemptIdleWatch: (timeoutMs) => {
3171
- attemptIdleTimeoutOverrideMs = resolveWatchTimeoutMs(timeoutMs);
3172
- scheduleAttemptIdleWatch();
3173
- },
3174
- scheduleProgressWatches,
3175
- clearCompletionIdleTimer,
3176
- clearAssistantCompletionIdleTimer,
3177
- clearTerminalIdleTimer,
3178
- clearAttemptIdleTimer,
3179
- clearAllTimers
3180
- };
3181
- }
3182
- /** Hard cap for per-call Codex dynamic tool timeout overrides. */
3183
- const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
3184
- const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
3185
- /** Timeout for message-delivery dynamic tool calls. */
3186
- const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 12e4;
3187
- const LOG_FIELD_MAX_LENGTH = 160;
3188
- function normalizeLogField(value) {
3189
- if (typeof value !== "string") return;
3190
- const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
3191
- if (!normalized) return;
3192
- return normalized.length > LOG_FIELD_MAX_LENGTH ? `${normalized.slice(0, LOG_FIELD_MAX_LENGTH - 3)}...` : normalized;
3193
- }
3194
- function readNumericTimeoutMs(value) {
3195
- if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
3196
- if (typeof value === "string") {
3197
- const parsed = parseStrictNonNegativeInteger(value);
3198
- if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
3199
- }
3200
- }
3201
- function formatDynamicToolTimeoutDetails(params) {
3202
- const tool = normalizeLogField(params.call.tool) ?? "unknown";
3203
- const baseMeta = {
3204
- tool: params.call.tool,
3205
- toolCallId: params.call.callId,
3206
- threadId: params.call.threadId,
3207
- turnId: params.call.turnId,
3208
- timeoutMs: params.timeoutMs,
3209
- timeoutKind: "codex_dynamic_tool_rpc"
3210
- };
3211
- if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
3212
- responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
3213
- consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
3214
- meta: baseMeta
3215
- };
3216
- const action = normalizeLogField(params.call.arguments.action);
3217
- const sessionId = normalizeLogField(params.call.arguments.sessionId);
3218
- const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
3219
- const actionPart = action ? ` action=${action}` : "";
3220
- const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
3221
- const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
3222
- const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
3223
- const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
3224
- return {
3225
- responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
3226
- consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
3227
- meta: {
3228
- ...baseMeta,
3229
- processAction: action,
3230
- processSessionId: sessionId,
3231
- processRequestedTimeoutMs: requestedTimeoutMs
3232
- }
3233
- };
3234
- }
3235
- /**
3236
- * Runs a dynamic tool call with run-abort and per-call timeout handling,
3237
- * returning a Codex protocol response instead of throwing.
3238
- */
3239
- async function handleDynamicToolCallWithTimeout(params) {
3240
- let didNotifyAgentToolResult = false;
3241
- const notifyAgentToolResult = (event) => {
3242
- if (didNotifyAgentToolResult) return;
3243
- didNotifyAgentToolResult = true;
3244
- try {
3245
- params.onAgentToolResult?.(event);
3246
- } catch (error) {
3247
- embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
3248
- }
3249
- };
3250
- const notifyFailedToolResult = (message) => {
3251
- notifyAgentToolResult({
3252
- toolName: params.call.tool,
3253
- result: {
3254
- content: [{
3255
- type: "text",
3256
- text: message
3257
- }],
3258
- details: {
3259
- status: "failed",
3260
- error: message
3261
- }
3262
- },
3263
- isError: true
3264
- });
3265
- };
3266
- if (params.signal.aborted) {
3267
- const message = "OpenClaw dynamic tool call aborted before execution.";
3268
- params.onFallbackSelected?.();
3269
- notifyFailedToolResult(message);
3270
- return failedDynamicToolResponse(message);
3271
- }
3272
- const controller = new AbortController();
3273
- let timeout;
3274
- let timedOut = false;
3275
- let resolveAbort;
3276
- const abortFromRun = () => {
3277
- const message = "OpenClaw dynamic tool call aborted.";
3278
- params.onFallbackSelected?.();
3279
- controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
3280
- notifyFailedToolResult(message);
3281
- resolveAbort?.(failedDynamicToolResponse(message, { sideEffectEvidence: true }));
3282
- };
3283
- const abortPromise = new Promise((resolve) => {
3284
- resolveAbort = resolve;
3285
- });
3286
- const timeoutPromise = new Promise((resolve) => {
3287
- const timeoutMs = clampDynamicToolTimeoutMs(params.timeoutMs);
3288
- timeout = setTimeout(() => {
3289
- timedOut = true;
3290
- const timeoutDetails = formatDynamicToolTimeoutDetails({
3291
- call: params.call,
3292
- timeoutMs
3293
- });
3294
- params.onFallbackSelected?.();
3295
- controller.abort(new Error(timeoutDetails.responseMessage));
3296
- params.onTimeout?.();
3297
- embeddedAgentLog.warn("codex dynamic tool call timed out", {
3298
- ...timeoutDetails.meta,
3299
- consoleMessage: timeoutDetails.consoleMessage
3300
- });
3301
- notifyFailedToolResult(timeoutDetails.responseMessage);
3302
- resolve(failedDynamicToolResponse(timeoutDetails.responseMessage, { sideEffectEvidence: true }));
3303
- }, timeoutMs);
3304
- timeout.unref?.();
3305
- });
3306
- try {
3307
- params.signal.addEventListener("abort", abortFromRun, { once: true });
3308
- if (params.signal.aborted) abortFromRun();
3309
- const response = await Promise.race([
3310
- params.toolBridge.handleToolCall(params.call, {
3311
- signal: controller.signal,
3312
- onAgentToolResult: notifyAgentToolResult,
3313
- toolCallOrdinal: params.toolCallOrdinal
3314
- }),
3315
- abortPromise,
3316
- timeoutPromise
3317
- ]);
3318
- if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response));
3319
- return response;
3320
- } catch (error) {
3321
- const message = error instanceof Error ? error.message : String(error);
3322
- notifyFailedToolResult(message);
3323
- return failedDynamicToolResponse(message, { sideEffectEvidence: true });
3324
- } finally {
3325
- if (timeout) clearTimeout(timeout);
3326
- params.signal.removeEventListener("abort", abortFromRun);
3327
- resolveAbort = void 0;
3328
- if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
3329
- }
3330
- }
3331
- function readDynamicToolResponseText(response) {
3332
- return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
3333
- }
3334
- function failedDynamicToolResponse(message, options) {
3335
- const response = {
3336
- contentItems: [{
3337
- type: "inputText",
3338
- text: message
3339
- }],
3340
- success: false
3341
- };
3342
- Object.defineProperty(response, "diagnosticTerminalType", {
3343
- configurable: true,
3344
- enumerable: false,
3345
- value: "error"
3346
- });
3347
- if (options?.sideEffectEvidence === true) Object.defineProperty(response, "sideEffectEvidence", {
3348
- configurable: true,
3349
- enumerable: false,
3350
- value: true
3351
- });
3352
- return response;
3353
- }
3354
- /** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
3355
- function toCodexDynamicToolProtocolResponse(response) {
3356
- return {
3357
- contentItems: response.contentItems,
3358
- success: response.success
3359
- };
3360
- }
3361
- /** Adds async-started progress details when a tool result continues out of band. */
3362
- function toCodexDynamicToolProgressResponse(response, protocolResponse) {
3363
- if (response.asyncStarted !== true) return protocolResponse;
3364
- return {
3365
- ...protocolResponse,
3366
- details: {
3367
- async: true,
3368
- status: "started"
3369
- }
3370
- };
3371
- }
3372
- /** Decides whether a terminal dynamic tool response can release the Codex turn. */
3373
- function shouldReleaseTurnAfterTerminalDynamicTool(state) {
3374
- return !state.completed && !state.aborted && state.responseSuccess && !state.currentTurnHadNonTerminalDynamicToolResult && state.activeAppServerTurnRequests === 0 && state.activeTurnItemIdsCount === 0 && state.pendingOpenClawDynamicToolCompletionIdsCount === 0;
3375
- }
3376
- /** Returns true when a non-async result should block terminal-release shortcuts. */
3377
- function shouldBlockTerminalReleaseForNonTerminalDynamicToolResult(response) {
3378
- return response.asyncStarted !== true;
3379
- }
3380
- /** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
3381
- function resolveTerminalDynamicToolBatchAction(state) {
3382
- if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
3383
- if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
3384
- if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
3385
- return "idle";
3386
- }
3387
- /** Returns true for diagnostic events that terminate a dynamic tool call. */
3388
- function isDynamicToolTerminalDiagnosticEvent(event) {
3389
- return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
3390
- }
3391
- /** Matches terminal diagnostics to a specific dynamic tool call id/name. */
3392
- function isMatchingDynamicToolTerminalDiagnostic(params) {
3393
- if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
3394
- if (params.runId !== void 0) return params.event.runId === params.runId;
3395
- if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
3396
- if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
3397
- return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
3398
- }
3399
- /** Checks pending diagnostics for a terminal event matching a tool call. */
3400
- function hasPendingDynamicToolTerminalDiagnostic(params) {
3401
- return hasPendingInternalDiagnosticEvent((event) => {
3402
- if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
3403
- return isMatchingDynamicToolTerminalDiagnostic({
3404
- event,
3405
- call: params.call,
3406
- runId: params.runId,
3407
- sessionId: params.sessionId,
3408
- sessionKey: params.sessionKey
3409
- });
3410
- });
3411
- }
3412
- /** Resolves per-tool timeout, applying media/message defaults and hard caps. */
3413
- function resolveDynamicToolCallTimeoutMs(params) {
3414
- return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? 9e4);
3415
- }
3416
- function readDynamicToolCallTimeoutMs(value) {
3417
- if (!isJsonObject(value)) return;
3418
- return readPositiveFiniteTimeoutMs(value.timeoutMs);
3419
- }
3420
- function readConfiguredDynamicToolTimeoutMs(toolName, config) {
3421
- if (toolName === "image_generate") {
3422
- const imageGenerationModel = config?.agents?.defaults?.imageGenerationModel;
3423
- if (!imageGenerationModel || typeof imageGenerationModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
3424
- return readPositiveFiniteTimeoutMs(imageGenerationModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
3425
- }
3426
- if (toolName === "image") return readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds) ?? 6e4;
3427
- if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
3428
- }
3429
- function readTimeoutSecondsAsMs(value) {
3430
- const seconds = readPositiveFiniteTimeoutMs(value);
3431
- return seconds === void 0 ? void 0 : seconds * 1e3;
3432
- }
3433
- function readPositiveFiniteTimeoutMs(value) {
3434
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
3435
- }
3436
- function clampDynamicToolTimeoutMs(timeoutMs) {
3437
- return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
3438
- }
3439
- //#endregion
3440
- //#region extensions/codex/src/app-server/local-runtime-attribution.ts
3441
- const OPENAI_PROVIDER_ID = "openai";
3442
- const OPENAI_RESPONSES_API = "openai-responses";
3443
- const OPENAI_CODEX_RESPONSES_API = "openai-chatgpt-responses";
3444
- function normalizeRuntimeId(value) {
3445
- return value?.trim().toLowerCase() ?? "";
3446
- }
3447
- /** Maps local Codex runtime plans onto the provider/api pair exposed to event projection. */
3448
- function resolveCodexLocalRuntimeAttribution(params) {
3449
- const authProfileProvider = normalizeRuntimeId(params.runtimePlan?.auth?.authProfileProviderForAuth);
3450
- 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 {
3451
- provider: OPENAI_PROVIDER_ID,
3452
- api: OPENAI_CODEX_RESPONSES_API
3453
- };
3454
- return {
3455
- provider: params.provider,
3456
- api: params.model.api
3457
- };
3458
- }
3459
- //#endregion
3460
- //#region extensions/codex/src/app-server/tool-progress-normalization.ts
3461
- /**
3462
- * Normalizes and sanitizes Codex dynamic-tool progress payloads before they are
3463
- * emitted into OpenClaw events or logs.
3464
- */
3465
- /** Maps OpenClaw tool-progress config to the mode used by Codex progress metadata. */
3466
- function resolveCodexToolProgressDetailMode(value) {
3467
- return value === "raw" ? "raw" : "explain";
3468
- }
3469
- /** Recursively redacts sensitive strings and handles circular values in event payloads. */
3470
- function sanitizeCodexAgentEventValue(value, seen = /* @__PURE__ */ new WeakSet()) {
3471
- if (typeof value === "string") return redactToolPayloadText(value);
3472
- if (Array.isArray(value)) {
3473
- if (seen.has(value)) return "[Circular]";
3474
- seen.add(value);
3475
- return value.map((entry) => sanitizeCodexAgentEventValue(entry, seen));
3476
- }
3477
- if (value && typeof value === "object") {
3478
- if (seen.has(value)) return "[Circular]";
3479
- seen.add(value);
3480
- const out = {};
3481
- for (const [key, child] of Object.entries(value)) out[key] = typeof child === "string" ? redactSensitiveFieldValue(key, child) : sanitizeCodexAgentEventValue(child, seen);
3482
- return out;
3483
- }
3484
- return value;
3485
- }
3486
- /** Sanitizes a record-shaped Codex agent event payload. */
3487
- function sanitizeCodexAgentEventRecord(value) {
3488
- return sanitizeCodexAgentEventValue(value);
3489
- }
3490
- /** Sanitizes dynamic-tool arguments before diagnostic/event emission. */
3491
- function sanitizeCodexToolArguments(value) {
3492
- if (!isJsonObject(value)) return;
3493
- return sanitizeCodexAgentEventRecord(value);
3494
- }
3495
- /** Sanitizes a Codex dynamic-tool response before diagnostic/event emission. */
3496
- function sanitizeCodexToolResponse(response) {
3497
- return sanitizeCodexAgentEventRecord(response);
3498
- }
3499
- /** Infers compact human-readable tool metadata from Codex dynamic-tool arguments. */
3500
- function inferCodexDynamicToolMeta(call, detailMode) {
3501
- return inferToolMetaFromArgs(call.tool, call.arguments, { detailMode });
3502
- }
3503
- //#endregion
3504
- //#region extensions/codex/src/app-server/transcript-mirror.ts
3505
- const MIRROR_IDENTITY_META_KEY = "mirrorIdentity";
3506
- function buildSenderLabel(params) {
3507
- const label = params.senderName ?? params.senderUsername ?? params.senderE164 ?? params.senderId;
3508
- if (!label) return;
3509
- if (!params.senderId || label.includes(params.senderId)) return label;
3510
- return `${label} (${params.senderId})`;
3511
- }
3512
- function buildCodexUserPromptMessageFromPrepared(params, preparedUserMessage) {
3513
- const senderId = normalizeOptionalString(params.senderId);
3514
- const senderName = normalizeOptionalString(params.senderName);
3515
- const senderUsername = normalizeOptionalString(params.senderUsername);
3516
- const senderE164 = normalizeOptionalString(params.senderE164);
3517
- const senderLabel = buildSenderLabel({
3518
- senderId,
3519
- senderName,
3520
- senderUsername,
3521
- senderE164
3522
- });
3523
- const sourceChannel = normalizeOptionalString(params.inputProvenance?.sourceChannel ?? params.messageChannel ?? params.messageProvider);
3524
- if (preparedUserMessage) return {
3525
- role: "user",
3526
- timestamp: Date.now(),
3527
- ...params.inputProvenance ? { provenance: params.inputProvenance } : {},
3528
- ...sourceChannel ? { sourceChannel } : {},
3529
- ...senderId ? { senderId } : {},
3530
- ...senderName ? { senderName } : {},
3531
- ...senderUsername ? { senderUsername } : {},
3532
- ...senderE164 ? { senderE164 } : {},
3533
- ...senderLabel ? { senderLabel } : {},
3534
- ...preparedUserMessage
3535
- };
3536
- return {
3537
- role: "user",
3538
- content: params.prompt,
3539
- timestamp: Date.now(),
3540
- ...params.inputProvenance ? { provenance: params.inputProvenance } : {},
3541
- ...sourceChannel ? { sourceChannel } : {},
3542
- ...senderId ? { senderId } : {},
3543
- ...senderName ? { senderName } : {},
3544
- ...senderUsername ? { senderUsername } : {},
3545
- ...senderE164 ? { senderE164 } : {},
3546
- ...senderLabel ? { senderLabel } : {}
3547
- };
3548
- }
3549
- function buildCodexUserPromptMessage(params) {
3550
- return buildCodexUserPromptMessageFromPrepared(params, params.userTurnTranscriptRecorder?.message);
3551
- }
3552
- async function buildResolvedCodexUserPromptMessage(params) {
3553
- return buildCodexUserPromptMessageFromPrepared(params, await params.userTurnTranscriptRecorder?.resolveMessage() ?? params.userTurnTranscriptRecorder?.message);
3554
- }
3555
- async function mirrorTranscriptBestEffort(params) {
3556
- try {
3557
- const messages = await resolveFinalCodexMirrorMessages({
3558
- params: params.params,
3559
- messagesSnapshot: params.result.messagesSnapshot,
3560
- turnId: params.turnId
3561
- });
3562
- const mirrorResult = await mirrorCodexAppServerTranscript({
3563
- sessionFile: params.params.sessionFile,
3564
- agentId: params.agentId,
3565
- sessionKey: params.sessionKey,
3566
- sessionId: params.params.sessionId,
3567
- cwd: params.cwd,
3568
- messages,
3569
- idempotencyScope: `codex-app-server:${params.threadId}`,
3570
- config: params.params.config
3571
- });
3572
- for (const message of mirrorResult.userMessagesPresent) params.notifyUserMessagePersisted(message);
3573
- } catch (error) {
3574
- embeddedAgentLog.warn("failed to mirror codex app-server transcript", { error });
3575
- }
3576
- }
3577
- async function resolveFinalCodexMirrorMessages(params) {
3578
- if (params.params.suppressNextUserMessagePersistence || !params.params.userTurnTranscriptRecorder) return params.messagesSnapshot;
3579
- const resolvedPrompt = attachCodexMirrorIdentity(await buildResolvedCodexUserPromptMessage(params.params), `${params.turnId}:prompt`);
3580
- const firstUserIndex = params.messagesSnapshot.findIndex((message) => message.role === "user");
3581
- if (firstUserIndex === -1) return [resolvedPrompt, ...params.messagesSnapshot];
3582
- const messages = params.messagesSnapshot.slice();
3583
- messages[firstUserIndex] = resolvedPrompt;
3584
- return messages;
3585
- }
3586
- function createCodexAppServerUserMessagePersistenceNotifier(runParams) {
3587
- let notified = false;
3588
- return (message) => {
3589
- if (notified) return;
3590
- notified = true;
3591
- runParams.userTurnTranscriptRecorder?.markRuntimePersisted(message);
3592
- try {
3593
- runParams.onUserMessagePersisted?.(message);
3594
- } catch (error) {
3595
- embeddedAgentLog.warn("codex app-server user persistence notification failed", { error: formatErrorMessage(error) });
3596
- }
3597
- };
3598
- }
3599
- async function mirrorPromptAtTurnStartBestEffort(params) {
3600
- if (params.params.suppressNextUserMessagePersistence) return;
3601
- try {
3602
- const mirrorPromise = (async () => {
3603
- const userPromptMessage = attachCodexMirrorIdentity(await buildResolvedCodexUserPromptMessage(params.params), `${params.turnId}:prompt`);
3604
- const mirrorResult = await mirrorCodexAppServerTranscript({
3605
- sessionFile: params.params.sessionFile,
3606
- agentId: params.agentId,
3607
- sessionKey: params.sessionKey,
3608
- sessionId: params.params.sessionId,
3609
- cwd: params.cwd,
3610
- messages: [userPromptMessage],
3611
- idempotencyScope: `codex-app-server:${params.threadId}`,
3612
- config: params.params.config
3613
- });
3614
- for (const message of mirrorResult.userMessagesPresent) params.notifyUserMessagePersisted(message);
3615
- })();
3616
- params.params.userTurnTranscriptRecorder?.markRuntimePersistencePending(mirrorPromise);
3617
- await mirrorPromise;
3618
- } catch (error) {
3619
- embeddedAgentLog.warn("failed to mirror codex app-server prompt at turn start", { error });
3620
- }
3621
- }
3622
- /**
3623
- * Tag a message with a stable logical identity for mirror dedupe. Callers
3624
- * should use a value that is invariant for the same logical message across
3625
- * re-emits (e.g. `${turnId}:prompt`, `${turnId}:assistant`) but distinct
3626
- * for genuinely-distinct messages (different turns, different kinds). When
3627
- * present this identity replaces the role/content fingerprint in the
3628
- * idempotency key, so the dedupe survives caller-scope rotation without
3629
- * collapsing distinct same-content turns.
3630
- */
3631
- function attachCodexMirrorIdentity(message, identity) {
3632
- const record = message;
3633
- const existing = record["__openclaw"];
3634
- const baseMeta = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
3635
- return {
3636
- ...record,
3637
- __openclaw: {
3638
- ...baseMeta,
3639
- [MIRROR_IDENTITY_META_KEY]: identity
3640
- }
3641
- };
3642
- }
3643
- function readMirrorIdentity(message) {
3644
- const meta = message["__openclaw"];
3645
- if (!meta || typeof meta !== "object" || Array.isArray(meta)) return;
3646
- const id = meta[MIRROR_IDENTITY_META_KEY];
3647
- return typeof id === "string" && id.length > 0 ? id : void 0;
3648
- }
3649
- function fingerprintMirrorMessageContent(message) {
3650
- const payload = JSON.stringify({
3651
- role: message.role,
3652
- content: message.content
3653
- });
3654
- return createHash("sha256").update(payload).digest("hex").slice(0, 16);
3655
- }
3656
- function buildMirrorDedupeIdentity(message) {
3657
- const explicit = readMirrorIdentity(message);
3658
- if (explicit) return explicit;
3659
- return `${message.role}:${fingerprintMirrorMessageContent(message)}`;
3660
- }
3661
- async function mirrorCodexAppServerTranscript(params) {
3662
- const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
3663
- if (messages.length === 0) return { userMessagesPresent: [] };
3664
- const transcriptTarget = resolveCodexMirrorTranscriptTarget(params);
3665
- const { appendedUpdates, userMessagesPresent } = await withSessionTranscriptWriteLock({
3666
- ...transcriptTarget,
3667
- config: params.config
3668
- }, async (transcript) => {
3669
- const nextAppendedUpdates = [];
3670
- const nextUserMessagesPresent = [];
3671
- const mirrorState = readTranscriptMirrorState(await transcript.readEvents());
3672
- let nextMessageSeq = mirrorState.messageCount;
3673
- for (const message of messages) {
3674
- const dedupeIdentity = buildMirrorDedupeIdentity(message);
3675
- const idempotencyKey = params.idempotencyScope ? `${params.idempotencyScope}:${dedupeIdentity}` : void 0;
3676
- const transcriptMessage = {
3677
- ...message,
3678
- ...idempotencyKey ? { idempotencyKey } : {}
3679
- };
3680
- if (idempotencyKey && mirrorState.idempotencyKeys.has(idempotencyKey)) {
3681
- const persistedUserMessage = mirrorState.userMessagesByIdempotencyKey.get(idempotencyKey);
3682
- if (persistedUserMessage) nextUserMessagesPresent.push(persistedUserMessage);
3683
- continue;
3684
- }
3685
- const nextMessage = runAgentHarnessBeforeMessageWriteHook({
3686
- message: transcriptMessage,
3687
- agentId: params.agentId,
3688
- sessionKey: params.sessionKey
3689
- });
3690
- if (!nextMessage) continue;
3691
- const messageToAppend = idempotencyKey ? {
3692
- ...nextMessage,
3693
- idempotencyKey
3694
- } : nextMessage;
3695
- const appended = await transcript.appendMessage({
3696
- message: messageToAppend,
3697
- idempotencyLookup: idempotencyKey ? "caller-checked" : "scan",
3698
- cwd: params.cwd
3699
- });
3700
- if (!appended) continue;
3701
- const { messageId, message: appendedMessage } = appended;
3702
- if (appendedMessage.role === "user") {
3703
- nextUserMessagesPresent.push(appendedMessage);
3704
- if (idempotencyKey) mirrorState.userMessagesByIdempotencyKey.set(idempotencyKey, appendedMessage);
3705
- }
3706
- nextMessageSeq += 1;
3707
- nextAppendedUpdates.push({
3708
- messageId,
3709
- message: appendedMessage,
3710
- messageSeq: nextMessageSeq
3711
- });
3712
- if (idempotencyKey) mirrorState.idempotencyKeys.add(idempotencyKey);
3713
- }
3714
- return {
3715
- appendedUpdates: nextAppendedUpdates,
3716
- userMessagesPresent: nextUserMessagesPresent
3717
- };
3718
- });
3719
- for (const update of appendedUpdates) await publishSessionTranscriptUpdateByIdentity({
3720
- ...transcriptTarget,
3721
- update: {
3722
- ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3723
- ...params.agentId ? { agentId: params.agentId } : {},
3724
- message: update.message,
3725
- messageId: update.messageId,
3726
- messageSeq: update.messageSeq
3727
- }
3728
- });
3729
- return { userMessagesPresent };
3730
- }
3731
- function resolveCodexMirrorTranscriptTarget(params) {
3732
- return {
3733
- ...params.agentId ? { agentId: params.agentId } : {},
3734
- sessionFile: params.sessionFile,
3735
- sessionId: params.sessionId,
3736
- sessionKey: params.sessionKey ?? ""
3737
- };
3738
- }
3739
- function readTranscriptMirrorState(events) {
3740
- const idempotencyKeys = /* @__PURE__ */ new Set();
3741
- const userMessagesByIdempotencyKey = /* @__PURE__ */ new Map();
3742
- let messageCount = 0;
3743
- for (const event of events) {
3744
- if (!event || typeof event !== "object" || Array.isArray(event)) continue;
3745
- const parsed = event;
3746
- if (parsed.type === "message") messageCount += 1;
3747
- if (typeof parsed.message?.idempotencyKey === "string") {
3748
- idempotencyKeys.add(parsed.message.idempotencyKey);
3749
- if (parsed.message.role === "user") userMessagesByIdempotencyKey.set(parsed.message.idempotencyKey, parsed.message);
3750
- }
3751
- }
3752
- return {
3753
- idempotencyKeys,
3754
- messageCount,
3755
- userMessagesByIdempotencyKey
3756
- };
3757
- }
3758
- //#endregion
3759
- //#region extensions/codex/src/app-server/event-projector.ts
3760
- const ZERO_USAGE = {
3761
- input: 0,
3762
- output: 0,
3763
- cacheRead: 0,
3764
- cacheWrite: 0,
3765
- totalTokens: 0,
3766
- cost: {
3767
- input: 0,
3768
- output: 0,
3769
- cacheRead: 0,
3770
- cacheWrite: 0,
3771
- total: 0
3772
- }
3773
- };
3774
- const CURRENT_TOKEN_USAGE_KEYS = [
3775
- "last",
3776
- "current",
3777
- "lastCall",
3778
- "lastCallUsage",
3779
- "lastTokenUsage",
3780
- "last_token_usage"
3781
- ];
3782
- const CODEX_PROMPT_TOTAL_INPUT_KEYS = [
3783
- "inputTokens",
3784
- "input_tokens",
3785
- "promptTokens",
3786
- "prompt_tokens"
3787
- ];
3788
- const MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM = 20;
3789
- const TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS = 12e3;
3790
- const MISSING_TOOL_RESULT_ERROR = "OpenClaw recorded a native Codex tool.call without a matching tool.result before the turn completed.";
3791
- const GENERATED_IMAGE_MEDIA_SUBDIR = "tool-image-generation";
3792
- const BYTES_PER_MB = 1024 * 1024;
3793
- const DEFAULT_GENERATED_IMAGE_MAX_BYTES = 6 * BYTES_PER_MB;
3794
- const TRANSCRIPT_PROGRESS_SUPPRESSED_TOOL_NAMES = new Set([
3795
- "message",
3796
- "messages",
3797
- "reply",
3798
- "send",
3799
- "reaction",
3800
- "react",
3801
- "typing"
3802
- ]);
3803
- function shouldEmitTranscriptToolProgress(toolName, _args) {
3804
- const normalized = typeof toolName === "string" ? toolName.trim().toLowerCase() : "";
3805
- return Boolean(normalized && !TRANSCRIPT_PROGRESS_SUPPRESSED_TOOL_NAMES.has(normalized));
3806
- }
3807
- var CodexAppServerEventProjector = class {
3808
- constructor(params, threadId, turnId, options = {}) {
3809
- this.params = params;
3810
- this.threadId = threadId;
3811
- this.turnId = turnId;
3812
- this.options = options;
3813
- this.assistantTextByItem = /* @__PURE__ */ new Map();
3814
- this.assistantItemOrder = [];
3815
- this.assistantPhaseByItem = /* @__PURE__ */ new Map();
3816
- this.latestTerminalAssistantCandidateSuperseded = false;
3817
- this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff = false;
3818
- this.terminalAssistantCandidateEarlierActiveItemIds = /* @__PURE__ */ new Set();
3819
- this.lastCommentaryProgressTextByItem = /* @__PURE__ */ new Map();
3820
- this.pendingRawCommentaryEchoes = 0;
3821
- this.reasoningTextByGroup = /* @__PURE__ */ new Map();
3822
- this.reasoningItemOrder = /* @__PURE__ */ new Map();
3823
- this.planTextByItem = /* @__PURE__ */ new Map();
3824
- this.activeItemIds = /* @__PURE__ */ new Set();
3825
- this.completedItemIds = /* @__PURE__ */ new Set();
3826
- this.activeCompactionItemIds = /* @__PURE__ */ new Set();
3827
- this.toolProgressTexts = /* @__PURE__ */ new Set();
3828
- this.toolResultSummaryItemIds = /* @__PURE__ */ new Set();
3829
- this.toolResultOutputItemIds = /* @__PURE__ */ new Set();
3830
- this.toolResultOutputStreamedItemIds = /* @__PURE__ */ new Set();
3831
- this.transcriptToolProgressSuppressedIds = /* @__PURE__ */ new Set();
3832
- this.toolTranscriptArgumentsById = /* @__PURE__ */ new Map();
3833
- this.toolResultOutputDeltaState = /* @__PURE__ */ new Map();
3834
- this.toolResultOutputTextByItem = /* @__PURE__ */ new Map();
3835
- this.toolMetas = /* @__PURE__ */ new Map();
3836
- this.terminalPresentationClearedItemIds = /* @__PURE__ */ new Set();
3837
- this.nativeToolOutcomeOrdinals = /* @__PURE__ */ new Map();
3838
- this.sideEffectingToolItemIds = /* @__PURE__ */ new Set();
3839
- this.sideEffectingDynamicToolCallIds = /* @__PURE__ */ new Set();
3840
- this.toolTranscriptMessages = [];
3841
- this.toolTranscriptCallIds = /* @__PURE__ */ new Set();
3842
- this.toolTranscriptResultIds = /* @__PURE__ */ new Set();
3843
- this.toolTranscriptNamesById = /* @__PURE__ */ new Map();
3844
- this.toolTrajectoryCallIds = /* @__PURE__ */ new Set();
3845
- this.toolTrajectoryResultIds = /* @__PURE__ */ new Set();
3846
- this.toolTrajectoryNamesById = /* @__PURE__ */ new Map();
3847
- this.toolTrajectoryItemsById = /* @__PURE__ */ new Map();
3848
- this.transcriptToolProgressCallIds = /* @__PURE__ */ new Set();
3849
- this.nativeGeneratedMediaItemIds = /* @__PURE__ */ new Set();
3850
- this.nativeGeneratedMediaUrlsByItemId = /* @__PURE__ */ new Map();
3851
- this.diagnosticToolStartedAtByItem = /* @__PURE__ */ new Map();
3852
- this.afterToolCallObservedItemIds = /* @__PURE__ */ new Set();
3853
- this.assistantStarted = false;
3854
- this.reasoningStarted = false;
3855
- this.reasoningEnded = false;
3856
- this.streamedPartialAssistantItemReplaceable = false;
3857
- this.promptErrorSource = null;
3858
- this.synthesizedMissingToolResultError = null;
3859
- this.aborted = false;
3860
- this.guardianReviewCount = 0;
3861
- this.completedCompactionCount = 0;
3862
- }
3863
- getCompletedTurnStatus() {
3864
- return this.completedTurn?.status;
3865
- }
3866
- hasCompletedTerminalAssistantText() {
3867
- const latestCompletedItemId = this.latestCompletedTerminalAssistantItemId;
3868
- if (!latestCompletedItemId) return false;
3869
- const finalItem = this.resolveFinalAssistantTextItem();
3870
- return this.latestCompletedItemId === latestCompletedItemId && finalItem?.itemId === latestCompletedItemId && this.completedItemIds.has(latestCompletedItemId);
3871
- }
3872
- getLatestTerminalAssistantCandidate() {
3873
- const itemId = this.latestTerminalAssistantCandidateItemId;
3874
- if (!itemId) return;
3875
- const text = this.assistantTextByItem.get(itemId)?.trim();
3876
- return {
3877
- itemId,
3878
- hasText: Boolean(text && !this.toolProgressTexts.has(text))
3879
- };
3880
- }
3881
- hasLatestTerminalAssistantCandidateText() {
3882
- return !this.latestTerminalAssistantCandidateSuperseded && this.getLatestTerminalAssistantCandidate()?.hasText === true;
3883
- }
3884
- canReleaseLatestTerminalAssistantAfterToolHandoff() {
3885
- return this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff && this.hasLatestTerminalAssistantCandidateText();
3886
- }
3887
- /** Restores a completed final item after only the enclosing turn timeout fired. */
3888
- recoverCompletedTerminalAssistantAfterTurnWatchTimeout() {
3889
- if (!this.aborted || this.promptError !== "codex app-server attempt timed out" || !this.hasCompletedTerminalAssistantText()) return false;
3890
- this.aborted = false;
3891
- this.promptError = void 0;
3892
- this.promptErrorSource = null;
3893
- return true;
3894
- }
3895
- /** Resolves the shared model-order position for a native tool item. */
3896
- recordNativeToolOutcome(item) {
3897
- if (!item || this.nativeToolOutcomeOrdinals.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
3898
- const ordinal = this.params.allocateToolOutcomeOrdinal?.(item.id);
3899
- if (ordinal !== void 0) this.nativeToolOutcomeOrdinals.set(item.id, ordinal);
3900
- }
3901
- async handleNotification(notification) {
3902
- const params = isJsonObject(notification.params) ? notification.params : void 0;
3903
- if (!params) return;
3904
- if (notification.method === "account/rateLimits/updated") {
3905
- this.latestRateLimits = params;
3906
- rememberCodexRateLimits(params);
3907
- return;
3908
- }
3909
- if (isHookNotificationMethod(notification.method)) {
3910
- if (!this.isHookNotificationForCurrentThread(params)) return;
3911
- } else if (!this.isNotificationForTurn(params)) return;
3912
- switch (notification.method) {
3913
- case "item/agentMessage/delta":
3914
- await this.handleAssistantDelta(params);
3915
- break;
3916
- case "item/reasoning/summaryTextDelta":
3917
- case "item/reasoning/textDelta":
3918
- await this.handleReasoningDelta(notification.method, params);
3919
- break;
3920
- case "item/plan/delta":
3921
- this.handlePlanDelta(params);
3922
- break;
3923
- case "turn/plan/updated":
3924
- this.handleTurnPlanUpdated(params);
3925
- break;
3926
- case "item/started":
3927
- await this.handleItemStarted(params);
3928
- break;
3929
- case "item/completed":
3930
- await this.handleItemCompleted(params);
3931
- break;
3932
- case "item/commandExecution/outputDelta":
3933
- this.handleOutputDelta(params, "bash");
3934
- break;
3935
- case "item/fileChange/outputDelta":
3936
- this.handleOutputDelta(params, "apply_patch");
3937
- break;
3938
- case "item/autoApprovalReview/started":
3939
- case "item/autoApprovalReview/completed":
3940
- this.handleGuardianReviewNotification(notification.method, params);
3941
- break;
3942
- case "hook/started":
3943
- case "hook/completed":
3944
- this.handleHookNotification(notification.method, params);
3945
- break;
3946
- case "thread/tokenUsage/updated":
3947
- this.handleTokenUsage(params);
3948
- break;
3949
- case "turn/completed":
3950
- await this.handleTurnCompleted(params);
3951
- break;
3952
- case "rawResponseItem/completed":
3953
- await this.handleRawResponseItemCompleted(params);
3954
- break;
3955
- case "error":
3956
- if (readBooleanAlias(params, ["willRetry", "will_retry"]) === true) break;
3957
- this.promptError = this.formatCodexErrorMessage(params) ?? "codex app-server error";
3958
- this.promptErrorSource = "prompt";
3959
- break;
3960
- default: break;
3961
- }
3962
- }
3963
- buildResult(toolTelemetry, options) {
3964
- const assistantTexts = this.collectAssistantTexts();
3965
- const reasoningText = collectReasoningTextValues(this.reasoningTextByGroup, this.reasoningItemOrder).join("\n\n");
3966
- const planText = collectTextValues(this.planTextByItem).join("\n\n");
3967
- const hasAssistantItemText = this.hasAssistantItemTextForSynthesis();
3968
- const legacyFailClosed = !this.completedTurn || this.completedTurn.status !== "completed" || hasAssistantItemText;
3969
- const hasDeliverableAssistantOnCompletedTurn = this.completedTurn?.status === "completed" && assistantTexts.some((text) => text.trim().length > 0);
3970
- this.synthesizeMissingToolResults({
3971
- synthesize: legacyFailClosed,
3972
- recordPromptError: legacyFailClosed && !hasDeliverableAssistantOnCompletedTurn
3973
- });
3974
- const lastAssistant = assistantTexts.length > 0 ? this.createAssistantMessage(assistantTexts.join("\n\n")) : void 0;
3975
- const currentAttemptAssistant = this.createCurrentAttemptAssistantMessage();
3976
- const turnId = this.turnId;
3977
- const messagesSnapshot = this.params.suppressNextUserMessagePersistence ? [] : [attachCodexMirrorIdentity(buildCodexUserPromptMessage(this.params), `${turnId}:prompt`)];
3978
- if (reasoningText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex reasoning", reasoningText), `${turnId}:reasoning`));
3979
- if (planText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex plan", planText), `${turnId}:plan`));
3980
- messagesSnapshot.push(...this.toolTranscriptMessages);
3981
- if (lastAssistant) messagesSnapshot.push(attachCodexMirrorIdentity(lastAssistant, `${turnId}:assistant`));
3982
- const turnFailed = this.completedTurn?.status === "failed";
3983
- const promptError = this.promptError ?? this.synthesizedMissingToolResultError ?? (turnFailed ? this.completedTurn?.error?.message ?? "codex app-server turn failed" : null);
3984
- const agentHarnessResultClassification = classifyAgentHarnessTerminalOutcome({
3985
- assistantTexts,
3986
- reasoningText,
3987
- planText,
3988
- promptError,
3989
- turnCompleted: Boolean(this.completedTurn)
3990
- });
3991
- const toolMetas = [...this.toolMetas.values()];
3992
- const hadPotentialSideEffects = toolTelemetry.didSendViaMessagingTool || (toolTelemetry.successfulCronAdds ?? 0) > 0 || this.nativeGeneratedMediaItemIds.size > 0 || this.sideEffectingToolItemIds.size > 0 || this.sideEffectingDynamicToolCallIds.size > 0;
3993
- return {
3994
- aborted: this.aborted,
3995
- externalAbort: false,
3996
- timedOut: false,
3997
- idleTimedOut: false,
3998
- timedOutDuringCompaction: false,
3999
- timedOutDuringToolExecution: false,
4000
- promptError,
4001
- promptErrorSource: promptError ? this.promptErrorSource || "prompt" : null,
4002
- sessionIdUsed: this.params.sessionId,
4003
- ...agentHarnessResultClassification ? { agentHarnessResultClassification } : {},
4004
- bootstrapPromptWarningSignaturesSeen: this.params.bootstrapPromptWarningSignaturesSeen,
4005
- bootstrapPromptWarningSignature: this.params.bootstrapPromptWarningSignature,
4006
- messagesSnapshot,
4007
- assistantTexts,
4008
- toolMetas,
4009
- lastAssistant,
4010
- currentAttemptAssistant,
4011
- ...this.lastNativeToolError ? { lastToolError: this.lastNativeToolError } : {},
4012
- didSendViaMessagingTool: toolTelemetry.didSendViaMessagingTool,
4013
- didDeliverSourceReplyViaMessageTool: toolTelemetry.didDeliverSourceReplyViaMessageTool === true,
4014
- messagingToolSentTexts: toolTelemetry.messagingToolSentTexts,
4015
- messagingToolSentMediaUrls: toolTelemetry.messagingToolSentMediaUrls,
4016
- messagingToolSentTargets: toolTelemetry.messagingToolSentTargets,
4017
- messagingToolSourceReplyPayloads: toolTelemetry.messagingToolSourceReplyPayloads ?? [],
4018
- heartbeatToolResponse: toolTelemetry.heartbeatToolResponse,
4019
- toolMediaUrls: this.buildToolMediaUrls(toolTelemetry),
4020
- toolAudioAsVoice: toolTelemetry.toolAudioAsVoice,
4021
- successfulCronAdds: toolTelemetry.successfulCronAdds,
4022
- cloudCodeAssistFormatError: false,
4023
- attemptUsage: this.tokenUsage,
4024
- replayMetadata: {
4025
- hadPotentialSideEffects,
4026
- replaySafe: !hadPotentialSideEffects
4027
- },
4028
- itemLifecycle: {
4029
- startedCount: this.activeItemIds.size + this.completedItemIds.size,
4030
- completedCount: this.completedItemIds.size,
4031
- activeCount: this.activeItemIds.size,
4032
- ...this.completedCompactionCount > 0 ? { compactionCount: this.completedCompactionCount } : {}
4033
- },
4034
- yieldDetected: options?.yieldDetected || false,
4035
- didSendDeterministicApprovalPrompt: this.guardianReviewCount > 0 ? false : void 0
4036
- };
4037
- }
4038
- recordDynamicToolCall(params) {
4039
- const args = sanitizeCodexToolArguments(params.arguments);
4040
- this.recordToolTranscriptCall({
4041
- id: params.callId,
4042
- name: params.tool,
4043
- arguments: args
4044
- });
4045
- }
4046
- recordDynamicToolResult(params) {
4047
- const resultText = collectDynamicToolContentText(params.contentItems);
4048
- if (params.asyncStarted === true) {
4049
- const existing = this.toolMetas.get(params.callId);
4050
- this.toolMetas.set(params.callId, {
4051
- toolName: existing?.toolName ?? params.tool,
4052
- ...existing?.meta ? { meta: existing.meta } : {},
4053
- asyncStarted: true
4054
- });
4055
- }
4056
- this.recordToolTranscriptResult({
4057
- id: params.callId,
4058
- name: params.tool,
4059
- text: resultText,
4060
- isError: !params.success
4061
- });
4062
- if (!params.success && params.terminalType === "blocked") this.lastNativeToolError = {
4063
- toolName: params.tool,
4064
- error: resultText || "codex dynamic tool blocked"
4065
- };
4066
- else if (params.success && this.lastNativeToolError && !this.lastNativeToolError.mutatingAction) this.lastNativeToolError = void 0;
4067
- if (params.sideEffectEvidence === true) this.sideEffectingDynamicToolCallIds.add(params.callId);
4068
- }
4069
- markTimedOut() {
4070
- this.aborted = true;
4071
- this.promptError = "codex app-server attempt timed out";
4072
- this.promptErrorSource = "prompt";
4073
- }
4074
- markAborted() {
4075
- this.aborted = true;
4076
- }
4077
- isCompacting() {
4078
- return this.activeCompactionItemIds.size > 0;
4079
- }
4080
- async handleAssistantDelta(params) {
4081
- const itemId = readString$5(params, "itemId") ?? readString$5(params, "id") ?? "assistant";
4082
- const delta = readString$5(params, "delta") ?? "";
4083
- if (!delta) return;
4084
- if (itemId !== this.pendingRawTerminalAssistantEchoItemId) this.pendingRawTerminalAssistantEchoItemId = void 0;
4085
- this.rememberAssistantPhase(readItem(params.item));
4086
- const phase = readString$5(params, "phase");
4087
- if (phase) this.assistantPhaseByItem.set(itemId, phase);
4088
- const isCommentary = this.isCommentaryAssistantItem(itemId);
4089
- if (!isCommentary && itemId !== this.latestTerminalAssistantCandidateItemId) this.markTerminalAssistantCandidateSupersededBy();
4090
- if (!this.assistantStarted) {
4091
- this.assistantStarted = true;
4092
- await this.params.onAssistantMessageStart?.();
4093
- }
4094
- this.rememberAssistantItem(itemId);
4095
- const text = `${this.assistantTextByItem.get(itemId) ?? ""}${delta}`;
4096
- this.assistantTextByItem.set(itemId, text);
4097
- if (isCommentary) this.emitCommentaryProgress({
4098
- itemId,
4099
- text
4100
- });
4101
- else {
4102
- const knownFinalAnswer = this.shouldStreamAssistantPartial(itemId);
4103
- const replace = this.streamedPartialAssistantItemId !== void 0 && this.streamedPartialAssistantItemId !== itemId;
4104
- if (replace && (!knownFinalAnswer || this.streamedPartialAssistantItemReplaceable)) this.streamedPartialAssistantItemReplaceable = true;
4105
- else if (this.streamedPartialAssistantItemId === void 0) this.streamedPartialAssistantItemReplaceable = !knownFinalAnswer;
4106
- this.streamedPartialAssistantItemId = itemId;
4107
- const replaceable = this.streamedPartialAssistantItemReplaceable;
4108
- const replacement = replace && replaceable;
4109
- const streamPayload = {
4110
- text,
4111
- delta: replacement ? "" : delta,
4112
- ...replacement ? { replace: true } : {}
4113
- };
4114
- this.emitAgentEvent({
4115
- stream: "assistant",
4116
- data: {
4117
- ...streamPayload,
4118
- ...replaceable ? { replaceable: true } : {}
4119
- }
4120
- });
4121
- if (knownFinalAnswer && !replaceable) await this.params.onPartialReply?.(streamPayload);
4122
- }
4123
- }
4124
- async handleReasoningDelta(method, params) {
4125
- const itemId = readString$5(params, "itemId") ?? readString$5(params, "id") ?? "reasoning";
4126
- const delta = readString$5(params, "delta") ?? "";
4127
- if (!delta) return;
4128
- this.reasoningStarted = true;
4129
- if (!this.reasoningItemOrder.has(itemId)) this.reasoningItemOrder.set(itemId, this.reasoningItemOrder.size);
4130
- const groupIndex = method === "item/reasoning/textDelta" ? readNonNegativeInteger(params, "contentIndex") ?? 0 : readNonNegativeInteger(params, "summaryIndex") ?? 0;
4131
- const groupKey = `${method}\0${itemId}\0${groupIndex}`;
4132
- const current = this.reasoningTextByGroup.get(groupKey);
4133
- this.reasoningTextByGroup.set(groupKey, {
4134
- itemId,
4135
- method,
4136
- index: groupIndex,
4137
- text: `${current?.text ?? ""}${delta}`
4138
- });
4139
- await this.params.onReasoningStream?.({
4140
- text: collectReasoningTextValues(this.reasoningTextByGroup, this.reasoningItemOrder).join("\n\n"),
4141
- isReasoningSnapshot: true
4142
- });
4143
- }
4144
- handlePlanDelta(params) {
4145
- const itemId = readString$5(params, "itemId") ?? readString$5(params, "id") ?? "plan";
4146
- const delta = readString$5(params, "delta") ?? "";
4147
- if (!delta) return;
4148
- const text = `${this.planTextByItem.get(itemId) ?? ""}${delta}`;
4149
- this.planTextByItem.set(itemId, text);
4150
- this.emitPlanUpdate({
4151
- explanation: void 0,
4152
- steps: splitPlanText(text)
4153
- });
4154
- }
4155
- handleTurnPlanUpdated(params) {
4156
- const plan = Array.isArray(params.plan) ? params.plan.flatMap((entry) => {
4157
- if (!isJsonObject(entry)) return [];
4158
- const step = readString$5(entry, "step");
4159
- const status = readString$5(entry, "status");
4160
- if (!step) return [];
4161
- return status ? [`${step} (${status})`] : [step];
4162
- }) : void 0;
4163
- this.emitPlanUpdate({
4164
- explanation: readNullableString$1(params, "explanation"),
4165
- steps: plan
4166
- });
4167
- }
4168
- async handleItemStarted(params) {
4169
- const item = readItem(params.item);
4170
- const itemId = item?.id ?? readString$5(params, "itemId") ?? readString$5(params, "id");
4171
- if (item?.type === "agentMessage" && itemId && itemId !== this.pendingRawTerminalAssistantEchoItemId) this.pendingRawTerminalAssistantEchoItemId = void 0;
4172
- this.rememberAssistantPhase(item);
4173
- if (itemId) {
4174
- this.activeItemIds.add(itemId);
4175
- if (itemId !== this.latestTerminalAssistantCandidateItemId) {
4176
- this.markTerminalAssistantCandidateSupersededBy(itemId, { preserveEarlierActiveItem: true });
4177
- if (this.latestTerminalAssistantCandidateSuperseded) this.pendingRawTerminalAssistantEchoItemId = void 0;
4178
- }
4179
- }
4180
- this.recordNativeToolOutcome(item);
4181
- if (item?.type === "contextCompaction" && itemId) {
4182
- this.activeCompactionItemIds.add(itemId);
4183
- await runAgentHarnessBeforeCompactionHook({
4184
- sessionFile: this.params.sessionFile,
4185
- messages: await this.readMirroredSessionMessages(),
4186
- ctx: {
4187
- runId: this.params.runId,
4188
- agentId: this.params.agentId,
4189
- sessionKey: this.params.sessionKey,
4190
- sessionId: this.params.sessionId,
4191
- workspaceDir: this.params.workspaceDir,
4192
- messageProvider: this.params.messageProvider ?? void 0,
4193
- trigger: this.params.trigger,
4194
- channelId: this.params.messageChannel ?? this.params.messageProvider ?? void 0
4195
- }
4196
- });
4197
- this.emitAgentEvent({
4198
- stream: "compaction",
4199
- data: {
4200
- phase: "start",
4201
- backend: "codex-app-server",
4202
- threadId: this.threadId,
4203
- turnId: this.turnId,
4204
- itemId
4205
- }
4206
- });
4207
- }
4208
- this.recordToolMeta(item);
4209
- this.emitStandardItemEvent({
4210
- phase: "start",
4211
- item
4212
- });
4213
- await this.emitNormalizedToolItemEvent({
4214
- phase: "start",
4215
- item
4216
- });
4217
- this.recordNativeToolTranscriptCall(item);
4218
- this.emitToolResultSummary(item);
4219
- this.emitAgentEvent({
4220
- stream: "codex_app_server.item",
4221
- data: {
4222
- phase: "started",
4223
- itemId,
4224
- type: item?.type
4225
- }
4226
- });
4227
- }
4228
- async handleItemCompleted(params) {
4229
- const item = readItem(params.item);
4230
- this.recordNativeToolOutcome(item);
4231
- this.clearTerminalPresentationForNativeItem(item);
4232
- const itemId = item?.id ?? readString$5(params, "itemId") ?? readString$5(params, "id");
4233
- if (item?.type === "agentMessage" && itemId && itemId !== this.pendingRawTerminalAssistantEchoItemId) this.pendingRawTerminalAssistantEchoItemId = void 0;
4234
- if (itemId) {
4235
- this.activeItemIds.delete(itemId);
4236
- this.completedItemIds.add(itemId);
4237
- this.latestCompletedItemId = itemId;
4238
- }
4239
- this.rememberAssistantPhase(item);
4240
- if (item?.type === "agentMessage" && !this.isCommentaryAssistantItem(item.id)) {
4241
- this.latestCompletedTerminalAssistantItemId = item.id;
4242
- this.markLatestTerminalAssistantCandidate(item.id);
4243
- this.pendingRawTerminalAssistantEchoItemId = item.id;
4244
- } else if (itemId) {
4245
- this.markTerminalAssistantCandidateSupersededBy(itemId, { preserveEarlierActiveItem: true });
4246
- if (this.latestTerminalAssistantCandidateSuperseded) this.pendingRawTerminalAssistantEchoItemId = void 0;
4247
- }
4248
- if (item?.type === "agentMessage" && typeof item.text === "string") {
4249
- this.rememberAssistantItem(item.id);
4250
- this.assistantTextByItem.set(item.id, item.text);
4251
- if (item.text && this.isCommentaryAssistantItem(item.id)) {
4252
- this.emitCommentaryProgress({
4253
- itemId: item.id,
4254
- text: item.text
4255
- });
4256
- this.pendingRawCommentaryEchoes += 1;
4257
- }
4258
- }
4259
- this.recordNativeGeneratedMedia(item);
4260
- if (item?.type === "plan" && typeof item.text === "string" && item.text) {
4261
- this.planTextByItem.set(item.id, item.text);
4262
- this.emitPlanUpdate({
4263
- explanation: void 0,
4264
- steps: splitPlanText(item.text)
4265
- });
4266
- }
4267
- if (item?.type === "contextCompaction" && itemId) {
4268
- this.activeCompactionItemIds.delete(itemId);
4269
- this.completedCompactionCount += 1;
4270
- await runAgentHarnessAfterCompactionHook({
4271
- sessionFile: this.params.sessionFile,
4272
- messages: await this.readMirroredSessionMessages(),
4273
- compactedCount: -1,
4274
- ctx: {
4275
- runId: this.params.runId,
4276
- agentId: this.params.agentId,
4277
- sessionKey: this.params.sessionKey,
4278
- sessionId: this.params.sessionId,
4279
- workspaceDir: this.params.workspaceDir,
4280
- messageProvider: this.params.messageProvider ?? void 0,
4281
- trigger: this.params.trigger,
4282
- channelId: this.params.messageChannel ?? this.params.messageProvider ?? void 0
4283
- }
4284
- });
4285
- this.emitAgentEvent({
4286
- stream: "compaction",
4287
- data: {
4288
- phase: "end",
4289
- backend: "codex-app-server",
4290
- completed: true,
4291
- threadId: this.threadId,
4292
- turnId: this.turnId,
4293
- itemId
4294
- }
4295
- });
4296
- }
4297
- this.recordToolMeta(item);
4298
- this.emitStandardItemEvent({
4299
- phase: "end",
4300
- item
4301
- });
4302
- await this.emitNormalizedToolItemEvent({
4303
- phase: "result",
4304
- item
4305
- });
4306
- this.recordNativeToolTranscriptCall(item);
4307
- this.recordNativeToolTranscriptResult(item);
4308
- this.emitToolResultSummary(item);
4309
- this.emitToolResultOutput(item);
4310
- this.emitAgentEvent({
4311
- stream: "codex_app_server.item",
4312
- data: {
4313
- phase: "completed",
4314
- itemId,
4315
- type: item?.type
4316
- }
4317
- });
4318
- }
4319
- handleTokenUsage(params) {
4320
- const tokenUsage = isJsonObject(params.tokenUsage) ? params.tokenUsage : void 0;
4321
- const current = (tokenUsage ? readFirstJsonObject(tokenUsage, CURRENT_TOKEN_USAGE_KEYS) : void 0) ?? readFirstJsonObject(params, CURRENT_TOKEN_USAGE_KEYS);
4322
- if (!current) return;
4323
- const usage = normalizeCodexTokenUsage(current);
4324
- if (usage) this.tokenUsage = usage;
4325
- }
4326
- handleGuardianReviewNotification(method, params) {
4327
- this.guardianReviewCount += 1;
4328
- const review = isJsonObject(params.review) ? params.review : void 0;
4329
- const action = isJsonObject(params.action) ? params.action : void 0;
4330
- this.emitAgentEvent({
4331
- stream: "codex_app_server.guardian",
4332
- data: {
4333
- method,
4334
- phase: method.endsWith("/started") ? "started" : "completed",
4335
- reviewId: readString$5(params, "reviewId"),
4336
- targetItemId: readNullableString$1(params, "targetItemId"),
4337
- decisionSource: readString$5(params, "decisionSource"),
4338
- status: review ? readString$5(review, "status") : void 0,
4339
- riskLevel: review ? readString$5(review, "riskLevel") : void 0,
4340
- userAuthorization: review ? readString$5(review, "userAuthorization") : void 0,
4341
- rationale: review ? readNullableString$1(review, "rationale") : void 0,
4342
- actionType: action ? readString$5(action, "type") : void 0
4343
- }
4344
- });
4345
- }
4346
- handleHookNotification(method, params) {
4347
- const run = isJsonObject(params.run) ? params.run : void 0;
4348
- if (!run) return;
4349
- const durationMs = readNumber$1(run, "durationMs");
4350
- const entries = readHookOutputEntries(run.entries);
4351
- const hookTurnId = readNullableString$1(params, "turnId");
4352
- this.emitAgentEvent({
4353
- stream: "codex_app_server.hook",
4354
- data: {
4355
- phase: method === "hook/started" ? "started" : "completed",
4356
- threadId: this.threadId,
4357
- turnId: hookTurnId === void 0 ? this.turnId : hookTurnId,
4358
- hookRunId: readString$5(run, "id"),
4359
- eventName: readString$5(run, "eventName"),
4360
- handlerType: readString$5(run, "handlerType"),
4361
- executionMode: readString$5(run, "executionMode"),
4362
- scope: readString$5(run, "scope"),
4363
- source: readString$5(run, "source"),
4364
- sourcePath: readString$5(run, "sourcePath"),
4365
- status: readString$5(run, "status"),
4366
- statusMessage: readNullableString$1(run, "statusMessage"),
4367
- ...durationMs !== void 0 ? { durationMs } : {},
4368
- ...entries.length > 0 ? { entries } : {}
4369
- }
4370
- });
4371
- }
4372
- async handleTurnCompleted(params) {
4373
- const turn = readTurn(params.turn);
4374
- if (!turn || turn.id !== this.turnId) return;
4375
- this.completedTurn = turn;
4376
- if (turn.status === "failed") {
4377
- this.promptError = formatCodexUsageLimitErrorMessage({
4378
- message: turn.error?.message,
4379
- codexErrorInfo: turn.error?.codexErrorInfo,
4380
- rateLimits: this.latestRateLimits ?? readRecentCodexRateLimits()
4381
- }) ?? turn.error?.message ?? "codex app-server turn failed";
4382
- this.promptErrorSource = "prompt";
4383
- }
4384
- const turnItems = turn.items ?? [];
4385
- for (let index = turnItems.length - 1; index >= 0; index -= 1) {
4386
- const item = turnItems[index];
4387
- if (!item || !this.isCurrentTurnSnapshotItem(item)) continue;
4388
- if (item?.type === "dynamicToolCall") break;
4389
- if (shouldClearTerminalPresentationForNativeItem(item)) {
4390
- this.clearTerminalPresentationForNativeItem(item);
4391
- break;
4392
- }
4393
- }
4394
- for (const item of turnItems) {
4395
- this.rememberAssistantPhase(item);
4396
- if (item.type === "agentMessage" && typeof item.text === "string") {
4397
- this.rememberAssistantItem(item.id);
4398
- this.assistantTextByItem.set(item.id, item.text);
4399
- }
4400
- this.recordNativeGeneratedMedia(item);
4401
- if (item.type === "plan" && typeof item.text === "string" && item.text) {
4402
- this.planTextByItem.set(item.id, item.text);
4403
- this.emitPlanUpdate({
4404
- explanation: void 0,
4405
- steps: splitPlanText(item.text)
4406
- });
4407
- }
4408
- this.recordToolMeta(item);
4409
- await this.emitSnapshotOnlyNativeToolProgress(item);
4410
- this.recordNativeToolTranscriptCall(item);
4411
- this.recordNativeToolTranscriptResult(item);
4412
- this.emitAfterToolCallObservation(item);
4413
- this.emitToolResultSummary(item);
4414
- this.emitToolResultOutput(item);
4415
- }
4416
- this.activeCompactionItemIds.clear();
4417
- await this.maybeEndReasoning();
4418
- }
4419
- async emitSnapshotOnlyNativeToolProgress(item) {
4420
- if (!shouldSynthesizeToolProgressForItem(item) || !this.isCurrentTurnSnapshotItem(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
4421
- if (!this.activeItemIds.has(item.id)) {
4422
- this.emitStandardItemEvent({
4423
- phase: "start",
4424
- item
4425
- });
4426
- await this.emitNormalizedToolItemEvent({
4427
- phase: "start",
4428
- item
4429
- });
4430
- }
4431
- this.activeItemIds.delete(item.id);
4432
- this.emitStandardItemEvent({
4433
- phase: "end",
4434
- item
4435
- });
4436
- await this.emitNormalizedToolItemEvent({
4437
- phase: "result",
4438
- item
4439
- });
4440
- this.completedItemIds.add(item.id);
4441
- }
4442
- isCurrentTurnSnapshotItem(item) {
4443
- const itemTurnId = readItemString(item, "turnId") ?? readItemString(item, "turn_id");
4444
- return itemTurnId === void 0 || itemTurnId === this.turnId;
4445
- }
4446
- handleOutputDelta(params, toolName) {
4447
- const itemId = readString$5(params, "itemId");
4448
- const delta = readString$5(params, "delta");
4449
- if (!itemId || !delta) return;
4450
- appendToolOutputDeltaText(this.toolResultOutputTextByItem, itemId, delta);
4451
- if (!this.shouldEmitToolOutput()) return;
4452
- if (this.transcriptToolProgressSuppressedIds.has(itemId) || !shouldEmitTranscriptToolProgress(toolName, this.toolTranscriptArgumentsById.get(itemId))) return;
4453
- const state = this.toolResultOutputDeltaState.get(itemId) ?? {
4454
- chars: 0,
4455
- messages: 0,
4456
- truncated: false
4457
- };
4458
- if (state.truncated) return;
4459
- const remainingChars = Math.max(0, TOOL_PROGRESS_OUTPUT_MAX_CHARS - state.chars);
4460
- const remainingMessages = Math.max(0, MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM - state.messages);
4461
- if (remainingChars === 0 || remainingMessages === 0) {
4462
- state.truncated = true;
4463
- this.toolResultOutputDeltaState.set(itemId, state);
4464
- this.emitToolResultMessage({
4465
- itemId,
4466
- text: formatToolOutput(toolName, void 0, "(output truncated)")
4467
- });
4468
- return;
4469
- }
4470
- const chunk = delta.length > remainingChars ? delta.slice(0, remainingChars) : delta;
4471
- state.chars += chunk.length;
4472
- state.messages += 1;
4473
- const reachedLimit = delta.length > remainingChars || state.chars >= TOOL_PROGRESS_OUTPUT_MAX_CHARS || state.messages >= MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM;
4474
- if (reachedLimit) state.truncated = true;
4475
- this.toolResultOutputDeltaState.set(itemId, state);
4476
- this.toolResultOutputStreamedItemIds.add(itemId);
4477
- this.emitToolResultMessage({
4478
- itemId,
4479
- text: formatToolOutput(toolName, void 0, reachedLimit ? `${chunk}\n...(truncated)...` : chunk)
4480
- });
4481
- }
4482
- async handleRawResponseItemCompleted(params) {
4483
- const item = isJsonObject(params.item) ? params.item : void 0;
4484
- if (!item) return;
4485
- const role = readString$5(item, "role");
4486
- const phase = readString$5(item, "phase");
4487
- const rawItemId = readString$5(item, "id");
4488
- const candidateWasSupersededBeforeRaw = this.latestTerminalAssistantCandidateSuperseded;
4489
- const pendingTerminalAssistantEchoItemId = this.pendingRawTerminalAssistantEchoItemId;
4490
- const isPendingTerminalAssistantEcho = role === "assistant" && phase !== "commentary" && pendingTerminalAssistantEchoItemId !== void 0 && (rawItemId === void 0 || rawItemId === pendingTerminalAssistantEchoItemId);
4491
- if (pendingTerminalAssistantEchoItemId !== void 0 && !isPendingTerminalAssistantEcho) this.pendingRawTerminalAssistantEchoItemId = void 0;
4492
- if (!isPendingTerminalAssistantEcho) {
4493
- this.latestCompletedItemId = void 0;
4494
- this.markTerminalAssistantCandidateSupersededBy(rawItemId);
4495
- }
4496
- await this.recordRawGeneratedImageMedia(item);
4497
- if (role !== "assistant") return;
4498
- if (phase === "commentary" && this.pendingRawCommentaryEchoes > 0) {
4499
- this.pendingRawCommentaryEchoes -= 1;
4500
- return;
4501
- }
4502
- const text = extractRawAssistantText(item);
4503
- if (isPendingTerminalAssistantEcho) {
4504
- const typedItemId = pendingTerminalAssistantEchoItemId;
4505
- this.pendingRawTerminalAssistantEchoItemId = void 0;
4506
- if (this.assistantTextByItem.get(typedItemId)?.trim() || !text) return;
4507
- this.rememberAssistantItem(typedItemId);
4508
- this.assistantTextByItem.set(typedItemId, text);
4509
- return;
4510
- }
4511
- if (!text) return;
4512
- const itemId = rawItemId ?? `raw-assistant-${this.assistantItemOrder.length + 1}`;
4513
- const isIdlessTerminalAssistantAfterCompletedWork = candidateWasSupersededBeforeRaw && rawItemId === void 0 && pendingTerminalAssistantEchoItemId === void 0 && this.activeItemIds.size === 0;
4514
- if (phase !== "commentary" && candidateWasSupersededBeforeRaw && itemId !== this.streamedPartialAssistantItemId && !isIdlessTerminalAssistantAfterCompletedWork) return;
4515
- if (phase) this.assistantPhaseByItem.set(itemId, phase);
4516
- this.rememberAssistantItem(itemId);
4517
- this.assistantTextByItem.set(itemId, text);
4518
- if (phase === "commentary") this.emitCommentaryProgress({
4519
- itemId,
4520
- text
4521
- });
4522
- else this.markLatestTerminalAssistantCandidate(itemId, { canReleaseAfterToolHandoff: isIdlessTerminalAssistantAfterCompletedWork });
4523
- }
4524
- markLatestTerminalAssistantCandidate(itemId, options) {
4525
- this.latestTerminalAssistantCandidateItemId = itemId;
4526
- this.latestTerminalAssistantCandidateSuperseded = false;
4527
- this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff = options?.canReleaseAfterToolHandoff === true;
4528
- this.terminalAssistantCandidateEarlierActiveItemIds = new Set(this.activeItemIds);
4529
- }
4530
- markTerminalAssistantCandidateSupersededBy(itemId, options) {
4531
- if (!this.latestTerminalAssistantCandidateItemId) return;
4532
- if (itemId && this.terminalAssistantCandidateEarlierActiveItemIds.has(itemId)) {
4533
- if (!options?.preserveEarlierActiveItem) this.terminalAssistantCandidateEarlierActiveItemIds.delete(itemId);
4534
- return;
4535
- }
4536
- this.latestTerminalAssistantCandidateSuperseded = true;
4537
- this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff = false;
4538
- this.terminalAssistantCandidateEarlierActiveItemIds.clear();
4539
- }
4540
- recordNativeGeneratedMedia(item) {
4541
- if (item?.type !== "imageGeneration") return;
4542
- const savedPath = readItemString(item, "savedPath")?.trim();
4543
- if (savedPath) this.recordNativeGeneratedMediaUrl({
4544
- itemId: item.id,
4545
- mediaUrl: savedPath
4546
- });
4547
- }
4548
- async recordRawGeneratedImageMedia(item) {
4549
- if (readString$5(item, "type") !== "image_generation_call") return;
4550
- const result = readString$5(item, "result");
4551
- if (!result) return;
4552
- const itemId = readString$5(item, "id") ?? `raw-image-${this.nativeGeneratedMediaItemIds.size}`;
4553
- this.nativeGeneratedMediaItemIds.add(itemId);
4554
- const maxBytes = resolveGeneratedImageMaxBytes(this.params.config);
4555
- const estimatedDecodedBytes = estimateBase64DecodedBytes(result);
4556
- if (estimatedDecodedBytes !== void 0 && estimatedDecodedBytes > maxBytes) {
4557
- embeddedAgentLog.warn("codex app-server raw image generation result exceeds media limit", {
4558
- itemId,
4559
- estimatedDecodedBytes,
4560
- maxBytes
4561
- });
4562
- return;
4563
- }
4564
- const asset = generatedImageAssetFromBase64({
4565
- base64: result,
4566
- index: this.nativeGeneratedMediaItemIds.size,
4567
- revisedPrompt: readString$5(item, "revised_prompt") ?? readString$5(item, "revisedPrompt"),
4568
- fileNamePrefix: "codex-image-generation",
4569
- sniffMimeType: true
4570
- });
4571
- if (!asset) return;
4572
- try {
4573
- const saved = await saveMediaBuffer(asset.buffer, asset.mimeType, GENERATED_IMAGE_MEDIA_SUBDIR, maxBytes, asset.fileName);
4574
- this.recordNativeGeneratedMediaUrl({
4575
- itemId,
4576
- mediaUrl: saved.path,
4577
- replaceExisting: true
4578
- });
4579
- } catch (error) {
4580
- embeddedAgentLog.warn("codex app-server raw image generation result save failed", {
4581
- itemId,
4582
- error
4583
- });
4584
- }
4585
- }
4586
- recordNativeGeneratedMediaUrl(params) {
4587
- if (this.nativeGeneratedMediaUrlsByItemId.has(params.itemId) && params.replaceExisting !== true) {
4588
- this.nativeGeneratedMediaItemIds.add(params.itemId);
4589
- return;
4590
- }
4591
- this.nativeGeneratedMediaUrlsByItemId.set(params.itemId, params.mediaUrl);
4592
- this.nativeGeneratedMediaItemIds.add(params.itemId);
4593
- }
4594
- buildToolMediaUrls(toolTelemetry) {
4595
- const mediaUrls = new Set(toolTelemetry.toolMediaUrls?.map((url) => url.trim()).filter(Boolean) ?? []);
4596
- if ((toolTelemetry.messagingToolSentMediaUrls?.length ?? 0) === 0) for (const mediaUrl of this.nativeGeneratedMediaUrlsByItemId.values()) mediaUrls.add(mediaUrl);
4597
- return mediaUrls.size > 0 ? [...mediaUrls] : toolTelemetry.toolMediaUrls;
4598
- }
4599
- async maybeEndReasoning() {
4600
- if (!this.reasoningStarted || this.reasoningEnded) return;
4601
- this.reasoningEnded = true;
4602
- await this.params.onReasoningEnd?.();
4603
- }
4604
- emitPlanUpdate(params) {
4605
- if (!params.explanation && (!params.steps || params.steps.length === 0)) return;
4606
- this.emitAgentEvent({
4607
- stream: "plan",
4608
- data: {
4609
- phase: "update",
4610
- title: "Plan updated",
4611
- source: "codex-app-server",
4612
- ...params.explanation ? { explanation: params.explanation } : {},
4613
- ...params.steps && params.steps.length > 0 ? { steps: params.steps } : {}
4614
- }
4615
- });
4616
- }
4617
- rememberAssistantPhase(item) {
4618
- if (item?.type !== "agentMessage") return;
4619
- const phase = readItemString(item, "phase");
4620
- if (phase) this.assistantPhaseByItem.set(item.id, phase);
4621
- }
4622
- isCommentaryAssistantItem(itemId) {
4623
- return this.assistantPhaseByItem.get(itemId) === "commentary";
4624
- }
4625
- shouldStreamAssistantPartial(itemId) {
4626
- return this.assistantPhaseByItem.get(itemId) === "final_answer";
4627
- }
4628
- emitCommentaryProgress(params) {
4629
- const progressText = params.text.replace(/\s+/g, " ").trim();
4630
- if (!progressText || this.lastCommentaryProgressTextByItem.get(params.itemId) === progressText) return;
4631
- this.lastCommentaryProgressTextByItem.set(params.itemId, progressText);
4632
- this.emitAgentEvent({
4633
- stream: "item",
4634
- data: {
4635
- itemId: params.itemId,
4636
- kind: "preamble",
4637
- title: "Preamble",
4638
- phase: "update",
4639
- progressText,
4640
- source: "codex-app-server"
4641
- }
4642
- });
4643
- }
4644
- emitStandardItemEvent(params) {
4645
- const { item } = params;
4646
- if (!item) return;
4647
- const kind = itemKind(item);
4648
- if (!kind) return;
4649
- const meta = itemMeta(item, this.toolProgressDetailMode());
4650
- const suppressChannelProgress = shouldSuppressChannelProgressForItem(item);
4651
- this.emitAgentEvent({
4652
- stream: "item",
4653
- data: {
4654
- itemId: item.id,
4655
- phase: params.phase,
4656
- kind,
4657
- title: itemTitle(item),
4658
- status: params.phase === "start" ? "running" : itemStatus(item),
4659
- ...itemName(item) ? { name: itemName(item) } : {},
4660
- ...meta ? { meta } : {},
4661
- ...suppressChannelProgress ? { suppressChannelProgress: true } : {}
4662
- }
4663
- });
4664
- }
4665
- async emitNormalizedToolItemEvent(params) {
4666
- const { item } = params;
4667
- if (!item || !shouldSynthesizeToolProgressForItem(item)) return;
4668
- const name = itemName(item);
4669
- if (!name) return;
4670
- const status = params.phase === "result" ? itemStatus(item) : "running";
4671
- const args = itemToolArgs(item);
4672
- const meta = itemMeta(item, this.toolProgressDetailMode());
4673
- this.recordToolTrajectoryEvent({
4674
- phase: params.phase,
4675
- item,
4676
- name,
4677
- args,
4678
- status
4679
- });
4680
- this.emitDiagnosticToolExecutionEvent({
4681
- phase: params.phase,
4682
- item,
4683
- name,
4684
- status
4685
- });
4686
- if (params.phase === "result") this.recordNativeToolError({
4687
- item,
4688
- name,
4689
- meta,
4690
- status
4691
- });
4692
- if (!shouldEmitTranscriptToolProgress(name, args)) {
4693
- if (params.phase === "result") {
4694
- this.emitAfterToolCallObservation(item);
4695
- await this.options.onNativeToolResultRecorded?.();
4696
- }
4697
- return;
4698
- }
4699
- this.emitAgentEvent({
4700
- stream: "tool",
4701
- data: {
4702
- phase: params.phase,
4703
- name,
4704
- itemId: item.id,
4705
- toolCallId: item.id,
4706
- ...meta ? { meta } : {},
4707
- ...params.phase === "start" && args ? { args } : {},
4708
- ...params.phase === "result" ? {
4709
- status,
4710
- isError: isNonSuccessItemStatus(status),
4711
- ...itemToolResult(item)
4712
- } : {}
4713
- }
4714
- });
4715
- if (params.phase === "result") {
4716
- this.emitAfterToolCallObservation(item);
4717
- await this.options.onNativeToolResultRecorded?.();
4718
- }
4719
- }
4720
- clearTerminalPresentationForNativeItem(item) {
4721
- if (!item || this.terminalPresentationClearedItemIds.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
4722
- const toolCallOrdinal = this.nativeToolOutcomeOrdinals.get(item.id);
4723
- this.terminalPresentationClearedItemIds.add(item.id);
4724
- this.params.onToolOutcome?.({
4725
- toolName: itemName(item) ?? item.type,
4726
- argsHash: "",
4727
- resultHash: "",
4728
- ...toolCallOrdinal !== void 0 ? { toolCallOrdinal } : {},
4729
- terminalPresentation: void 0,
4730
- presentationOnly: true
4731
- });
4732
- }
4733
- recordNativeToolError(params) {
4734
- if (!isNonSuccessItemStatus(params.status)) {
4735
- if (!this.lastNativeToolError) return;
4736
- if (!this.lastNativeToolError.mutatingAction) {
4737
- this.lastNativeToolError = void 0;
4738
- return;
4739
- }
4740
- const actionFingerprint = nativeToolActionFingerprint(params.item);
4741
- if (this.lastNativeToolError.actionFingerprint && actionFingerprint && this.lastNativeToolError.actionFingerprint === actionFingerprint) this.lastNativeToolError = void 0;
4742
- return;
4743
- }
4744
- const error = itemToolError(params.item, params.status, this.toolResultOutputTextByItem);
4745
- const actionFingerprint = nativeToolActionFingerprint(params.item);
4746
- this.lastNativeToolError = {
4747
- toolName: params.name,
4748
- ...params.meta ? { meta: params.meta } : {},
4749
- ...error ? { error } : {},
4750
- ...isMutatingNativeToolItem(params.item) ? { mutatingAction: true } : {},
4751
- ...actionFingerprint ? { actionFingerprint } : {}
4752
- };
4753
- }
4754
- recordToolTrajectoryEvent(params) {
4755
- if (params.phase === "start") {
4756
- this.toolTrajectoryCallIds.add(params.item.id);
4757
- this.toolTrajectoryNamesById.set(params.item.id, params.name);
4758
- this.toolTrajectoryItemsById.set(params.item.id, params.item);
4759
- this.options.trajectoryRecorder?.recordEvent("tool.call", {
4760
- threadId: this.threadId,
4761
- turnId: this.turnId,
4762
- itemId: params.item.id,
4763
- toolCallId: params.item.id,
4764
- name: params.name,
4765
- arguments: params.args
4766
- });
4767
- return;
4768
- }
4769
- this.toolTrajectoryResultIds.add(params.item.id);
4770
- const toolResult = itemToolResult(params.item).result;
4771
- const output = itemOutputText(params.item, this.toolResultOutputTextByItem);
4772
- this.options.trajectoryRecorder?.recordEvent("tool.result", {
4773
- threadId: this.threadId,
4774
- turnId: this.turnId,
4775
- itemId: params.item.id,
4776
- toolCallId: params.item.id,
4777
- name: params.name,
4778
- status: params.status,
4779
- isError: isNonSuccessItemStatus(params.status),
4780
- ...toolResult ? { result: toolResult } : {},
4781
- ...output ? { output } : {}
4782
- });
4783
- }
4784
- emitDiagnosticToolExecutionEvent(params) {
4785
- const base = {
4786
- runId: this.params.runId,
4787
- sessionId: this.params.sessionId,
4788
- sessionKey: this.params.sessionKey,
4789
- toolName: params.name,
4790
- toolCallId: params.item.id
4791
- };
4792
- if (params.phase === "start") {
4793
- this.diagnosticToolStartedAtByItem.set(params.item.id, Date.now());
4794
- emitTrustedDiagnosticEvent({
4795
- type: "tool.execution.started",
4796
- ...base
4797
- });
4798
- return;
4799
- }
4800
- const startedAt = this.diagnosticToolStartedAtByItem.get(params.item.id);
4801
- this.diagnosticToolStartedAtByItem.delete(params.item.id);
4802
- const durationMs = (typeof params.item.durationMs === "number" ? params.item.durationMs : void 0) ?? (startedAt === void 0 ? 0 : Math.max(0, Date.now() - startedAt));
4803
- const terminalEvent = params.status === "blocked" ? {
4804
- type: "tool.execution.blocked",
4805
- reason: "codex_native_tool_blocked",
4806
- deniedReason: "codex_native_tool_blocked"
4807
- } : params.status === "failed" ? {
4808
- type: "tool.execution.error",
4809
- durationMs,
4810
- errorCategory: "codex_native_tool_error"
4811
- } : {
4812
- type: "tool.execution.completed",
4813
- durationMs
4814
- };
4815
- emitTrustedDiagnosticEvent({
4816
- ...base,
4817
- ...terminalEvent
4818
- });
4819
- }
4820
- emitAfterToolCallObservation(item) {
4821
- if (!this.shouldEmitAfterToolCallObservation(item)) return;
4822
- const name = itemName(item);
4823
- if (!name) return;
4824
- const status = itemStatus(item);
4825
- if (status === "running") return;
4826
- this.afterToolCallObservedItemIds.add(item.id);
4827
- const result = itemToolResult(item).result;
4828
- const error = itemToolError(item, status, this.toolResultOutputTextByItem);
4829
- const startedAt = resolveStartedAtFromDurationMs(item.durationMs);
4830
- const hookParams = {
4831
- toolName: name,
4832
- toolCallId: item.id,
4833
- runId: this.params.runId,
4834
- agentId: this.params.agentId,
4835
- sessionId: this.params.sessionId,
4836
- sessionKey: this.params.sessionKey,
4837
- startArgs: itemToolArgs(item) ?? {},
4838
- ...result !== void 0 ? { result } : {},
4839
- ...error ? { error } : {},
4840
- ...startedAt !== void 0 ? { startedAt } : {}
4841
- };
4842
- setImmediate(() => {
4843
- runAgentHarnessAfterToolCallHook(hookParams);
4844
- });
4845
- }
4846
- shouldEmitAfterToolCallObservation(item) {
4847
- if (!shouldSynthesizeToolProgressForItem(item) || this.afterToolCallObservedItemIds.has(item.id)) return false;
4848
- if (this.options.nativePostToolUseRelayEnabled && isNativePostToolUseRelayItem(item)) return false;
4849
- return true;
4850
- }
4851
- emitToolResultSummary(item) {
4852
- if (!item || !this.params.onToolResult || !this.shouldEmitToolResult()) return;
4853
- const itemId = item.id;
4854
- if (this.toolResultSummaryItemIds.has(itemId)) return;
4855
- const toolName = itemName(item);
4856
- if (!toolName) return;
4857
- if (!shouldEmitTranscriptToolProgress(toolName, itemToolArgs(item))) return;
4858
- this.toolResultSummaryItemIds.add(itemId);
4859
- const meta = itemMeta(item, this.toolProgressDetailMode());
4860
- this.emitToolResultMessage({
4861
- itemId,
4862
- text: formatToolSummary(toolName, meta)
4863
- });
4864
- }
4865
- emitToolResultOutput(item) {
4866
- if (!item || !this.params.onToolResult || !this.shouldEmitToolOutput()) return;
4867
- const itemId = item.id;
4868
- if (this.toolResultOutputItemIds.has(itemId)) return;
4869
- if (this.toolResultOutputStreamedItemIds.has(itemId)) return;
4870
- const toolName = itemName(item);
4871
- const output = itemOutputText(item, this.toolResultOutputTextByItem);
4872
- if (!toolName || !output) return;
4873
- if (!shouldEmitTranscriptToolProgress(toolName, itemToolArgs(item))) return;
4874
- this.emitToolResultMessage({
4875
- itemId,
4876
- text: formatToolOutput(toolName, itemMeta(item, this.toolProgressDetailMode()), output),
4877
- finalOutput: true,
4878
- isError: isNonSuccessItemStatus(itemStatus(item))
4879
- });
4880
- }
4881
- emitToolResultMessage(params) {
4882
- const text = params.text.trim();
4883
- if (!text) return;
4884
- this.toolProgressTexts.add(text);
4885
- if (params.finalOutput) this.toolResultOutputItemIds.add(params.itemId);
4886
- try {
4887
- Promise.resolve(this.params.onToolResult?.({
4888
- text,
4889
- ...params.isError === true ? { isError: true } : {}
4890
- })).catch(() => {});
4891
- } catch {}
4892
- }
4893
- shouldEmitToolResult() {
4894
- return typeof this.params.shouldEmitToolResult === "function" ? this.params.shouldEmitToolResult() : this.params.verboseLevel === "on" || this.params.verboseLevel === "full";
4895
- }
4896
- shouldEmitToolOutput() {
4897
- return typeof this.params.shouldEmitToolOutput === "function" ? this.params.shouldEmitToolOutput() : this.params.verboseLevel === "full";
4898
- }
4899
- toolProgressDetailMode() {
4900
- return resolveCodexToolProgressDetailMode(this.params.toolProgressDetail);
4901
- }
4902
- recordToolMeta(item) {
4903
- if (!item) return;
4904
- if (isSideEffectingNativeToolItem(item)) this.sideEffectingToolItemIds.add(item.id);
4905
- else this.sideEffectingToolItemIds.delete(item.id);
4906
- const toolName = itemName(item);
4907
- if (!toolName) return;
4908
- const meta = itemMeta(item, this.toolProgressDetailMode());
4909
- const existing = this.toolMetas.get(item.id);
4910
- this.toolMetas.set(item.id, {
4911
- toolName,
4912
- ...meta ? { meta } : {},
4913
- ...existing?.asyncStarted ? { asyncStarted: true } : {}
4914
- });
4915
- }
4916
- recordNativeToolTranscriptCall(item) {
4917
- if (!item || !shouldRecordNativeToolTranscript(item)) return;
4918
- const name = itemName(item);
4919
- if (!name) return;
4920
- this.recordToolTranscriptCall({
4921
- id: item.id,
4922
- name,
4923
- arguments: itemToolArgs(item)
4924
- });
4925
- }
4926
- recordNativeToolTranscriptResult(item) {
4927
- if (!item || !shouldRecordNativeToolTranscript(item)) return;
4928
- const name = itemName(item);
4929
- if (!name) return;
4930
- this.recordToolTranscriptResult({
4931
- id: item.id,
4932
- name,
4933
- text: itemTranscriptResultText(item, this.toolResultOutputTextByItem),
4934
- isError: isNonSuccessItemStatus(itemStatus(item))
4935
- });
4936
- }
4937
- recordToolTranscriptCall(params) {
4938
- if (!params.id || !params.name || this.toolTranscriptCallIds.has(params.id)) return;
4939
- this.toolTranscriptCallIds.add(params.id);
4940
- this.toolTranscriptNamesById.set(params.id, params.name);
4941
- this.toolTranscriptArgumentsById.set(params.id, params.arguments);
4942
- if (!shouldEmitTranscriptToolProgress(params.name, params.arguments)) this.transcriptToolProgressSuppressedIds.add(params.id);
4943
- else this.transcriptToolProgressSuppressedIds.delete(params.id);
4944
- this.emitTranscriptToolCallProgress(params);
4945
- this.toolTranscriptMessages.push(attachCodexMirrorIdentity(this.createToolCallMessage(params), `${this.turnId}:tool:${params.id}:call`));
4946
- }
4947
- recordToolTranscriptResult(params) {
4948
- if (!params.id || !params.name || this.toolTranscriptResultIds.has(params.id)) return;
4949
- this.toolTranscriptResultIds.add(params.id);
4950
- this.emitTranscriptToolResultProgress(params);
4951
- this.toolTranscriptMessages.push(attachCodexMirrorIdentity(this.createToolResultMessage(params), `${this.turnId}:tool:${params.id}:result`));
4952
- }
4953
- synthesizeMissingToolResults(params) {
4954
- if (!params.synthesize) return;
4955
- const missingTranscriptIds = [...this.toolTranscriptCallIds].filter((id) => !this.toolTranscriptResultIds.has(id));
4956
- const missingTrajectoryIds = [...this.toolTrajectoryCallIds].filter((id) => !this.toolTrajectoryResultIds.has(id));
4957
- if (missingTranscriptIds.length === 0 && missingTrajectoryIds.length === 0) return;
4958
- for (const id of missingTranscriptIds) {
4959
- const name = this.toolTranscriptNamesById.get(id) ?? this.toolTrajectoryNamesById.get(id);
4960
- if (!name) continue;
4961
- this.recordToolTranscriptResult({
4962
- id,
4963
- name,
4964
- text: formatMissingToolResultError({
4965
- id,
4966
- name
4967
- }),
4968
- isError: true
4969
- });
4970
- }
4971
- for (const id of missingTrajectoryIds) {
4972
- const name = this.toolTrajectoryNamesById.get(id) ?? this.toolTranscriptNamesById.get(id);
4973
- if (!name) continue;
4974
- this.toolTrajectoryResultIds.add(id);
4975
- const text = formatMissingToolResultError({
4976
- id,
4977
- name
4978
- });
4979
- this.options.trajectoryRecorder?.recordEvent("tool.result", {
4980
- threadId: this.threadId,
4981
- turnId: this.turnId,
4982
- itemId: id,
4983
- toolCallId: id,
4984
- name,
4985
- status: "failed",
4986
- isError: true,
4987
- result: {
4988
- status: "failed",
4989
- reason: "missing_tool_result"
4990
- },
4991
- output: text
4992
- });
4993
- }
4994
- if (!params.recordPromptError) {
4995
- const firstMissingId = missingTranscriptIds.find((id) => {
4996
- const name = this.toolTranscriptNamesById.get(id) ?? this.toolTrajectoryNamesById.get(id);
4997
- return Boolean(name);
4998
- }) ?? missingTrajectoryIds.find((id) => {
4999
- const name = this.toolTrajectoryNamesById.get(id) ?? this.toolTranscriptNamesById.get(id);
5000
- return Boolean(name);
5001
- });
5002
- if (firstMissingId) {
5003
- const name = this.toolTranscriptNamesById.get(firstMissingId) ?? this.toolTrajectoryNamesById.get(firstMissingId);
5004
- if (name) {
5005
- const item = this.toolTrajectoryItemsById.get(firstMissingId);
5006
- const meta = item ? itemMeta(item, this.toolProgressDetailMode()) : this.toolMetas.get(firstMissingId)?.meta;
5007
- const actionFingerprint = item ? nativeToolActionFingerprint(item) : void 0;
5008
- this.lastNativeToolError = {
5009
- toolName: name,
5010
- ...meta ? { meta } : {},
5011
- error: formatMissingToolResultError({
5012
- id: firstMissingId,
5013
- name
5014
- }),
5015
- ...item && isMutatingNativeToolItem(item) ? { mutatingAction: true } : {},
5016
- ...actionFingerprint ? { actionFingerprint } : {}
5017
- };
5018
- }
5019
- }
5020
- return;
5021
- }
5022
- const missingCount = new Set([...missingTranscriptIds, ...missingTrajectoryIds]).size;
5023
- this.synthesizedMissingToolResultError = missingCount === 1 ? MISSING_TOOL_RESULT_ERROR : `${MISSING_TOOL_RESULT_ERROR} missingToolResultCount=${missingCount}`;
5024
- this.promptErrorSource = this.promptErrorSource ?? "prompt";
5025
- }
5026
- emitTranscriptToolCallProgress(params) {
5027
- if (!shouldEmitTranscriptToolProgress(params.name, params.arguments)) return;
5028
- this.transcriptToolProgressCallIds.add(params.id);
5029
- const args = normalizeToolTranscriptArguments(params.arguments);
5030
- const meta = inferToolMetaFromArgs(params.name, args, { detailMode: this.toolProgressDetailMode() });
5031
- if (!this.params.onToolResult || !this.shouldEmitToolResult() || this.toolResultSummaryItemIds.has(params.id) || this.toolResultOutputStreamedItemIds.has(params.id)) return;
5032
- this.toolResultSummaryItemIds.add(params.id);
5033
- this.emitToolResultMessage({
5034
- itemId: params.id,
5035
- text: formatToolSummary(params.name, meta)
5036
- });
5037
- }
5038
- emitTranscriptToolResultProgress(params) {
5039
- if (this.transcriptToolProgressSuppressedIds.has(params.id) || !shouldEmitTranscriptToolProgress(params.name, this.toolTranscriptArgumentsById.get(params.id))) return;
5040
- if (!this.transcriptToolProgressCallIds.has(params.id)) this.emitTranscriptToolCallProgress({
5041
- id: params.id,
5042
- name: params.name,
5043
- arguments: {}
5044
- });
5045
- if (!this.params.onToolResult || !this.shouldEmitToolOutput() || this.toolResultOutputItemIds.has(params.id) || this.toolResultOutputStreamedItemIds.has(params.id)) return;
5046
- const text = params.text?.trim();
5047
- if (!text) return;
5048
- this.emitToolResultMessage({
5049
- itemId: params.id,
5050
- text: formatToolOutput(params.name, void 0, text),
5051
- finalOutput: true,
5052
- isError: params.isError
5053
- });
5054
- }
5055
- formatCodexErrorMessage(params) {
5056
- const error = isJsonObject(params.error) ? params.error : void 0;
5057
- return formatCodexUsageLimitErrorMessage({
5058
- message: error ? readString$5(error, "message") : void 0,
5059
- codexErrorInfo: error?.codexErrorInfo,
5060
- rateLimits: this.latestRateLimits ?? readRecentCodexRateLimits()
5061
- }) ?? readCodexErrorNotificationMessage(params);
5062
- }
5063
- emitAgentEvent(event) {
5064
- try {
5065
- emitAgentEvent({
5066
- runId: this.params.runId,
5067
- stream: event.stream,
5068
- data: event.data,
5069
- ...this.params.sessionKey ? { sessionKey: this.params.sessionKey } : {}
5070
- });
5071
- } catch (error) {
5072
- embeddedAgentLog.debug("codex app-server global agent event emit failed", { error });
5073
- }
5074
- try {
5075
- const maybePromise = this.params.onAgentEvent?.(event);
5076
- Promise.resolve(maybePromise).catch((error) => {
5077
- embeddedAgentLog.debug("codex app-server agent event handler rejected", { error });
5078
- });
5079
- } catch (error) {
5080
- embeddedAgentLog.debug("codex app-server agent event handler threw", { error });
5081
- }
5082
- }
5083
- collectAssistantTexts() {
5084
- const finalText = this.resolveFinalAssistantText();
5085
- return finalText ? [finalText] : [];
5086
- }
5087
- hasAssistantItemTextForSynthesis() {
5088
- for (let i = this.assistantItemOrder.length - 1; i >= 0; i -= 1) {
5089
- const itemId = this.assistantItemOrder[i];
5090
- if (!itemId) continue;
5091
- if (this.assistantPhaseByItem.get(itemId) === "commentary") continue;
5092
- const text = this.assistantTextByItem.get(itemId);
5093
- if (text && text.length > 0) return true;
5094
- }
5095
- return false;
5096
- }
5097
- resolveFinalAssistantText() {
5098
- return this.resolveFinalAssistantTextItem()?.text;
5099
- }
5100
- resolveFinalAssistantTextItem() {
5101
- for (let i = this.assistantItemOrder.length - 1; i >= 0; i -= 1) {
5102
- const itemId = this.assistantItemOrder[i];
5103
- if (!itemId) continue;
5104
- const text = this.assistantTextByItem.get(itemId)?.trim();
5105
- if (this.assistantPhaseByItem.get(itemId) === "commentary") continue;
5106
- if (text && !this.toolProgressTexts.has(text)) return {
5107
- itemId,
5108
- text
5109
- };
5110
- }
5111
- }
5112
- rememberAssistantItem(itemId) {
5113
- if (!itemId || this.assistantItemOrder.includes(itemId)) return;
5114
- this.assistantItemOrder.push(itemId);
5115
- }
5116
- createCurrentAttemptAssistantMessage() {
5117
- for (let i = this.assistantItemOrder.length - 1; i >= 0; i -= 1) {
5118
- const itemId = this.assistantItemOrder[i];
5119
- if (!itemId || this.isCommentaryAssistantItem(itemId) || !this.assistantTextByItem.has(itemId)) continue;
5120
- const text = this.assistantTextByItem.get(itemId) ?? "";
5121
- const normalizedText = text.trim();
5122
- if (normalizedText && this.toolProgressTexts.has(normalizedText)) continue;
5123
- return this.createAssistantMessage(text);
5124
- }
5125
- }
5126
- async readMirroredSessionMessages() {
5127
- return await readCodexMirroredSessionHistoryMessages({
5128
- agentId: this.params.agentId,
5129
- sessionFile: this.params.sessionFile,
5130
- sessionId: this.params.sessionId,
5131
- sessionKey: this.params.sessionKey
5132
- }) ?? [];
5133
- }
5134
- createAssistantMessage(text) {
5135
- const attribution = resolveCodexLocalRuntimeAttribution(this.params);
5136
- const usage = this.tokenUsage ? {
5137
- input: this.tokenUsage.input ?? 0,
5138
- output: this.tokenUsage.output ?? 0,
5139
- cacheRead: this.tokenUsage.cacheRead ?? 0,
5140
- cacheWrite: this.tokenUsage.cacheWrite ?? 0,
5141
- totalTokens: this.tokenUsage.total ?? (this.tokenUsage.input ?? 0) + (this.tokenUsage.output ?? 0) + (this.tokenUsage.cacheRead ?? 0) + (this.tokenUsage.cacheWrite ?? 0),
5142
- cost: ZERO_USAGE.cost
5143
- } : ZERO_USAGE;
5144
- return {
5145
- role: "assistant",
5146
- content: [{
5147
- type: "text",
5148
- text
5149
- }],
5150
- api: attribution.api ?? "openai-chatgpt-responses",
5151
- provider: attribution.provider,
5152
- model: this.params.modelId,
5153
- usage,
5154
- stopReason: this.aborted ? "aborted" : this.promptError ? "error" : "stop",
5155
- errorMessage: this.promptError ? formatErrorMessage(this.promptError) : void 0,
5156
- timestamp: Date.now()
5157
- };
5158
- }
5159
- createAssistantMirrorMessage(title, text) {
5160
- const attribution = resolveCodexLocalRuntimeAttribution(this.params);
5161
- return {
5162
- role: "assistant",
5163
- content: [{
5164
- type: "text",
5165
- text: `${title}:\n${text}`
5166
- }],
5167
- api: attribution.api ?? "openai-chatgpt-responses",
5168
- provider: attribution.provider,
5169
- model: this.params.modelId,
5170
- usage: ZERO_USAGE,
5171
- stopReason: "stop",
5172
- timestamp: Date.now()
5173
- };
5174
- }
5175
- createToolCallMessage(params) {
5176
- const args = normalizeToolTranscriptArguments(params.arguments);
5177
- const attribution = resolveCodexLocalRuntimeAttribution(this.params);
5178
- return {
5179
- role: "assistant",
5180
- content: [{
5181
- type: "toolCall",
5182
- id: params.id,
5183
- name: params.name,
5184
- arguments: args,
5185
- input: args
5186
- }],
5187
- api: attribution.api ?? "openai-chatgpt-responses",
5188
- provider: attribution.provider,
5189
- model: this.params.modelId,
5190
- usage: ZERO_USAGE,
5191
- stopReason: "toolUse",
5192
- timestamp: Date.now()
5193
- };
5194
- }
5195
- createToolResultMessage(params) {
5196
- const text = truncateToolTranscriptText(params.text?.trim() || toolResultStatusText(params));
5197
- return {
5198
- role: "toolResult",
5199
- toolCallId: params.id,
5200
- toolName: params.name,
5201
- isError: params.isError,
5202
- content: [{
5203
- type: "toolResult",
5204
- id: params.id,
5205
- name: params.name,
5206
- toolName: params.name,
5207
- toolCallId: params.id,
5208
- toolUseId: params.id,
5209
- tool_use_id: params.id,
5210
- content: text,
5211
- text
5212
- }],
5213
- timestamp: Date.now()
5214
- };
5215
- }
5216
- isNotificationForTurn(params) {
5217
- const threadId = readCodexNotificationThreadId(params);
5218
- const turnId = readNotificationTurnId(params);
5219
- return threadId === this.threadId && turnId === this.turnId;
5220
- }
5221
- isHookNotificationForCurrentThread(params) {
5222
- const threadId = readString$5(params, "threadId");
5223
- const turnId = params.turnId;
5224
- return threadId === this.threadId && (turnId === this.turnId || turnId === null);
5225
- }
5226
- };
5227
- function isHookNotificationMethod(method) {
5228
- return method === "hook/started" || method === "hook/completed";
5229
- }
5230
- function readNotificationTurnId(record) {
5231
- return readCodexNotificationTurnId(record);
5232
- }
5233
- function readString$5(record, key) {
5234
- const value = record[key];
5235
- return typeof value === "string" ? value : void 0;
5236
- }
5237
- function estimateBase64DecodedBytes(base64) {
5238
- let nonWhitespaceLength = 0;
5239
- let previousCode = -1;
5240
- let lastCode = -1;
5241
- for (let i = 0; i < base64.length; i += 1) {
5242
- const code = base64.charCodeAt(i);
5243
- if (isBase64WhitespaceCode(code)) continue;
5244
- nonWhitespaceLength += 1;
5245
- previousCode = lastCode;
5246
- lastCode = code;
5247
- }
5248
- if (nonWhitespaceLength === 0) return;
5249
- const equalsCode = "=".charCodeAt(0);
5250
- const padding = lastCode === equalsCode ? previousCode === equalsCode ? 2 : 1 : 0;
5251
- return Math.max(0, Math.floor(nonWhitespaceLength * 3 / 4) - padding);
5252
- }
5253
- function isBase64WhitespaceCode(code) {
5254
- return code === 32 || code === 9 || code === 10 || code === 13;
5255
- }
5256
- function resolveGeneratedImageMaxBytes(config) {
5257
- const configured = config?.agents?.defaults?.mediaMaxMb;
5258
- if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return Math.floor(configured * BYTES_PER_MB);
5259
- return DEFAULT_GENERATED_IMAGE_MAX_BYTES;
5260
- }
5261
- function normalizeNonEmptyString(value) {
5262
- if (typeof value !== "string") return;
5263
- return value.trim() || void 0;
5264
- }
5265
- function readNonEmptyString(record, key) {
5266
- return normalizeNonEmptyString(record[key]);
5267
- }
5268
- function readNonEmptyStringArray(record, key) {
5269
- const value = record[key];
5270
- if (!Array.isArray(value)) return [];
5271
- const entries = [];
5272
- for (const entry of value) {
5273
- const normalized = normalizeNonEmptyString(entry);
5274
- if (normalized) entries.push(normalized);
5275
- }
5276
- return entries;
5277
- }
5278
- function readNullableString$1(record, key) {
5279
- const value = record[key];
5280
- if (value === null) return null;
5281
- return typeof value === "string" ? value : void 0;
5282
- }
5283
- function readNumber$1(record, key) {
5284
- const value = record[key];
5285
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5286
- }
5287
- function resolveStartedAtFromDurationMs(durationMs) {
5288
- if (typeof durationMs !== "number" || !Number.isFinite(durationMs)) return;
5289
- return asDateTimestampMs(Date.now() - Math.max(0, durationMs));
5290
- }
5291
- function readNonNegativeInteger(record, key) {
5292
- const value = readNumber$1(record, key);
5293
- return value !== void 0 && Number.isInteger(value) && value >= 0 ? value : void 0;
5294
- }
5295
- function readBoolean(record, key) {
5296
- const value = record[key];
5297
- return typeof value === "boolean" ? value : void 0;
5298
- }
5299
- function readBooleanAlias(record, keys) {
5300
- for (const key of keys) {
5301
- const value = readBoolean(record, key);
5302
- if (value !== void 0) return value;
5303
- }
5304
- }
5305
- function readCodexErrorNotificationMessage(record) {
5306
- const error = record.error;
5307
- if (isJsonObject(error)) return readString$5(error, "message") ?? readString$5(error, "error");
5308
- return readString$5(record, "message");
5309
- }
5310
- function readHookOutputEntries(value) {
5311
- if (!Array.isArray(value)) return [];
5312
- return value.flatMap((entry) => {
5313
- if (!isJsonObject(entry)) return [];
5314
- const text = readString$5(entry, "text");
5315
- if (!text) return [];
5316
- const kind = readString$5(entry, "kind");
5317
- return [{
5318
- ...kind ? { kind } : {},
5319
- text
5320
- }];
5321
- });
5322
- }
5323
- function readFirstJsonObject(record, keys) {
5324
- for (const key of keys) {
5325
- const value = record[key];
5326
- if (isJsonObject(value)) return value;
5327
- }
5328
- }
5329
- function readNumberAlias(record, keys) {
5330
- for (const key of keys) {
5331
- const value = readNumber$1(record, key);
5332
- if (value !== void 0) return value;
5333
- }
5334
- }
5335
- function normalizeCodexTokenUsage(record) {
5336
- const promptTotalInput = readNumberAlias(record, CODEX_PROMPT_TOTAL_INPUT_KEYS);
5337
- const cacheRead = readNumberAlias(record, [
5338
- "cachedInputTokens",
5339
- "cached_input_tokens",
5340
- "cacheRead",
5341
- "cache_read",
5342
- "cache_read_input_tokens",
5343
- "cached_tokens"
5344
- ]);
5345
- return normalizeUsage({
5346
- input: promptTotalInput !== void 0 && cacheRead !== void 0 ? Math.max(0, promptTotalInput - cacheRead) : promptTotalInput ?? readNumber$1(record, "input"),
5347
- output: readNumberAlias(record, [
5348
- "outputTokens",
5349
- "output_tokens",
5350
- "output"
5351
- ]),
5352
- cacheRead,
5353
- cacheWrite: readNumberAlias(record, [
5354
- "cacheWrite",
5355
- "cache_write",
5356
- "cacheCreationInputTokens",
5357
- "cache_creation_input_tokens"
5358
- ]),
5359
- total: readNumberAlias(record, [
5360
- "totalTokens",
5361
- "total_tokens",
5362
- "total"
5363
- ])
5364
- });
5365
- }
5366
- function splitPlanText(text) {
5367
- return text.split(/\r?\n/).map((line) => line.trim().replace(/^[-*]\s+/, "")).filter((line) => line.length > 0);
5368
- }
5369
- function collectTextValues(map) {
5370
- return [...map.values()].filter((text) => text.trim().length > 0);
5371
- }
5372
- function collectReasoningTextValues(groups, itemOrder) {
5373
- return [...groups.values()].toSorted((left, right) => {
5374
- const itemDelta = (itemOrder.get(left.itemId) ?? Number.MAX_SAFE_INTEGER) - (itemOrder.get(right.itemId) ?? Number.MAX_SAFE_INTEGER);
5375
- if (itemDelta !== 0) return itemDelta;
5376
- const methodDelta = reasoningMethodOrder(left.method) - reasoningMethodOrder(right.method);
5377
- return methodDelta !== 0 ? methodDelta : left.index - right.index;
5378
- }).map((group) => group.text).filter((text) => text.trim().length > 0);
5379
- }
5380
- function reasoningMethodOrder(method) {
5381
- return method === "item/reasoning/summaryTextDelta" ? 0 : 1;
5382
- }
5383
- function extractRawAssistantText(item) {
5384
- return (Array.isArray(item.content) ? item.content : []).flatMap((entry) => {
5385
- if (!isJsonObject(entry)) return [];
5386
- const type = readString$5(entry, "type");
5387
- if (type !== "output_text" && type !== "text") return [];
5388
- const value = readString$5(entry, "text");
5389
- return value ? [value] : [];
5390
- }).join("").trim() || void 0;
5391
- }
5392
- function itemKind(item) {
5393
- switch (item.type) {
5394
- case "dynamicToolCall":
5395
- case "mcpToolCall": return "tool";
5396
- case "commandExecution": return "command";
5397
- case "fileChange": return "patch";
5398
- case "webSearch": return "search";
5399
- case "reasoning":
5400
- case "contextCompaction": return "analysis";
5401
- default: return;
5402
- }
5403
- }
5404
- function itemTitle(item) {
5405
- switch (item.type) {
5406
- case "commandExecution": return "Command";
5407
- case "fileChange": return "File change";
5408
- case "mcpToolCall": return "MCP tool";
5409
- case "dynamicToolCall": return "Tool";
5410
- case "webSearch": return "Web search";
5411
- case "contextCompaction": return "Context compaction";
5412
- case "reasoning": return "Reasoning";
5413
- default: return item.type;
5414
- }
5415
- }
5416
- function itemStatus(item) {
5417
- const status = readItemString(item, "status");
5418
- if (status === "failed") return "failed";
5419
- if (status === "declined") return "blocked";
5420
- if (status === "inProgress" || status === "running") return "running";
5421
- return "completed";
5422
- }
5423
- function formatMissingToolResultError(params) {
5424
- return `${MISSING_TOOL_RESULT_ERROR} toolCallId=${params.id}; toolName=${params.name}`;
5425
- }
5426
- function isNonSuccessItemStatus(status) {
5427
- return status === "failed" || status === "blocked";
5428
- }
5429
- function itemName(item) {
5430
- if (item.type === "dynamicToolCall" && typeof item.tool === "string") return item.tool;
5431
- if (item.type === "mcpToolCall" && typeof item.tool === "string") {
5432
- const server = typeof item.server === "string" ? item.server : void 0;
5433
- return server ? `${server}.${item.tool}` : item.tool;
5434
- }
5435
- if (item.type === "commandExecution") return "bash";
5436
- if (item.type === "fileChange") return "apply_patch";
5437
- if (item.type === "webSearch") return "web_search";
5438
- }
5439
- function isSideEffectingNativeToolItem(item) {
5440
- return itemStatus(item) !== "blocked" && (isMutatingNativeToolItem(item) || item.type === "mcpToolCall");
5441
- }
5442
- function shouldSynthesizeToolProgressForItem(item) {
5443
- switch (item.type) {
5444
- case "commandExecution":
5445
- case "fileChange":
5446
- case "webSearch":
5447
- case "mcpToolCall": return true;
5448
- default: return false;
5449
- }
5450
- }
5451
- function shouldRecordNativeToolTranscript(item) {
5452
- return shouldSynthesizeToolProgressForItem(item) && item.type !== "webSearch";
5453
- }
5454
- function isMutatingNativeToolItem(item) {
5455
- if (item.type === "commandExecution") return true;
5456
- return item.type === "fileChange" || item.type === "collabAgentToolCall" || item.type === "imageGeneration";
5457
- }
5458
- function shouldClearTerminalPresentationForNativeItem(item) {
5459
- switch (item.type) {
5460
- case "collabAgentToolCall":
5461
- case "commandExecution":
5462
- case "fileChange":
5463
- case "imageGeneration":
5464
- case "imageView":
5465
- case "mcpToolCall":
5466
- case "webSearch": return true;
5467
- default: return false;
5468
- }
5469
- }
5470
- function nativeToolActionFingerprint(item) {
5471
- if (item.type === "commandExecution" && typeof item.command === "string") return JSON.stringify({
5472
- type: item.type,
5473
- command: item.command,
5474
- cwd: typeof item.cwd === "string" ? item.cwd : ""
5475
- });
5476
- if (item.type === "fileChange") return JSON.stringify({
5477
- type: item.type,
5478
- changes: itemFileChanges(item)
5479
- });
5480
- }
5481
- function isNativePostToolUseRelayItem(item) {
5482
- switch (item.type) {
5483
- case "commandExecution":
5484
- case "fileChange":
5485
- case "mcpToolCall": return true;
5486
- default: return false;
5487
- }
5488
- }
5489
- function shouldSuppressChannelProgressForItem(item) {
5490
- if (shouldSynthesizeToolProgressForItem(item)) return true;
5491
- return item.type === "dynamicToolCall";
5492
- }
5493
- function itemToolArgs(item) {
5494
- if (item.type === "commandExecution") return sanitizeCodexAgentEventRecord({
5495
- command: item.command,
5496
- ...typeof item.cwd === "string" ? { cwd: item.cwd } : {}
5497
- });
5498
- if (item.type === "fileChange") return sanitizeCodexAgentEventRecord({ changes: itemFileChanges(item) });
5499
- if (item.type === "webSearch") return webSearchToolArgs(item);
5500
- if (item.type === "dynamicToolCall" || item.type === "mcpToolCall") return sanitizeCodexToolArguments(item.arguments);
5501
- }
5502
- function webSearchToolArgs(item) {
5503
- const action = isJsonObject(item.action) ? item.action : void 0;
5504
- const actionType = action ? readNonEmptyString(action, "type") : void 0;
5505
- const queries = action && actionType === "search" ? readNonEmptyStringArray(action, "queries") : [];
5506
- const query = normalizeNonEmptyString(item.query) ?? (action && actionType === "search" ? readNonEmptyString(action, "query") : void 0) ?? queries[0];
5507
- const url = action ? readNonEmptyString(action, "url") : void 0;
5508
- const pattern = action ? readNonEmptyString(action, "pattern") : void 0;
5509
- const args = {};
5510
- if (query) args.query = query;
5511
- if (queries.length > 0) args.queries = queries;
5512
- if (actionType && actionType !== "search") args.action = actionType;
5513
- if (url) args.url = url;
5514
- if (pattern) args.pattern = pattern;
5515
- if (!query && !url && !pattern) args.queryUnavailable = true;
5516
- return sanitizeCodexAgentEventRecord(args);
5517
- }
5518
- function itemToolResult(item) {
5519
- if (item.type === "commandExecution") return { result: sanitizeCodexAgentEventRecord({
5520
- status: item.status,
5521
- exitCode: item.exitCode,
5522
- durationMs: item.durationMs
5523
- }) };
5524
- if (item.type === "fileChange") return { result: sanitizeCodexAgentEventRecord({
5525
- status: item.status,
5526
- changes: itemFileChanges(item)
5527
- }) };
5528
- if (item.type === "mcpToolCall") return { result: sanitizeCodexAgentEventRecord({
5529
- status: item.status,
5530
- durationMs: item.durationMs,
5531
- ...item.error ? { error: item.error } : {},
5532
- ...item.result ? { result: item.result } : {}
5533
- }) };
5534
- if (item.type === "webSearch") return { result: webSearchToolResult(item) };
5535
- return {};
5536
- }
5537
- function webSearchToolResult(item) {
5538
- return sanitizeCodexAgentEventRecord({
5539
- status: itemStatus(item),
5540
- ...typeof item.durationMs === "number" ? { durationMs: item.durationMs } : {},
5541
- ...webSearchToolArgs(item)
5542
- });
5543
- }
5544
- function itemFileChanges(item) {
5545
- return Array.isArray(item.changes) ? item.changes.map((change) => ({
5546
- path: change.path,
5547
- kind: change.kind
5548
- })) : [];
5549
- }
5550
- function itemToolError(item, status, outputTextByItem) {
5551
- if (status === "blocked") return "codex native tool blocked";
5552
- if (status !== "failed") return;
5553
- return itemOutputText(item, outputTextByItem) ?? "codex native tool failed";
5554
- }
5555
- function itemMeta(item, detailMode = "explain") {
5556
- if (item.type === "commandExecution" && typeof item.command === "string") return inferToolMetaFromArgs("exec", {
5557
- command: item.command,
5558
- cwd: typeof item.cwd === "string" ? item.cwd : void 0
5559
- }, { detailMode });
5560
- if (item.type === "webSearch") return inferToolMetaFromArgs("web_search", webSearchToolArgs(item), { detailMode });
5561
- const toolName = itemName(item);
5562
- if ((item.type === "dynamicToolCall" || item.type === "mcpToolCall") && toolName) return inferToolMetaFromArgs(toolName, item.arguments, { detailMode });
5563
- }
5564
- function itemOutputText(item, outputTextByItem) {
5565
- if (item.type === "commandExecution") return item.aggregatedOutput?.trim() || outputTextByItem?.get(item.id)?.trim() || void 0;
5566
- if (item.type === "dynamicToolCall") return collectDynamicToolContentText(item.contentItems).trim() || void 0;
5567
- if (item.type === "mcpToolCall") {
5568
- if (item.error) return stringifyJsonValue(item.error);
5569
- return item.result ? stringifyJsonValue(item.result) : void 0;
5570
- }
5571
- }
5572
- function itemTranscriptResultText(item, outputTextByItem) {
5573
- const output = itemOutputText(item, outputTextByItem);
5574
- if (output) return output;
5575
- const result = itemToolResult(item).result;
5576
- return result ? stringifyJsonValue(result) : itemStatus(item);
5577
- }
5578
- function appendToolOutputDeltaText(outputTextByItem, itemId, delta) {
5579
- const current = outputTextByItem.get(itemId) ?? "";
5580
- if (current.length >= TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS) return;
5581
- const remaining = TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS - current.length;
5582
- const next = current + (delta.length > remaining ? delta.slice(0, remaining) : delta);
5583
- outputTextByItem.set(itemId, next);
5584
- }
5585
- function normalizeToolTranscriptArguments(value) {
5586
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
5587
- return value;
5588
- }
5589
- function collectDynamicToolContentText(contentItems) {
5590
- if (!Array.isArray(contentItems)) return "";
5591
- return contentItems.flatMap((entry) => {
5592
- if (!isJsonObject(entry)) return [];
5593
- const text = readString$5(entry, "text");
5594
- return text ? [text] : [];
5595
- }).join("\n");
5596
- }
5597
- function truncateToolTranscriptText(text) {
5598
- if (text.length <= TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS) return text;
5599
- return `${text.slice(0, TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS)}\n...(truncated)...`;
5600
- }
5601
- function toolResultStatusText(params) {
5602
- return params.isError ? `${params.name} failed` : `${params.name} completed`;
5603
- }
5604
- function stringifyJsonValue(value) {
5605
- try {
5606
- return JSON.stringify(value, null, 2);
5607
- } catch {
5608
- return;
5609
- }
5610
- }
5611
- function formatToolSummary(toolName, meta) {
5612
- const trimmedMeta = meta?.trim();
5613
- return formatToolAggregate(toolName, trimmedMeta ? [trimmedMeta] : void 0, { markdown: true });
5614
- }
5615
- function formatToolOutput(toolName, meta, output) {
5616
- const formattedOutput = formatToolProgressOutput(output);
5617
- if (!formattedOutput) return formatToolSummary(toolName, meta);
5618
- const fence = markdownFenceForText(formattedOutput);
5619
- return `${formatToolSummary(toolName, meta)}\n${fence}txt\n${formattedOutput}\n${fence}`;
5620
- }
5621
- function markdownFenceForText(text) {
5622
- return "`".repeat(Math.max(3, longestBacktickRun(text) + 1));
5623
- }
5624
- function longestBacktickRun(value) {
5625
- let longest = 0;
5626
- let current = 0;
5627
- for (const char of value) {
5628
- if (char === "`") {
5629
- current += 1;
5630
- longest = Math.max(longest, current);
5631
- continue;
3308
+ function fireTerminalIdleTimeout() {
3309
+ if (params.isCompleted() || params.isTerminalTurnNotificationQueued() || params.signal.aborted || !terminalIdleWatchArmed || params.getActiveAppServerTurnRequests() > 0) return;
3310
+ const idleMs = Math.max(0, Date.now() - completionLastActivityAt);
3311
+ if (idleMs < turnTerminalIdleTimeoutMs) {
3312
+ scheduleTerminalIdleWatch();
3313
+ return;
5632
3314
  }
5633
- current = 0;
3315
+ const timeout = {
3316
+ kind: "terminal",
3317
+ idleMs,
3318
+ timeoutMs: turnTerminalIdleTimeoutMs,
3319
+ lastActivityReason: completionLastActivityReason,
3320
+ details: completionLastActivityDetails
3321
+ };
3322
+ params.onTimeout(timeout);
3323
+ params.onMarkTimedOut();
3324
+ params.onRecordEvent("turn.terminal_idle_timeout", {
3325
+ threadId: params.threadId,
3326
+ turnId: params.getTurnId(),
3327
+ idleMs,
3328
+ timeoutMs: timeout.timeoutMs,
3329
+ lastActivityReason: timeout.lastActivityReason,
3330
+ ...timeout.details
3331
+ });
3332
+ embeddedAgentLog.warn("codex app-server turn idle timed out waiting for terminal event", {
3333
+ threadId: params.threadId,
3334
+ turnId: params.getTurnId(),
3335
+ idleMs,
3336
+ timeoutMs: timeout.timeoutMs,
3337
+ lastActivityReason: timeout.lastActivityReason,
3338
+ ...timeout.details
3339
+ });
3340
+ params.onAbort("turn_terminal_idle_timeout");
5634
3341
  }
5635
- return longest;
5636
- }
5637
- function readItemString(item, key) {
5638
- const value = item[key];
5639
- return typeof value === "string" ? value : void 0;
5640
- }
5641
- function readItem(value) {
5642
- if (!isJsonObject(value)) return;
5643
- const type = typeof value.type === "string" ? value.type : void 0;
5644
- const id = typeof value.id === "string" ? value.id : void 0;
5645
- if (!type || !id) return;
5646
- return value;
5647
- }
5648
- function readTurn(value) {
5649
- return readCodexTurn(value);
3342
+ return {
3343
+ isCompletionIdleWatchArmed: () => completionIdleWatchArmed,
3344
+ isCompletionIdleWatchPinnedByTerminalError: () => completionIdleWatchPinnedByTerminalError,
3345
+ isAssistantCompletionIdleWatchArmed: () => assistantCompletionIdleWatchArmed,
3346
+ armAttemptIdleWatch: () => {
3347
+ attemptIdleWatchArmed = true;
3348
+ scheduleAttemptIdleWatch();
3349
+ },
3350
+ armTerminalIdleWatch: () => {
3351
+ terminalIdleWatchArmed = true;
3352
+ scheduleTerminalIdleWatch();
3353
+ },
3354
+ armCompletionIdleWatch: (options) => {
3355
+ completionIdleWatchArmed = true;
3356
+ completionIdleWatchPinnedByTerminalError = options?.pinnedByTerminalError === true;
3357
+ completionIdleTimeoutOverrideMs = options?.timeoutMs !== void 0 ? resolveWatchTimeoutMs(options.timeoutMs) : void 0;
3358
+ scheduleCompletionIdleWatch();
3359
+ },
3360
+ disarmCompletionIdleWatch: () => {
3361
+ completionIdleWatchArmed = false;
3362
+ completionIdleWatchPinnedByTerminalError = false;
3363
+ completionIdleTimeoutOverrideMs = void 0;
3364
+ clearCompletionIdleTimer();
3365
+ },
3366
+ armAssistantCompletionIdleWatch: (details) => {
3367
+ assistantCompletionIdleWatchArmed = true;
3368
+ assistantCompletionLastActivityAt = Date.now();
3369
+ assistantCompletionLastActivityDetails = details;
3370
+ scheduleAssistantCompletionIdleWatch();
3371
+ },
3372
+ disarmAssistantCompletionIdleWatch: () => {
3373
+ assistantCompletionIdleWatchArmed = false;
3374
+ assistantCompletionLastActivityDetails = void 0;
3375
+ clearAssistantCompletionIdleTimer();
3376
+ },
3377
+ touchActivity: (reason, options) => {
3378
+ completionLastActivityAt = Date.now();
3379
+ completionLastActivityReason = reason;
3380
+ completionLastActivityDetails = options?.details;
3381
+ completionIdleTimeoutOverrideMs = void 0;
3382
+ if (options?.attemptProgress) recordAttemptProgress(reason, options);
3383
+ params.onProgressDiagnostic(reason);
3384
+ if (options?.arm) {
3385
+ completionIdleWatchArmed = true;
3386
+ completionIdleWatchPinnedByTerminalError = false;
3387
+ }
3388
+ scheduleProgressWatches();
3389
+ },
3390
+ noteNotificationReceived: (method, options) => {
3391
+ const now = Date.now();
3392
+ completionLastActivityAt = Math.max(completionLastActivityAt, Math.min(now, options?.receivedAtMs ?? now));
3393
+ completionLastActivityReason = `notification:${method}`;
3394
+ if (options?.details !== void 0) completionLastActivityDetails = options.details;
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
+ };
5650
3408
  }
5651
3409
  //#endregion
5652
3410
  //#region extensions/codex/src/app-server/native-subagent-notification.ts
@@ -5792,8 +3550,7 @@ var CodexNativeSubagentTaskMirror = class {
5792
3550
  constructor(params, runtime) {
5793
3551
  this.params = params;
5794
3552
  this.runtime = runtime;
5795
- this.mirroredThreadIds = /* @__PURE__ */ new Set();
5796
- this.failedMirrorThreadIds = /* @__PURE__ */ new Set();
3553
+ this.mirrorStateByThreadId = /* @__PURE__ */ new Map();
5797
3554
  this.terminalRunIds = /* @__PURE__ */ new Set();
5798
3555
  this.authoritativeRunIds = /* @__PURE__ */ new Set();
5799
3556
  this.expectedAuthoritativeRunIds = /* @__PURE__ */ new Set();
@@ -5827,8 +3584,8 @@ var CodexNativeSubagentTaskMirror = class {
5827
3584
  const spawn = readSubagentThreadSpawnSource(thread.source, this.params.parentThreadId);
5828
3585
  if (!spawn) return;
5829
3586
  const threadId = thread.id.trim();
5830
- if (!threadId || this.mirroredThreadIds.has(threadId)) return;
5831
- this.mirroredThreadIds.add(threadId);
3587
+ if (!threadId || this.mirrorStateByThreadId.get(threadId) === "mirrored") return;
3588
+ this.mirrorStateByThreadId.set(threadId, "mirrored");
5832
3589
  const runId = codexNativeSubagentRunId(threadId);
5833
3590
  const label = trimOptional(spawn.agent_nickname) ?? trimOptional(thread.agentNickname) ?? trimOptional(spawn.agent_role) ?? trimOptional(thread.agentRole) ?? "Codex subagent";
5834
3591
  const task = trimOptional(thread.preview) ?? `Codex native subagent${label === "Codex subagent" ? "" : ` ${label}`}`;
@@ -5846,11 +3603,9 @@ var CodexNativeSubagentTaskMirror = class {
5846
3603
  lastEventAt: this.now(),
5847
3604
  progressSummary: "Codex native subagent started."
5848
3605
  })) {
5849
- this.mirroredThreadIds.delete(threadId);
5850
- this.failedMirrorThreadIds.add(threadId);
3606
+ this.mirrorStateByThreadId.set(threadId, "failed");
5851
3607
  return;
5852
3608
  }
5853
- this.failedMirrorThreadIds.delete(threadId);
5854
3609
  this.terminalRunIds.delete(runId);
5855
3610
  this.authoritativeRunIds.delete(runId);
5856
3611
  this.applyStatus(threadId, thread.status);
@@ -5861,7 +3616,7 @@ var CodexNativeSubagentTaskMirror = class {
5861
3616
  this.applyStatus(notification.threadId, notification.status);
5862
3617
  }
5863
3618
  applyStatus(threadId, status) {
5864
- if (!this.mirroredThreadIds.has(threadId) && this.failedMirrorThreadIds.has(threadId)) return;
3619
+ if (this.mirrorStateByThreadId.get(threadId) === "failed") return;
5865
3620
  const statusType = status?.type;
5866
3621
  if (!statusType) return;
5867
3622
  const runId = codexNativeSubagentRunId(threadId);
@@ -5911,7 +3666,7 @@ var CodexNativeSubagentTaskMirror = class {
5911
3666
  const isSpawnAgentTool = normalizeToolName$1(readString$3(item, "tool")) === "spawnagent";
5912
3667
  const receiverThreadIds = readStringArray$1(item.receiverThreadIds);
5913
3668
  const agentsStates = readAgentsStates(item.agentsStates);
5914
- const spawnChildThreadIds = new Set([...receiverThreadIds, ...agentsStates.keys()]);
3669
+ const spawnChildThreadIds = /* @__PURE__ */ new Set([...receiverThreadIds, ...agentsStates.keys()]);
5915
3670
  if (isSpawnAgentTool) for (const childThreadId of spawnChildThreadIds) this.createTaskFromCollabSpawnItem(childThreadId, item);
5916
3671
  const toolCallStatus = normalizeCollabToolCallStatus(readString$3(item, "status"));
5917
3672
  const terminalToolCallThreadIds = /* @__PURE__ */ new Set();
@@ -5934,8 +3689,8 @@ var CodexNativeSubagentTaskMirror = class {
5934
3689
  }
5935
3690
  createTaskFromCollabSpawnItem(threadId, item) {
5936
3691
  const normalizedThreadId = threadId.trim();
5937
- if (!normalizedThreadId || this.mirroredThreadIds.has(normalizedThreadId)) return;
5938
- this.mirroredThreadIds.add(normalizedThreadId);
3692
+ if (!normalizedThreadId || this.mirrorStateByThreadId.get(normalizedThreadId) === "mirrored") return;
3693
+ this.mirrorStateByThreadId.set(normalizedThreadId, "mirrored");
5939
3694
  const prompt = trimOptional(readString$3(item, "prompt"));
5940
3695
  const runId = codexNativeSubagentRunId(normalizedThreadId);
5941
3696
  const createdAt = this.now();
@@ -5952,16 +3707,14 @@ var CodexNativeSubagentTaskMirror = class {
5952
3707
  lastEventAt: createdAt,
5953
3708
  progressSummary: "Codex native subagent spawned."
5954
3709
  })) {
5955
- this.mirroredThreadIds.delete(normalizedThreadId);
5956
- this.failedMirrorThreadIds.add(normalizedThreadId);
3710
+ this.mirrorStateByThreadId.set(normalizedThreadId, "failed");
5957
3711
  return;
5958
3712
  }
5959
- this.failedMirrorThreadIds.delete(normalizedThreadId);
5960
3713
  this.terminalRunIds.delete(runId);
5961
3714
  this.authoritativeRunIds.delete(runId);
5962
3715
  }
5963
3716
  applyCollabAgentStatus(threadId, status, message) {
5964
- if (!this.mirroredThreadIds.has(threadId) && this.failedMirrorThreadIds.has(threadId)) return;
3717
+ if (this.mirrorStateByThreadId.get(threadId) === "failed") return;
5965
3718
  const normalizedStatus = normalizeAgentStateStatus(status);
5966
3719
  if (!normalizedStatus) return;
5967
3720
  const runId = codexNativeSubagentRunId(threadId);
@@ -6159,6 +3912,7 @@ function registerCodexNativeSubagentMonitor(params) {
6159
3912
  taskRuntimeScope: params.taskRuntimeScope,
6160
3913
  agentId: params.agentId
6161
3914
  });
3915
+ return monitor;
6162
3916
  }
6163
3917
  /** Tracks native subagent thread notifications, transcript completions, and task delivery. */
6164
3918
  var CodexNativeSubagentMonitor = class {
@@ -6185,6 +3939,13 @@ var CodexNativeSubagentMonitor = class {
6185
3939
  this.childThreadIdsByAgentPath.clear();
6186
3940
  this.transcriptPathsByChildThreadId.clear();
6187
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
+ }
6188
3949
  configure(options) {
6189
3950
  const codexHome = normalizeOptionalString(options.codexHome);
6190
3951
  if (codexHome) this.codexHome = codexHome;
@@ -6222,10 +3983,29 @@ var CodexNativeSubagentMonitor = class {
6222
3983
  error: formatErrorMessage(error)
6223
3984
  });
6224
3985
  }
3986
+ this.markChildTurnStarted(notification);
3987
+ await this.handleChildSystemError(notification);
6225
3988
  this.captureChildAssistantMessage(notification);
6226
3989
  await this.handleChildTurnCompletion(notification);
6227
3990
  await this.handleCompletionNotification(notification);
6228
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
+ }
6229
4009
  ensureParentTaskRuntime(state) {
6230
4010
  if (state.taskRuntime || !state.requesterSessionKey || !state.taskRuntimeScope) return;
6231
4011
  state.taskRuntime = this.runtime.createAgentHarnessTaskRuntime({
@@ -6262,7 +4042,7 @@ var CodexNativeSubagentMonitor = class {
6262
4042
  const parentThreadId = item ? (readString$2(item, "senderThreadId") ?? readString$2(params, "threadId"))?.trim() : void 0;
6263
4043
  const state = parentThreadId ? this.parentStates.get(parentThreadId) : void 0;
6264
4044
  if (state && parentThreadId) {
6265
- 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));
6266
4046
  for (const childThreadId of childThreadIds) this.registerChildThread(parentThreadId, childThreadId);
6267
4047
  }
6268
4048
  return state;
@@ -6348,6 +4128,8 @@ var CodexNativeSubagentMonitor = class {
6348
4128
  if (childState && turn && readString$2(turn, "status") === "interrupted") {
6349
4129
  const turnId = readString$2(turn, "id");
6350
4130
  if (turnId) childState.assistantMessagesByTurn.delete(turnId);
4131
+ childState.settledWithoutCompletion = true;
4132
+ await this.flushDeferredParentSettlements(childState.parentThreadId);
6351
4133
  return;
6352
4134
  }
6353
4135
  if (childState && turn) this.captureChildTurnAssistantMessages(childState, turn);
@@ -6401,7 +4183,10 @@ var CodexNativeSubagentMonitor = class {
6401
4183
  childState.noFinalCompletionFallbackTimer = void 0;
6402
4184
  }
6403
4185
  }
6404
- if (!state.requesterSessionKey) return;
4186
+ if (!state.requesterSessionKey) {
4187
+ await this.flushDeferredParentSettlements(state.parentThreadId);
4188
+ return;
4189
+ }
6405
4190
  const completionKey = buildCompletionDedupeKey(state.parentThreadId, completion);
6406
4191
  if (state.deliveredCompletionKeys.has(completionKey)) return;
6407
4192
  const deliveryState = childState ?? this.ensureChildState(state.parentThreadId, completion.childThreadId);
@@ -6454,6 +4239,7 @@ var CodexNativeSubagentMonitor = class {
6454
4239
  });
6455
4240
  } finally {
6456
4241
  childState.deliveringCompletionKey = void 0;
4242
+ await this.flushDeferredParentSettlements(state.parentThreadId);
6457
4243
  }
6458
4244
  }
6459
4245
  markCompletionDeliveryPending(completion, error) {
@@ -6486,6 +4272,28 @@ var CodexNativeSubagentMonitor = class {
6486
4272
  }, delayMs);
6487
4273
  unrefTimer(childState.completionDeliveryTimer);
6488
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
+ }
6489
4297
  finalizeCompletionTask(completion, eventAt) {
6490
4298
  const taskRuntime = this.getTaskRuntimeForChild(completion.childThreadId);
6491
4299
  if (!taskRuntime) return;
@@ -6526,7 +4334,8 @@ var CodexNativeSubagentMonitor = class {
6526
4334
  assistantMessagesByTurn: /* @__PURE__ */ new Map(),
6527
4335
  transcriptPollAttempt: 0,
6528
4336
  transcriptTerminal: false,
6529
- completionDeliveryAttempt: 0
4337
+ completionDeliveryAttempt: 0,
4338
+ settledWithoutCompletion: false
6530
4339
  };
6531
4340
  this.childStates.set(normalizedChildThreadId, childState);
6532
4341
  }
@@ -6965,7 +4774,8 @@ function parseCodexAppServerByteLimit(value) {
6965
4774
  if (!match) return;
6966
4775
  const amount = Number(match[1]);
6967
4776
  if (!Number.isFinite(amount) || amount <= 0) return;
6968
- 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];
6969
4779
  if (multiplier === void 0) return;
6970
4780
  return Math.max(1, Math.round(amount * multiplier));
6971
4781
  }
@@ -7149,7 +4959,10 @@ async function rotateOversizedCodexAppServerStartupBinding(params) {
7149
4959
  bytes: file.bytes
7150
4960
  }))
7151
4961
  });
7152
- await clearCodexAppServerBinding(params.sessionFile);
4962
+ await params.bindingStore.mutate(params.identity, {
4963
+ kind: "clear",
4964
+ threadId: binding.threadId
4965
+ });
7153
4966
  return;
7154
4967
  }
7155
4968
  }
@@ -7178,7 +4991,10 @@ async function rotateOversizedCodexAppServerStartupBinding(params) {
7178
4991
  reserveTokens,
7179
4992
  projectedTurnTokens: params.projectedTurnTokens
7180
4993
  });
7181
- await clearCodexAppServerBinding(params.sessionFile);
4994
+ await params.bindingStore.mutate(params.identity, {
4995
+ kind: "clear",
4996
+ threadId: binding.threadId
4997
+ });
7182
4998
  return;
7183
4999
  }
7184
5000
  if (compaction?.truncateAfterCompaction !== true) return binding;
@@ -7462,7 +5278,7 @@ async function markCodexAuthProfileBlockedFromRateLimits(params) {
7462
5278
  async function formatCodexTurnStartUsageLimitError(params) {
7463
5279
  return refreshCodexUsageLimitError({
7464
5280
  client: params.client,
7465
- source: readCodexTurnStartUsageLimitErrorSource(params.error, params.pendingNotifications),
5281
+ source: readCodexTurnStartUsageLimitErrorSource(params.client, params.error, params.errorNotification, params.rateLimitsRevisionBeforeTurnStart),
7466
5282
  timeoutMs: params.timeoutMs,
7467
5283
  signal: params.signal
7468
5284
  });
@@ -7475,7 +5291,7 @@ async function refreshCodexUsageLimitPromptError(params) {
7475
5291
  source: {
7476
5292
  message: params.message,
7477
5293
  codexErrorInfo: "usageLimitExceeded",
7478
- rateLimits: readRecentCodexRateLimits()
5294
+ rateLimits: readRecentCodexRateLimits(params.client)
7479
5295
  },
7480
5296
  timeoutMs: params.timeoutMs,
7481
5297
  signal: params.signal
@@ -7513,7 +5329,7 @@ async function readCodexRateLimitsFromAppServerForUsageLimitError(params) {
7513
5329
  timeoutMs: resolveCodexUsageLimitRateLimitRefreshTimeoutMs(params.timeoutMs),
7514
5330
  signal: params.signal
7515
5331
  });
7516
- rememberCodexRateLimits(rateLimits);
5332
+ rememberCodexRateLimitsRead(params.client, rateLimits);
7517
5333
  return rateLimits;
7518
5334
  } catch (error) {
7519
5335
  embeddedAgentLog.debug("codex app-server rate-limit refresh failed after usage-limit error", { error: formatErrorMessage(error) });
@@ -7524,38 +5340,25 @@ function resolveCodexUsageLimitRateLimitRefreshTimeoutMs(timeoutMs) {
7524
5340
  if (timeoutMs === void 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) return CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS;
7525
5341
  return Math.max(100, Math.min(timeoutMs, CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS));
7526
5342
  }
7527
- function readCodexTurnStartUsageLimitErrorSource(error, pendingNotifications) {
7528
- const notificationError = readLatestCodexErrorNotification(pendingNotifications);
7529
- const notificationRateLimits = readLatestRateLimitNotificationPayload(pendingNotifications);
5343
+ function readCodexTurnStartUsageLimitErrorSource(client, error, errorNotification, rateLimitsRevisionBeforeTurnStart) {
5344
+ const notificationError = readCodexErrorNotification(errorNotification);
7530
5345
  const errorPayload = readCodexErrorPayload(error);
7531
- const rateLimits = notificationRateLimits ?? errorPayload.rateLimits ?? readRecentCodexRateLimits();
5346
+ const rateLimits = errorPayload.rateLimits ?? readRecentCodexRateLimits(client);
5347
+ const cacheUpdatedDuringTurnStart = rateLimitsRevisionBeforeTurnStart !== void 0 && readCodexRateLimitsRevision(client) > rateLimitsRevisionBeforeTurnStart;
7532
5348
  return {
7533
5349
  message: notificationError?.message ?? errorPayload.message ?? formatErrorMessage(error),
7534
5350
  codexErrorInfo: notificationError?.codexErrorInfo ?? errorPayload.codexErrorInfo,
7535
5351
  rateLimits,
7536
- rateLimitsTrustedForProfile: notificationRateLimits !== void 0 || errorPayload.rateLimits !== void 0
5352
+ rateLimitsTrustedForProfile: errorPayload.rateLimits !== void 0 || cacheUpdatedDuringTurnStart
7537
5353
  };
7538
5354
  }
7539
- function readLatestRateLimitNotificationPayload(notifications) {
7540
- for (let index = notifications.length - 1; index >= 0; index -= 1) {
7541
- const notification = notifications[index];
7542
- if (notification?.method === "account/rateLimits/updated") {
7543
- rememberCodexRateLimits(notification.params);
7544
- return notification.params;
7545
- }
7546
- }
7547
- }
7548
- function readLatestCodexErrorNotification(notifications) {
7549
- for (let index = notifications.length - 1; index >= 0; index -= 1) {
7550
- const notification = notifications[index];
7551
- if (notification?.method !== "error" || !isJsonObject(notification.params)) continue;
7552
- const error = notification.params.error;
7553
- if (!isJsonObject(error)) continue;
7554
- return {
7555
- message: readString$1(error, "message"),
7556
- codexErrorInfo: error.codexErrorInfo
7557
- };
7558
- }
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;
7559
5362
  }
7560
5363
  function readCodexErrorPayload(error) {
7561
5364
  const message = error instanceof Error ? error.message : void 0;
@@ -7564,7 +5367,6 @@ function readCodexErrorPayload(error) {
7564
5367
  if (!isJsonObject(data)) return { message };
7565
5368
  const nestedError = isJsonObject(data.error) ? data.error : data;
7566
5369
  const rateLimits = nestedError.rateLimits ?? data.rateLimits;
7567
- if (rateLimits !== void 0) rememberCodexRateLimits(rateLimits);
7568
5370
  return {
7569
5371
  message: readString$1(nestedError, "message") ?? message,
7570
5372
  codexErrorInfo: nestedError.codexErrorInfo,
@@ -7704,7 +5506,6 @@ function readRequestId(record) {
7704
5506
  //#region extensions/codex/src/app-server/run-attempt.ts
7705
5507
  const CODEX_NATIVE_HOOK_RELAY_RENEW_INTERVAL_MS = 6e4;
7706
5508
  const CODEX_APP_SERVER_PROJECTED_CHARS_PER_TOKEN = 4;
7707
- const CODEX_APP_SERVER_ACTIVE_NATIVE_TURN_WAIT_TIMEOUT_MS = 3e4;
7708
5509
  function shouldKeepCodexSharedAbortOpen(params) {
7709
5510
  if (params.explicitCancellationObserved || params.result.aborted || params.result.externalAbort) return false;
7710
5511
  return params.trigger === "memory" || !params.attemptSucceeded;
@@ -7792,7 +5593,7 @@ function toTranscriptToolResultContentItem(item) {
7792
5593
  }
7793
5594
  function formatUnsupportedCodexDynamicToolOutput(type) {
7794
5595
  const rawType = typeof type === "string" ? type.replace(/\s+/g, " ").trim() : "";
7795
- 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 ? "..." : ""}]`;
7796
5597
  }
7797
5598
  function shouldAwaitCodexAgentEndHook(params) {
7798
5599
  return !params.messageChannel && !params.messageProvider;
@@ -7811,7 +5612,7 @@ async function runCodexAgentEndHook(params, hookParams) {
7811
5612
  }
7812
5613
  runAgentEndSideEffects(sideEffectParams);
7813
5614
  }
7814
- async function runCodexAppServerAttempt(params, options = {}) {
5615
+ async function runCodexAppServerAttempt(params, options) {
7815
5616
  const attemptStartedAt = Date.now();
7816
5617
  const profilerEnabled = isCodexAppServerProfilerEnabled(params.config);
7817
5618
  const codexModelCallTrace = freezeDiagnosticTraceContext(createDiagnosticTraceContextFromActiveScope());
@@ -7823,7 +5624,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
7823
5624
  resetAnnounced: false
7824
5625
  };
7825
5626
  const preDynamicStartupStages = createCodexDynamicToolBuildStageTracker({ enabled: profilerEnabled });
7826
- const attemptClientFactory = options.clientFactory ?? defaultLeasedCodexAppServerClientFactory;
5627
+ const attemptClientFactory = options.clientFactory ?? getLeasedSharedCodexAppServerClient;
7827
5628
  const pluginConfig = readCodexPluginConfig(options.pluginConfig);
7828
5629
  const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
7829
5630
  const { sessionAgentId } = resolveSessionAgentIds$1({
@@ -7851,10 +5652,17 @@ async function runCodexAppServerAttempt(params, options = {}) {
7851
5652
  agentId: sessionAgentId
7852
5653
  });
7853
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;
7854
5662
  preDynamicStartupStages.mark("session-agent");
7855
5663
  const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
7856
5664
  const isInactiveThreadBootstrapBinding = (binding) => !activeContextEngine && binding?.contextEngine?.projection?.mode === "thread_bootstrap";
7857
- let startupBinding = await readCodexAppServerBinding(params.sessionFile);
5665
+ let startupBinding = await bindingStore.read(bindingIdentity);
7858
5666
  preDynamicStartupStages.mark("read-binding");
7859
5667
  const startupBindingAuthProfileId = startupBinding?.authProfileId;
7860
5668
  const initialStartupBindingHadInactiveThreadBootstrap = isInactiveThreadBootstrapBinding(startupBinding);
@@ -7953,6 +5761,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
7953
5761
  else params.abortSignal?.addEventListener("abort", abortFromUpstream, { once: true });
7954
5762
  startupBinding = await rotateOversizedCodexAppServerStartupBinding({
7955
5763
  binding: startupBinding,
5764
+ bindingStore,
5765
+ identity: bindingIdentity,
7956
5766
  sessionFile: params.sessionFile,
7957
5767
  agentDir,
7958
5768
  codexHome: appServer.start.env?.CODEX_HOME,
@@ -8158,6 +5968,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
8158
5968
  hookContext: {
8159
5969
  agentId: sessionAgentId,
8160
5970
  config: params.config,
5971
+ workspaceDir: effectiveWorkspace,
8161
5972
  sessionId: params.sessionId,
8162
5973
  sessionKey: sandboxSessionKey,
8163
5974
  runId: params.runId,
@@ -8406,8 +6217,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8406
6217
  codexTurnPromptText = decorateCodexTurnPromptText(nextPromptBuild);
8407
6218
  };
8408
6219
  const selectNewerVisibleHistoryAfterBinding = (binding) => {
8409
- const bindingUpdatedAt = Date.parse(binding.updatedAt);
8410
- if (!Number.isFinite(bindingUpdatedAt)) return [];
6220
+ const historyCoveredThrough = Date.parse(binding.historyCoveredThrough ?? "");
6221
+ const cutoff = Number.isFinite(historyCoveredThrough) ? historyCoveredThrough : 0;
8411
6222
  return historyMessages.filter((message) => {
8412
6223
  if (message.role !== "user" && message.role !== "assistant") return false;
8413
6224
  const record = message;
@@ -8415,9 +6226,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
8415
6226
  if (typeof idempotencyKey === "string" && idempotencyKey.startsWith("codex-app-server:")) return false;
8416
6227
  const meta = record["__openclaw"];
8417
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;
8418
6230
  if (typeof mirrorIdentity === "string" && mirrorIdentity.startsWith("codex-app-server:")) return false;
8419
6231
  const timestamp = typeof message.timestamp === "number" ? message.timestamp : typeof message.timestamp === "string" ? Date.parse(message.timestamp) : NaN;
8420
- return Number.isFinite(timestamp) && timestamp > bindingUpdatedAt;
6232
+ return Number.isFinite(timestamp) && timestamp > cutoff;
8421
6233
  });
8422
6234
  };
8423
6235
  const applyResumeStaleBindingContinuityProjection = (binding) => {
@@ -8469,6 +6281,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8469
6281
  });
8470
6282
  startupBinding = await rotateOversizedCodexAppServerStartupBinding({
8471
6283
  binding: startupBinding,
6284
+ bindingStore,
6285
+ identity: bindingIdentity,
8472
6286
  sessionFile: params.sessionFile,
8473
6287
  agentDir,
8474
6288
  codexHome: appServer.start.env?.CODEX_HOME,
@@ -8516,11 +6330,41 @@ async function runCodexAppServerAttempt(params, options = {}) {
8516
6330
  });
8517
6331
  let client;
8518
6332
  let thread;
6333
+ let turnRouter;
6334
+ let turnRoute;
6335
+ let routeActivated = false;
6336
+ let detachRouteAbort = () => void 0;
8519
6337
  let trajectoryEndRecorded = false;
8520
6338
  const markTrajectoryEndRecorded = () => {
8521
6339
  trajectoryEndRecorded = true;
8522
6340
  };
8523
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
+ };
8524
6368
  let releaseSharedClientLease;
8525
6369
  let sharedCodexClientRetiredForOneShotCleanup = false;
8526
6370
  const releaseSharedClientLeaseOnce = () => {
@@ -8558,6 +6402,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
8558
6402
  await releaseCodexSandboxExecServerEnvironment(sandbox);
8559
6403
  }
8560
6404
  };
6405
+ const releaseCurrentRoute = () => {
6406
+ detachRouteAbort();
6407
+ detachRouteAbort = () => void 0;
6408
+ turnRoute?.release();
6409
+ turnRoute = void 0;
6410
+ routeActivated = false;
6411
+ };
8561
6412
  let codexEnvironmentSelection;
8562
6413
  let codexExecutionCwd = effectiveCwd;
8563
6414
  let codexSandboxPolicy;
@@ -8582,7 +6433,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
8582
6433
  attemptTimeoutMs: params.timeoutMs,
8583
6434
  startupTimeoutMs,
8584
6435
  turnStartTimeoutMs: params.timeoutMs,
8585
- 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
+ }
8586
6443
  });
8587
6444
  return {
8588
6445
  configPatch: nativeHookRelay ? buildCodexNativeHookRelayConfig({
@@ -8602,6 +6459,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
8602
6459
  pluginAppServer = attemptAppServer;
8603
6460
  const startupResult = await startCodexAttemptThread({
8604
6461
  attemptClientFactory,
6462
+ bindingStore,
8605
6463
  appServer: attemptAppServer,
8606
6464
  pluginConfig,
8607
6465
  computerUseConfig,
@@ -8634,6 +6492,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8634
6492
  });
8635
6493
  client = startupResult.client;
8636
6494
  thread = startupResult.thread;
6495
+ turnRouter = startupResult.turnRouter;
6496
+ turnRoute = startupResult.turnRoute;
8637
6497
  pluginAppServer = startupResult.pluginAppServer;
8638
6498
  if (thread.lifecycle.action === "started") {
8639
6499
  const activeThreadReviewerPolicyContext = resolveCodexModelBackedReviewerPolicyContext({
@@ -8689,6 +6549,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8689
6549
  }
8690
6550
  });
8691
6551
  } catch (error) {
6552
+ activateNativePreToolUseFailureFallback();
6553
+ releaseCurrentRoute();
8692
6554
  nativeHookRelay?.unregister();
8693
6555
  await releaseSandboxExecEnvironment();
8694
6556
  params.abortSignal?.removeEventListener("abort", abortFromUpstream);
@@ -8709,7 +6571,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
8709
6571
  prompt: codexTurnPromptText,
8710
6572
  tools: toolBridge.availableSpecs
8711
6573
  });
8712
- const pendingNotifications = [];
6574
+ let latestStartupErrorNotification;
6575
+ let rateLimitsRevisionBeforeLastTurnStart;
8713
6576
  let completed = false;
8714
6577
  let terminalTurnNotificationQueued = false;
8715
6578
  let timedOut = false;
@@ -8729,7 +6592,6 @@ async function runCodexAppServerAttempt(params, options = {}) {
8729
6592
  const completion = new Promise((resolve) => {
8730
6593
  resolveCompletion = resolve;
8731
6594
  });
8732
- let notificationQueue = Promise.resolve();
8733
6595
  const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs(options.turnCompletionIdleTimeoutMs ?? appServer.turnCompletionIdleTimeoutMs);
8734
6596
  const turnAssistantCompletionIdleTimeoutMs = resolveCodexTurnAssistantCompletionIdleTimeoutMs(options.turnAssistantCompletionIdleTimeoutMs);
8735
6597
  const postToolRawAssistantCompletionIdleTimeoutMs = resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs(options.postToolRawAssistantCompletionIdleTimeoutMs ?? appServer.postToolRawAssistantCompletionIdleTimeoutMs, turnAssistantCompletionIdleTimeoutMs);
@@ -8749,7 +6611,6 @@ async function runCodexAppServerAttempt(params, options = {}) {
8749
6611
  let terminalDynamicToolReleaseCheckScheduled = false;
8750
6612
  let currentTurnHadNonTerminalDynamicToolResult = false;
8751
6613
  const turnIdRef = {};
8752
- const projectorRef = {};
8753
6614
  const userInputBridgeRef = {};
8754
6615
  const steeringQueueRef = {};
8755
6616
  const renewNativeHookRelayForTurnProgress = () => {
@@ -8901,6 +6762,21 @@ async function runCodexAppServerAttempt(params, options = {}) {
8901
6762
  });
8902
6763
  lifecycleTerminalEmitted = true;
8903
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
+ };
8904
6780
  const executionPhaseKeys = /* @__PURE__ */ new Set();
8905
6781
  const emitExecutionPhaseOnce = (key, info) => {
8906
6782
  if (executionPhaseKeys.has(key)) return;
@@ -8978,7 +6854,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
8978
6854
  const steeringQueue = steeringQueueRef.current;
8979
6855
  userInputBridge?.handleNotification(notification);
8980
6856
  if (!projector || !turnId) {
8981
- pendingNotifications.push(notification);
6857
+ if (notification.method === "error") latestStartupErrorNotification = notification;
8982
6858
  return;
8983
6859
  }
8984
6860
  const notificationState = applyCodexTurnNotificationState({
@@ -9050,85 +6926,43 @@ async function runCodexAppServerAttempt(params, options = {}) {
9050
6926
  }
9051
6927
  }
9052
6928
  };
9053
- let activeNativeTurnCompletionWaiter;
9054
- const waitForActiveNativeTurnCompletion = async (turnIds) => {
9055
- const turnIdSet = turnIds?.length ? new Set(turnIds) : void 0;
9056
- const matchesCompletion = (notification) => isCodexThreadTurnCompletedNotification(notification, thread.threadId, turnIdSet);
9057
- if (pendingNotifications.some((notification) => matchesCompletion(notification))) return true;
9058
- return await new Promise((resolve) => {
9059
- let settled = false;
9060
- const timeoutRef = {};
9061
- const finish = (completedNativeTurn) => {
9062
- if (settled) return;
9063
- settled = true;
9064
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
9065
- runAbortController.signal.removeEventListener("abort", abortListener);
9066
- if (activeNativeTurnCompletionWaiter?.resolve === finishComplete) activeNativeTurnCompletionWaiter = void 0;
9067
- resolve(completedNativeTurn);
9068
- };
9069
- const finishComplete = () => finish(true);
9070
- const abortListener = () => finish(false);
9071
- timeoutRef.current = setTimeout(() => finish(false), Math.min(appServer.requestTimeoutMs, CODEX_APP_SERVER_ACTIVE_NATIVE_TURN_WAIT_TIMEOUT_MS));
9072
- activeNativeTurnCompletionWaiter = {
9073
- matches: matchesCompletion,
9074
- resolve: finishComplete
9075
- };
9076
- 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
9077
6935
  });
9078
6936
  };
9079
- const enqueueNotification = (notification) => {
6937
+ const noteNotificationReceived = (notification, scope, receivedAtMs) => {
9080
6938
  const projector = projectorRef.current;
9081
6939
  const turnId = turnIdRef.current;
9082
- const userInputBridge = userInputBridgeRef.current;
9083
- const correlation = describeCodexNotificationCorrelation(notification, {
9084
- threadId: thread.threadId,
9085
- ...turnId ? { turnId } : {}
9086
- });
9087
- embeddedAgentLog.trace("codex app-server raw notification received", correlation);
9088
- if (notification.method === "turn/completed" && correlation.matchesActiveTurn === false) if (correlation.matchesActiveThread) embeddedAgentLog.warn("codex app-server turn/completed did not match active turn", correlation);
9089
- else embeddedAgentLog.debug("codex app-server turn/completed ignored for other subscribed thread", correlation);
9090
- if (notification.method === "turn/completed" && correlation.matchesActiveThread) {
9091
- if (activeNativeTurnCompletionWaiter?.matches(notification)) activeNativeTurnCompletionWaiter.resolve();
9092
- }
9093
- if (isCodexNotificationOutsideActiveRun(correlation)) return Promise.resolve();
9094
- if (!projector || !turnId) {
9095
- userInputBridge?.handleNotification(notification);
9096
- pendingNotifications.push(notification);
9097
- return Promise.resolve();
9098
- }
6940
+ if (!projector || !turnId) return;
9099
6941
  if (isTerminalTurnNotificationForTurn(notification, turnId)) terminalTurnNotificationQueued = true;
9100
- const isNativeResponseStreamDelta = isNativeResponseStreamDeltaNotification(notification);
9101
- const nativeResponseStreamDeltaMatchesActiveTurn = isNativeResponseStreamDelta && (correlation.matchesActiveTurn === true || isUnscopedCodexNotification(correlation) && canAttributeUnscopedNativeResponseDeltaToThisTurn(client));
9102
- const notificationMatchesActiveTurn = correlation.matchesActiveTurn === true || !isNativeResponseStreamDelta && correlation.matchesActiveTurn !== false || nativeResponseStreamDeltaMatchesActiveTurn;
9103
- if (correlation.matchesActiveTurn === true) {
6942
+ if (scope.turnId === turnId) {
9104
6943
  const modelToolCallId = readRawResponseToolCallId(notification);
9105
6944
  if (modelToolCallId) allocateCodexToolOutcomeOrdinal?.(modelToolCallId);
9106
6945
  const nativeItem = readCodexNotificationItem(notification.params);
9107
6946
  if (nativeItem?.type === "webSearch") projector.recordNativeToolOutcome(nativeItem);
9108
6947
  }
9109
- if (notificationMatchesActiveTurn) {
9110
- if (readCodexFinalizationHookNotification(notification, thread.threadId, turnId)?.phase === "started") {
9111
- unsettledFinalizationHookCount += 1;
9112
- turnWatches.disarmAssistantCompletionIdleWatch();
9113
- }
9114
- turnWatches.noteNotificationReceived(notification.method, isNativeResponseStreamDelta ? {
9115
- attemptProgress: true,
9116
- ...turnCrossedToolHandoff ? { attemptTimeoutMs: postToolRawAssistantCompletionIdleTimeoutMs } : {},
9117
- details: { lastNotificationMethod: notification.method }
9118
- } : void 0);
6948
+ if (readCodexFinalizationHookNotification(notification, thread.threadId, turnId)?.phase === "started") {
6949
+ unsettledFinalizationHookCount += 1;
6950
+ turnWatches.disarmAssistantCompletionIdleWatch();
9119
6951
  }
9120
- notificationQueue = notificationQueue.then(() => handleNotification(notification), () => handleNotification(notification));
9121
- return notificationQueue;
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);
9122
6960
  };
9123
6961
  const drainNotificationQueue = async () => {
9124
- while (true) {
9125
- const queued = notificationQueue;
9126
- await queued;
9127
- if (queued === notificationQueue) return;
9128
- }
6962
+ await turnRoute?.drain();
9129
6963
  };
9130
6964
  const nativeSubagentCodexHome = appServer.start.transport === "stdio" ? appServer.start.env?.CODEX_HOME ?? resolveCodexAppServerHomeDir(agentDir) : void 0;
9131
- registerCodexNativeSubagentMonitor({
6965
+ nativeSubagentMonitorRef.current = registerCodexNativeSubagentMonitor({
9132
6966
  client,
9133
6967
  parentThreadId: thread.threadId,
9134
6968
  requesterSessionKey: params.sessionKey,
@@ -9136,8 +6970,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9136
6970
  agentId: params.agentId,
9137
6971
  codexHome: nativeSubagentCodexHome
9138
6972
  });
9139
- const notificationCleanup = client.addNotificationHandler(enqueueNotification);
9140
- const requestCleanup = client.addRequestHandler(async (request) => {
6973
+ const handleServerRequest = async (request, scope) => {
9141
6974
  const turnId = turnIdRef.current;
9142
6975
  const userInputBridge = userInputBridgeRef.current;
9143
6976
  const projector = projectorRef.current;
@@ -9151,14 +6984,9 @@ async function runCodexAppServerAttempt(params, options = {}) {
9151
6984
  turnWatches.touchActivity(`request:${request.method}:start`, { attemptProgress: true });
9152
6985
  };
9153
6986
  try {
9154
- if (request.method === "account/chatgptAuthTokens/refresh") return refreshCodexAppServerAuthTokens({
9155
- agentDir,
9156
- authProfileId: startupAuthProfileId,
9157
- config: params.config
9158
- });
9159
6987
  if (!turnId) return;
9160
6988
  if (request.method === "mcpServer/elicitation/request") {
9161
- if (isCurrentThreadOptionalTurnRequestParams(request.params, thread.threadId, turnId)) {
6989
+ if (!scope.turnId || scope.turnId === turnId) {
9162
6990
  armCompletionWatchOnResponse = true;
9163
6991
  markCurrentTurnRequestProgress();
9164
6992
  }
@@ -9173,7 +7001,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9173
7001
  });
9174
7002
  }
9175
7003
  if (request.method === "item/tool/requestUserInput") {
9176
- if (isCurrentThreadTurnRequestParams(request.params, thread.threadId, turnId)) {
7004
+ if (scope.turnId === turnId) {
9177
7005
  armCompletionWatchOnResponse = true;
9178
7006
  markCurrentTurnRequestProgress();
9179
7007
  }
@@ -9184,7 +7012,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9184
7012
  }
9185
7013
  if (request.method !== "item/tool/call") {
9186
7014
  if (isCodexAppServerApprovalRequest(request.method)) {
9187
- if (isCurrentApprovalTurnRequestParams(request.params, thread.threadId, turnId)) {
7015
+ if (scope.turnId === turnId) {
9188
7016
  armCompletionWatchOnResponse = true;
9189
7017
  markCurrentTurnRequestProgress();
9190
7018
  }
@@ -9199,7 +7027,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
9199
7027
  execReviewerAgentId: sessionAgentId,
9200
7028
  internalExecAutoReview: appServer.approvalsReviewer === "user",
9201
7029
  autoApprove: shouldAutoApproveCodexAppServerApprovals(appServer),
9202
- signal: runAbortController.signal
7030
+ signal: runAbortController.signal,
7031
+ onNativeToolFailureDisposition: (itemId, disposition) => projector?.recordNativeToolApprovalFailure(itemId, disposition)
9203
7032
  });
9204
7033
  }
9205
7034
  return;
@@ -9230,6 +7059,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9230
7059
  });
9231
7060
  emitDynamicToolStartedDiagnostic({
9232
7061
  call,
7062
+ agentId: sessionAgentId,
9233
7063
  runId: params.runId,
9234
7064
  sessionId: params.sessionId,
9235
7065
  sessionKey: params.sessionKey
@@ -9337,6 +7167,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9337
7167
  })) emitDynamicToolTerminalDiagnostic({
9338
7168
  response,
9339
7169
  call,
7170
+ agentId: sessionAgentId,
9340
7171
  runId: params.runId,
9341
7172
  sessionId: params.sessionId,
9342
7173
  sessionKey: params.sessionKey,
@@ -9363,6 +7194,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9363
7194
  sessionKey: params.sessionKey
9364
7195
  })) emitDynamicToolErrorDiagnostic({
9365
7196
  call,
7197
+ agentId: sessionAgentId,
9366
7198
  runId: params.runId,
9367
7199
  sessionId: params.sessionId,
9368
7200
  sessionKey: params.sessionKey,
@@ -9386,7 +7218,63 @@ async function runCodexAppServerAttempt(params, options = {}) {
9386
7218
  scheduleTerminalDynamicToolReleaseCheck();
9387
7219
  } else turnWatches.scheduleProgressWatches();
9388
7220
  }
9389
- });
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
+ }
9390
7278
  const buildLlmInputEvent = () => ({
9391
7279
  runId: params.runId,
9392
7280
  sessionId: params.sessionId,
@@ -9433,6 +7321,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9433
7321
  throw error;
9434
7322
  };
9435
7323
  const startCodexTurn = async () => {
7324
+ const activeTurnRoute = await ensureCurrentThreadRoute();
9436
7325
  const turnAppServer = withCodexAppServerFastModeServiceTier(pluginAppServer, params);
9437
7326
  pluginAppServer = turnAppServer;
9438
7327
  const turnStartParams = buildTurnStartParams(params, {
@@ -9450,31 +7339,42 @@ async function runCodexAppServerAttempt(params, options = {}) {
9450
7339
  heartbeatCollaborationInstructions: workspaceBootstrapContext.heartbeatCollaborationInstructions
9451
7340
  });
9452
7341
  codexModelCallDiagnostics.setRequestPayloadBytes(utf8JsonByteLength(turnStartParams));
9453
- const startedTurn = assertCodexTurnStartResponse(await client.request("turn/start", turnStartParams, {
9454
- timeoutMs: params.timeoutMs,
9455
- signal: runAbortController.signal
9456
- }));
9457
- throwIfTurnStartAcceptedAfterAbort();
9458
- 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
+ }
9459
7365
  };
9460
- const activeNativeTurnIds = thread.lifecycle.action === "resumed" ? thread.lifecycle.activeTurnIds ?? [] : [];
9461
- if (activeNativeTurnIds.length > 0) {
9462
- embeddedAgentLog.info("codex app-server resumed thread has active native turn; waiting before turn/start", {
9463
- threadId: thread.threadId,
9464
- activeTurnIds: activeNativeTurnIds
9465
- });
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 });
9466
7368
  emitCodexAppServerEvent(params, {
9467
7369
  stream: "codex_app_server.lifecycle",
9468
7370
  data: {
9469
7371
  phase: "turn_start_waiting_for_native_turn",
9470
- threadId: thread.threadId,
9471
- activeTurnIds: activeNativeTurnIds
7372
+ threadId: thread.threadId
9472
7373
  }
9473
7374
  });
9474
- if (!await waitForActiveNativeTurnCompletion(activeNativeTurnIds) && !runAbortController.signal.aborted) embeddedAgentLog.warn("codex app-server active native turn did not complete before turn/start wait timed out", {
9475
- threadId: thread.threadId,
9476
- activeTurnIds: activeNativeTurnIds
9477
- });
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 });
9478
7378
  }
9479
7379
  try {
9480
7380
  codexModelCallDiagnostics.emitStarted();
@@ -9520,25 +7420,25 @@ async function runCodexAppServerAttempt(params, options = {}) {
9520
7420
  error: formatErrorMessage(turnStartError)
9521
7421
  });
9522
7422
  try {
9523
- const preRetrySessionFile = activeSessionFile;
9524
- const clearedPreRetryBinding = await clearCodexAppServerBindingForThread(preRetrySessionFile, thread.threadId);
9525
- const clearedActiveBinding = activeSessionFile !== preRetrySessionFile ? await clearCodexAppServerBindingForThread(activeSessionFile, thread.threadId) : false;
9526
- 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", {
9527
7427
  threadId: thread.threadId,
9528
7428
  error: formatErrorMessage(turnStartError)
9529
7429
  });
9530
7430
  else {
9531
7431
  thread = await restartContextEngineCodexThread();
9532
7432
  {
9533
- const retryBinding = await readCodexAppServerBinding(activeSessionFile);
7433
+ const retryBinding = await bindingStore.read(bindingIdentity);
9534
7434
  if (retryBinding && retryBinding.threadId === thread.threadId && retryBinding.contextEngine?.projection) {
9535
- const { schemaVersion: _schemaVersion, sessionFile: _boundSessionFile, updatedAt: _updatedAt, ...bindingForWrite } = retryBinding;
9536
- await writeCodexAppServerBinding(activeSessionFile, {
9537
- ...bindingForWrite,
9538
- contextEngine: bindingForWrite.contextEngine ? {
9539
- ...bindingForWrite.contextEngine,
7435
+ await bindingStore.mutate(bindingIdentity, {
7436
+ kind: "patch",
7437
+ threadId: retryBinding.threadId,
7438
+ patch: { contextEngine: retryBinding.contextEngine ? {
7439
+ ...retryBinding.contextEngine,
9540
7440
  projection: void 0
9541
- } : void 0
7441
+ } : void 0 }
9542
7442
  });
9543
7443
  embeddedAgentLog.info("codex app-server cleared stale context-engine projection after overflow retry", {
9544
7444
  threadId: thread.threadId,
@@ -9567,12 +7467,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
9567
7467
  const usageLimitError = await formatCodexTurnStartUsageLimitError({
9568
7468
  client,
9569
7469
  error: turnStartError,
9570
- pendingNotifications,
7470
+ errorNotification: latestStartupErrorNotification,
7471
+ rateLimitsRevisionBeforeTurnStart: rateLimitsRevisionBeforeLastTurnStart,
9571
7472
  timeoutMs: appServer.requestTimeoutMs,
9572
7473
  signal: runAbortController.signal
9573
7474
  });
9574
7475
  const turnStartErrorMessage = usageLimitError?.message ?? formatErrorMessage(turnStartError);
9575
- if (isInvalidCodexImagePayloadError(turnStartErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(activeSessionFile, {
7476
+ if (isInvalidCodexImagePayloadError(turnStartErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(bindingStore, bindingIdentity, {
9576
7477
  phase: "turn_start",
9577
7478
  threadId: thread.threadId,
9578
7479
  error: turnStartErrorMessage
@@ -9634,8 +7535,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
9634
7535
  threadId: thread.threadId,
9635
7536
  timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
9636
7537
  });
9637
- notificationCleanup();
9638
- requestCleanup();
7538
+ releaseCurrentRoute();
7539
+ activateNativePreToolUseFailureFallback();
9639
7540
  nativeHookRelay?.unregister();
9640
7541
  await releaseSandboxExecEnvironment();
9641
7542
  await runAgentCleanupStep({
@@ -9666,6 +7567,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9666
7567
  }
9667
7568
  }
9668
7569
  if (!turn) {
7570
+ activateNativePreToolUseFailureFallback();
9669
7571
  await releaseSharedClientLeaseAndRetireOneShotClient();
9670
7572
  throw new Error("codex app-server turn/start failed without an error");
9671
7573
  }
@@ -9697,27 +7599,12 @@ async function runCodexAppServerAttempt(params, options = {}) {
9697
7599
  }
9698
7600
  }, thread.threadId, activeTurnId, {
9699
7601
  nativePostToolUseRelayEnabled: nativeHookRelay?.allowedEvents.includes("post_tool_use") === true && nativeHookRelay.shouldRelayEvent("post_tool_use"),
7602
+ readRecentRateLimits: () => readRecentCodexRateLimits(client),
7603
+ runAbortSignal: runAbortController.signal,
9700
7604
  trajectoryRecorder,
9701
7605
  onNativeToolResultRecorded: maybeAnnounceFastModeAutoOff
9702
7606
  });
9703
- if (isTerminalTurnStatus(turn.turn.status) || pendingNotifications.some((notification) => isTerminalTurnNotificationForTurn(notification, activeTurnId))) terminalTurnNotificationQueued = true;
9704
- const closeCleanup = client.addCloseHandler?.(() => {
9705
- if (completed || terminalTurnNotificationQueued || runAbortController.signal.aborted) return;
9706
- clientClosedPromptError = "codex app-server client closed before turn completed";
9707
- trajectoryRecorder?.recordEvent("turn.client_closed", {
9708
- threadId: thread.threadId,
9709
- turnId: activeTurnId
9710
- });
9711
- embeddedAgentLog.warn("codex app-server client closed before turn completed", {
9712
- threadId: thread.threadId,
9713
- turnId: activeTurnId
9714
- });
9715
- clientClosedAbort = true;
9716
- runAbortController.abort("client_closed");
9717
- completed = true;
9718
- turnWatches.clearAllTimers();
9719
- resolveCompletion?.();
9720
- });
7607
+ if (isTerminalTurnStatus(turn.turn.status)) terminalTurnNotificationQueued = true;
9721
7608
  emitLifecycleStart();
9722
7609
  const activeProjector = projectorRef.current;
9723
7610
  if (!activeProjector) throw new Error("codex app-server projector was not initialized");
@@ -9725,7 +7612,17 @@ async function runCodexAppServerAttempt(params, options = {}) {
9725
7612
  turnWatches.touchActivity("turn:start", { arm: true });
9726
7613
  turnWatches.armAttemptIdleWatch();
9727
7614
  turnWatches.touchActivity("turn:start", { attemptProgress: true });
9728
- 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
+ }
9729
7626
  if (!completed && isTerminalTurnStatus(turn.turn.status)) await enqueueNotification({
9730
7627
  method: "turn/completed",
9731
7628
  params: {
@@ -9733,6 +7630,9 @@ async function runCodexAppServerAttempt(params, options = {}) {
9733
7630
  turnId: activeTurnId,
9734
7631
  turn: turn.turn
9735
7632
  }
7633
+ }, {
7634
+ threadId: thread.threadId,
7635
+ turnId: activeTurnId
9736
7636
  });
9737
7637
  const activeSteeringQueue = createCodexSteeringQueue({
9738
7638
  client,
@@ -9774,7 +7674,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
9774
7674
  const abortListener = () => {
9775
7675
  if (timedOut) {
9776
7676
  (async () => {
9777
- await clearCodexAppServerBindingForThread(activeSessionFile, thread.threadId);
7677
+ await bindingStore.mutate(bindingIdentity, {
7678
+ kind: "clear",
7679
+ threadId: thread.threadId
7680
+ });
9778
7681
  await retireCodexAppServerClientAfterTimedOutTurn(client, {
9779
7682
  threadId: thread.threadId,
9780
7683
  turnId: activeTurnId,
@@ -9822,7 +7725,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9822
7725
  const clientClosedPromptErrorForFinal = clientClosedPromptError && hasRecoverableCompletedAssistant ? void 0 : clientClosedPromptError;
9823
7726
  let finalPromptError = clientClosedPromptErrorForFinal ?? (effectiveTurnCompletionIdleTimedOut ? turnCompletionIdleTimeoutMessage : effectiveTimedOut ? "codex app-server attempt timed out" : result.promptError);
9824
7727
  const finalPromptErrorMessage = typeof finalPromptError === "string" ? finalPromptError : finalPromptError ? formatErrorMessage(finalPromptError) : void 0;
9825
- if (isInvalidCodexImagePayloadError(finalPromptErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(activeSessionFile, {
7728
+ if (isInvalidCodexImagePayloadError(finalPromptErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(bindingStore, bindingIdentity, {
9826
7729
  phase: "turn_completed",
9827
7730
  threadId: thread.threadId,
9828
7731
  turnId: activeTurnId,
@@ -9838,9 +7741,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
9838
7741
  turnId: activeTurnId,
9839
7742
  error: finalPromptErrorMessage
9840
7743
  });
9841
- const preClearSessionFile = activeSessionFile;
9842
- await clearCodexAppServerBindingForThread(preClearSessionFile, thread.threadId);
9843
- if (activeSessionFile !== preClearSessionFile) await clearCodexAppServerBindingForThread(activeSessionFile, thread.threadId);
7744
+ await bindingStore.mutate(bindingIdentity, {
7745
+ kind: "clear",
7746
+ threadId: thread.threadId
7747
+ });
9844
7748
  }
9845
7749
  const refreshedUsageLimitPromptError = await refreshCodexUsageLimitPromptError({
9846
7750
  client,
@@ -9896,7 +7800,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
9896
7800
  if (modelCallFailureKind) codexModelCallDiagnostics.emitError(finalPromptError ?? "codex app-server attempt interrupted", { failureKind: modelCallFailureKind });
9897
7801
  else if (finalPromptError) codexModelCallDiagnostics.emitError(finalPromptError);
9898
7802
  else codexModelCallDiagnostics.emitCompleted(result);
9899
- await mirrorTranscriptBestEffort({
7803
+ const assistantTranscriptOwned = await mirrorTranscriptBestEffort({
9900
7804
  params,
9901
7805
  agentId: sessionAgentId,
9902
7806
  notifyUserMessagePersisted,
@@ -9973,17 +7877,14 @@ async function runCodexAppServerAttempt(params, options = {}) {
9973
7877
  shouldDelayNativeHookRelayUnregister = completedTurnStatus === "completed" && !effectiveTimedOut && !runAbortController.signal.aborted && !finalAborted && !finalPromptError;
9974
7878
  if (shouldDelayNativeHookRelayUnregister) try {
9975
7879
  await markCodexAppServerBindingCoveredThroughTurn({
9976
- sessionFile: params.sessionFile,
9977
- threadId: thread.threadId,
9978
- authProfileStore: params.authProfileStore,
9979
- agentDir: params.agentDir,
9980
- config: params.config
7880
+ bindingStore,
7881
+ identity: bindingIdentity,
7882
+ threadId: thread.threadId
9981
7883
  });
9982
7884
  } catch (error) {
9983
- if (!await clearCodexAppServerBindingForThread(params.sessionFile, thread.threadId, {
9984
- authProfileStore: params.authProfileStore,
9985
- agentDir: params.agentDir,
9986
- config: params.config
7885
+ if (!await bindingStore.mutate(bindingIdentity, {
7886
+ kind: "clear",
7887
+ threadId: thread.threadId
9987
7888
  })) throw error;
9988
7889
  embeddedAgentLog.warn("codex app-server binding coverage update failed after completed turn; cleared stale binding", {
9989
7890
  threadId: thread.threadId,
@@ -10015,11 +7916,18 @@ async function runCodexAppServerAttempt(params, options = {}) {
10015
7916
  });
10016
7917
  if (finalPromptError) emitLifecycleTerminal({
10017
7918
  phase: "error",
10018
- error: formatErrorMessage(finalPromptError)
7919
+ error: formatErrorMessage(finalPromptError),
7920
+ ...buildLifecycleTerminalMeta({
7921
+ aborted: finalAborted,
7922
+ timedOut: effectiveTimedOut
7923
+ })
10019
7924
  });
10020
7925
  else emitLifecycleTerminal({
10021
7926
  phase: "end",
10022
- ...finalAborted ? { aborted: true } : {}
7927
+ ...buildLifecycleTerminalMeta({
7928
+ aborted: finalAborted,
7929
+ timedOut: effectiveTimedOut
7930
+ })
10023
7931
  });
10024
7932
  return {
10025
7933
  ...result,
@@ -10029,6 +7937,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
10029
7937
  promptErrorSource: finalPromptErrorSource,
10030
7938
  ...codexAppServerFailure ? { codexAppServerFailure } : {},
10031
7939
  ...promptTimeoutOutcome ? { promptTimeoutOutcome } : {},
7940
+ ...assistantTranscriptOwned ? { assistantTranscriptOwned: true } : {},
10032
7941
  systemPromptReport
10033
7942
  };
10034
7943
  } finally {
@@ -10036,7 +7945,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
10036
7945
  codexModelCallDiagnostics.emitError("codex app-server run completed without model-call terminal event");
10037
7946
  emitLifecycleTerminal({
10038
7947
  phase: "error",
10039
- 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
+ })
10040
7953
  });
10041
7954
  if (trajectoryRecorder && !trajectoryEndRecorded) trajectoryRecorder.recordEvent("session.ended", {
10042
7955
  status: timedOut || runAbortController.signal.aborted && !clientClosedAbort ? "interrupted" : "cleanup",
@@ -10055,16 +7968,21 @@ async function runCodexAppServerAttempt(params, options = {}) {
10055
7968
  }
10056
7969
  });
10057
7970
  if (!timedOut && !runAbortController.signal.aborted) await steeringQueueRef.current?.flushPending();
10058
- 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, {
10059
7979
  threadId: thread.threadId,
10060
7980
  timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
10061
7981
  });
10062
7982
  userInputBridgeRef.current?.cancelPending();
10063
7983
  turnWatches.clearAllTimers();
10064
- notificationCleanup();
10065
- requestCleanup();
10066
- closeCleanup?.();
10067
- await releaseSharedClientLeaseAndRetireOneShotClient();
7984
+ releaseCurrentRoute();
7985
+ if (!yieldedOneShotCleanupDeferred) await releaseSharedClientLeaseAndRetireOneShotClient();
10068
7986
  if (nativeHookRelay) if (shouldDelayNativeHookRelayUnregister) scheduleCodexNativeHookRelayUnregister({
10069
7987
  relay: nativeHookRelay,
10070
7988
  hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec
@@ -10081,9 +7999,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
10081
7999
  function readDynamicToolCallParams(value) {
10082
8000
  return readCodexDynamicToolCallParams(value);
10083
8001
  }
10084
- async function clearCodexBindingAfterInvalidImagePayload(sessionFile, fields) {
10085
- const currentBinding = await readCodexAppServerBinding(sessionFile);
10086
- 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) {
10087
8007
  embeddedAgentLog.warn("codex app-server image payload error detected for unbound thread; preserving thread binding", {
10088
8008
  ...fields,
10089
8009
  boundThreadId: currentBinding.threadId
@@ -10091,32 +8011,21 @@ async function clearCodexBindingAfterInvalidImagePayload(sessionFile, fields) {
10091
8011
  return;
10092
8012
  }
10093
8013
  embeddedAgentLog.warn("codex app-server image payload error detected; clearing thread binding", fields);
10094
- await clearCodexAppServerBinding(sessionFile);
8014
+ await bindingStore.mutate(identity, {
8015
+ kind: "clear",
8016
+ threadId: expectedThreadId
8017
+ });
10095
8018
  }
10096
8019
  async function markCodexAppServerBindingCoveredThroughTurn(params) {
10097
- const currentBinding = await readCodexAppServerBinding(params.sessionFile, {
10098
- authProfileStore: params.authProfileStore,
10099
- agentDir: params.agentDir,
10100
- config: params.config
10101
- });
10102
- if (!currentBinding || currentBinding.threadId !== params.threadId) return;
10103
- const { schemaVersion: _schemaVersion, sessionFile: _boundSessionFile, updatedAt: _updatedAt, ...bindingForWrite } = currentBinding;
10104
- await writeCodexAppServerBinding(params.sessionFile, bindingForWrite, {
10105
- authProfileStore: params.authProfileStore,
10106
- agentDir: params.agentDir,
10107
- config: params.config
8020
+ await params.bindingStore.mutate(params.identity, {
8021
+ kind: "patch",
8022
+ threadId: params.threadId,
8023
+ patch: { historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString() }
10108
8024
  });
10109
8025
  }
10110
8026
  function isNonEmptyString(value) {
10111
8027
  return typeof value === "string" && value.length > 0;
10112
8028
  }
10113
- function canAttributeUnscopedNativeResponseDeltaToThisTurn(client) {
10114
- const activeLeases = client.getActiveSharedLeaseCountForUnscopedNotifications?.();
10115
- return activeLeases === void 0 || activeLeases <= 1;
10116
- }
10117
- function isUnscopedCodexNotification(correlation) {
10118
- return !correlation.threadId && !correlation.turnId && !correlation.nestedTurnThreadId && !correlation.nestedTurnId;
10119
- }
10120
8029
  function shouldUseFreshCodexThreadAfterContextEngineOverflow(params) {
10121
8030
  if (!params.contextEngineActive || params.thread.lifecycle.action !== "resumed") return false;
10122
8031
  return isCodexContextWindowError(params.error);
@@ -10131,13 +8040,6 @@ function isCodexActiveCompactTurnError(error) {
10131
8040
  const codexErrorInfo = isJsonObject(data?.codexErrorInfo) ? data.codexErrorInfo : void 0;
10132
8041
  return (isJsonObject(codexErrorInfo?.activeTurnNotSteerable) ? codexErrorInfo.activeTurnNotSteerable : void 0)?.turnKind === "compact";
10133
8042
  }
10134
- function isCodexThreadTurnCompletedNotification(notification, threadId, turnIds) {
10135
- if (notification.method !== "turn/completed") return false;
10136
- const correlation = describeCodexNotificationCorrelation(notification, { threadId });
10137
- if (!correlation.matchesActiveThread) return false;
10138
- const turnId = correlation.turnId ?? correlation.nestedTurnId;
10139
- return !turnIds || turnId !== void 0 && turnIds.has(turnId);
10140
- }
10141
8043
  function readCodexFinalizationHookNotification(notification, threadId, turnId) {
10142
8044
  if (notification.method !== "hook/started" && notification.method !== "hook/completed") return;
10143
8045
  const params = isJsonObject(notification.params) ? notification.params : void 0;
@@ -10207,12 +8109,15 @@ function handleApprovalRequest(params) {
10207
8109
  execReviewerAgentId: params.execReviewerAgentId,
10208
8110
  internalExecAutoReview: params.internalExecAutoReview,
10209
8111
  autoApprove: params.autoApprove,
10210
- signal: params.signal
8112
+ signal: params.signal,
8113
+ onNativeToolFailureDisposition: params.onNativeToolFailureDisposition
10211
8114
  });
10212
8115
  }
10213
8116
  function resolveCodexDynamicToolDirectNames(params) {
10214
- if (params.sourceReplyDeliveryMode !== "message_tool_only") return [];
10215
- 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;
10216
8121
  }
10217
8122
  //#endregion
10218
8123
  export { runCodexAppServerAttempt };