@openclaw/codex 2026.8.2 → 2026.9.1

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 (61) hide show
  1. package/dist/api.js +3 -3
  2. package/dist/{attempt-client-cleanup-CpVfwctT.js → attempt-client-cleanup-DYHcRQAa.js} +6 -43
  3. package/dist/attempt-context-DmQUNWwT.js +634 -0
  4. package/dist/{binding-connection-CF7BmrDk.js → binding-connection-E6uDsPlx.js} +2 -2
  5. package/dist/{bounded-turn-DRC3hcjm.js → bounded-turn-wzJrwKKe.js} +8 -9
  6. package/dist/{capabilities-D6m_TdVy.js → capabilities-dIjNVCYH.js} +1 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters--UToCokS.js → command-formatters-CLHhEB1Q.js} +3 -3
  9. package/dist/{command-handlers-CLFx2Ypa.js → command-handlers-CTGb2csu.js} +54 -55
  10. package/dist/{compact-5xqW1oCh.js → compact-_t0HQrUS.js} +33 -25
  11. package/dist/{config-CcYL77O6.js → config-BhmfC1s6.js} +12 -8
  12. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  13. package/dist/doctor-contract-api.js +31 -4
  14. package/dist/{dynamic-tools-BmeXjwNZ.js → dynamic-tools-BU9tmlYm.js} +448 -457
  15. package/dist/{effective-mcp-catalog-XXQKgK1U.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
  16. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
  17. package/dist/{event-projector-usage-B2r5vhWH.js → event-projector-usage-C5mcrGjp.js} +9 -137
  18. package/dist/{harness-BgeB7LnD.js → harness-DXVZ5II5.js} +13 -13
  19. package/dist/harness.js +1 -1
  20. package/dist/index.js +323 -313
  21. package/dist/{isolated-completion-CxTus3vZ.js → isolated-completion-kg9kv4iH.js} +4 -4
  22. package/dist/{launch-args-sXOAg0SX.js → launch-args-RXQwn8zV.js} +7 -1
  23. package/dist/{managed-binary-ZkGodW19.js → managed-binary-4-Vjz5GH.js} +14 -9
  24. package/dist/media-understanding-provider.js +1 -1
  25. package/dist/{model-catalog-DULBcniB.js → model-catalog-CUM68Hkl.js} +11 -11
  26. package/dist/{models-DaWfETQk.js → models-sYnEc0gd.js} +3 -3
  27. package/dist/{native-subagent-monitor-vXYUZH37.js → native-subagent-monitor-CnH147jH.js} +3 -3
  28. package/dist/{node-exec-server.runtime-DirKVZ5h.js → node-exec-server.runtime-CstNHCmC.js} +2 -2
  29. package/dist/{plugin-approval-roundtrip-CCWQGlAI.js → plugin-approval-roundtrip-CNe-Xrjx.js} +29 -3
  30. package/dist/{plugin-marketplace-discovery-BXjIFEiw.js → plugin-marketplace-discovery-CfFlKqDl.js} +6 -6
  31. package/dist/{plugin-metadata-cache-DQQm4gWT.js → plugin-metadata-cache-BFrPhb2c.js} +5 -5
  32. package/dist/provider-capabilities-DW5p4k9w.js +101 -0
  33. package/dist/{rate-limits-BqHEHXQJ.js → rate-limits-CawQJfUS.js} +1 -1
  34. package/dist/{request-BWxweEdK.js → request-DYAI5FQE.js} +18 -6
  35. package/dist/{run-attempt-BVLE4Gbi.js → run-attempt-D1hJO4fX.js} +483 -1865
  36. package/dist/{runtime-artifact-D9pYp4zL.js → runtime-artifact-C3jVimFn.js} +19 -12
  37. package/dist/{session-binding-D0zinCP1.js → session-binding-BHuTCRZQ.js} +8 -10
  38. package/dist/{session-catalog-eMhVm0Ql.js → session-catalog-yyvTWFOw.js} +138 -116
  39. package/dist/{session-cli-B2lyjJA0.js → session-cli-CmFEXMAM.js} +1 -1
  40. package/dist/session-initialization-BeWzjGiR.js +74 -0
  41. package/dist/{session-permission-policy-ScnAyG90.js → session-permission-policy-DeWVTCMj.js} +2 -2
  42. package/dist/{session-retirement-CtUtUTr9.js → session-retirement-BujDjXVF.js} +16 -9
  43. package/dist/{session-upstream-marker-gqrYIRHk.js → session-upstream-marker-C351-5Qw.js} +18 -2
  44. package/dist/{settled-turn-context-Bk3IUwod.js → settled-turn-context-D3SNWtUn.js} +6 -28
  45. package/dist/{settled-turn-finalizer-BXLN9PfJ.js → settled-turn-finalizer-BVJx4yoc.js} +5 -5
  46. package/dist/{shared-client-B1tT5U6r.js → shared-client-DxcfF9KX.js} +2748 -101
  47. package/dist/{side-question-CZ-mCw-t.js → side-question-BTZAII4l.js} +97 -63
  48. package/dist/{thread-lifecycle-CGGKP720.js → thread-lifecycle-DAcEJ_Om.js} +2039 -1487
  49. package/dist/{thread-ownership-RkaoZCmQ.js → thread-ownership-zGokd8i1.js} +10 -9
  50. package/dist/thread-policy-g4KbJPzp.js +54 -0
  51. package/dist/{thread-resume-BFt1-ZDa.js → thread-resume-C7pyVKeW.js} +15 -7
  52. package/dist/{transcript-mirror-1ep4XjzF.js → transcript-mirror-DTUxFWa_.js} +47 -10
  53. package/dist/{transport-stdio-NT_Dpd9V.js → transport-stdio-CyPldczm.js} +3 -1
  54. package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
  55. package/dist/{usage-DUhd58gI.js → usage-Bj-rrzkR.js} +3 -3
  56. package/dist/{web-search-provider.runtime-BEmlE7ov.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
  57. package/openclaw.plugin.json +0 -30
  58. package/package.json +5 -5
  59. package/dist/protocol-validators-Bg5rpNxk.js +0 -2542
  60. package/dist/session-catalog-home-id-Dt2GiG5o.js +0 -112
  61. package/dist/upstream-session-fork-Dc8BFExw.js +0 -312
@@ -1,54 +1,54 @@
1
- import { C as isCodexRemoteExecPlacementSandbox, D as resolveOpenClawExecPolicyForCodexAppServer, E as resolveCodexPluginsPolicy, P as resolveCodexAppServerHomeDir, S as isCodexPairedNodeRemoteExecPlacementSandbox, T as readCodexPluginConfig, d as withMcpElicitationsApprovalPolicy, m as resolveCodexModelBackedReviewerPolicyContext, o as resolveCodexComputerUseConfig, p as canUseCodexModelBackedApprovalsReviewerForModel, r as resolveCodexAppServerHomeScope, u as shouldAutoApproveCodexAppServerApprovals, v as readCodexRequirementsToml, w as isCodexSandboxExecServerEnabled } from "./config-CcYL77O6.js";
2
- import { $ as CodexAppServerRpcError, A as CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS, B as resolveCodexTurnTerminalIdleTimeoutMs, Dt as readCodexRateLimitsRevision, Ft as resolveCodexAppServerFallbackApiKeyCacheKey, G as isCodexAppServerConnectionClosedError, H as getCodexAppServerClientInstanceId, Ht as isJsonObject, I as resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs, It as resolveCodexAppServerPreparedApiKeyCacheKey, J as isCodexAppServerOverloadError, K as isCodexAppServerIndeterminateRequestCancellationError, L as resolveCodexStartupTimeoutMs, Lt as resolveCodexAppServerPreparedAuthHandoff, M as TURN_FINALIZE_DRAIN_ABORT_GRACE_MS, Mt as resolveCodexAppServerAuthProfileId, N as TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS, Nt as resolveCodexAppServerAuthProfileIdForAgent, Ot as readRecentCodexRateLimits, P as isCodexAppServerStartupError, Q as resolveCodexAppServerClientInstanceId, R as resolveCodexTurnAssistantCompletionIdleTimeoutMs, Tt as withAbortableTimeout, U as isCodexAppServerApprovalRequest, V as withCodexStartupTimeout, Vt as flattenCodexDynamicToolFunctions, W as isCodexAppServerBrokenPipeError, X as isCodexAppServerRequestTimeoutError, _t as ensureCodexAppServerClientRuntime, a as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, at as resolveDynamicToolCallTimeoutMs, c as getLeasedSharedCodexAppServerClient, ct as shouldReleaseTurnAfterTerminalDynamicTool, dt as resolveCodexToolAbortTerminalReason, et as createCodexElicitationResponse, f as readCodexAppServerClientDesktopGenerationFingerprint, h as releaseLeasedSharedCodexAppServerClient, i as clearSharedCodexAppServerClientIfCurrent, it as isMatchingDynamicToolTerminalDiagnostic, j as CodexAppServerStartupError, jt as resolveCodexAppServerAuthAccountCacheKey, kt as rememberCodexRateLimitsRead, l as getSharedCodexAppServerClient, lt as toCodexDynamicToolProgressResponse, nt as hasPendingDynamicToolTerminalDiagnostic, ot as resolveTerminalDynamicToolBatchAction, q as isCodexAppServerIndeterminateTransportError, rt as isDynamicToolTerminalDiagnosticEvent, s as createIsolatedCodexAppServerClient, st as shouldBlockTerminalReleaseForNonTerminalDynamicToolResult, tt as handleDynamicToolCallWithTimeout, u as isCodexAppServerStartSelectionChangedError, ut as toCodexDynamicToolProtocolResponse, v as retainSharedCodexAppServerClientIfCurrent, xt as protectCodexAppServerLiveThread, y as retireSharedCodexAppServerClientIfCurrent, z as resolveCodexTurnCompletionIdleTimeoutMs } from "./shared-client-B1tT5U6r.js";
3
- import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, l as resolveCodexRunSessionBindingAuthority, u as scopeCodexRunBindingStore } from "./session-binding-D0zinCP1.js";
4
- import { c as assertCodexTurnStartResponse, d as readCodexTurn, f as readCodexTurnCompletedNotification, l as readCodexDynamicToolCallParams } from "./protocol-validators-Bg5rpNxk.js";
5
- import { B as readNonNegativeInteger, D as itemStatus, E as itemName, H as splitPlanText, I as readItem, L as readItemString, M as extractRawAssistantText, P as readCodexErrorNotificationMessage, V as readNullableString, _ as readCodexNotificationThreadId, a as closeCodexStartupClientBestEffort, d as terminateCodexBackgroundTerminals, f as unsubscribeCodexThreadBestEffort, g as isCodexNotificationForTurn, h as CodexProjectionDiagnostics, j as shouldSynthesizeToolProgressForItem, k as shouldClearTerminalPresentationForNativeItem, l as retireCodexAppServerClientAfterTimedOutTurn, m as getCodexAppServerTurnRouter, o as interruptCodexTurnAndWaitBestEffort, p as CODEX_APP_SERVER_NATIVE_TURN_WAIT_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError, t as CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS, u as retireUnsafeCodexTurnClientBestEffort } from "./attempt-client-cleanup-CpVfwctT.js";
6
- import { C as readRawResponseToolCallId, E as updateActiveTurnItemIds, S as readNotificationItemId, T as updateActiveCompletionBlockerItemIds, _ as isReasoningItemCompletionNotification, a as codexExecutionToolName, b as isTerminalTurnStatus, c as isAssistantCompletionReleaseNotification, d as isNativeToolProgressNotification, f as isPendingOpenClawDynamicToolCompletionNotification, g as isRawToolOutputCompletionNotification, h as isRawReasoningCompletionNotification, l as isCodexTurnAbortMarkerNotification, m as isRawFunctionToolOutputCompletionNotification, o as describeNotificationActivity, p as isRawAssistantProgressNotification, r as projectCodexThreadUsageUpdate, s as isAssistantCommentaryCompletionNotification, t as CodexResponseCompletionProjection, u as isFileChangePatchUpdatedNotification, v as isReasoningProgressNotification, w as shouldDisarmAssistantCompletionIdleWatch, x as readCodexNotificationItem, y as isRetryableErrorNotification } from "./event-projector-usage-B2r5vhWH.js";
7
- import { i as buildCodexPluginAppCacheKey, r as buildCodexAppServerRuntimeFingerprint, s as defaultCodexAppInventoryCache } from "./thread-resume-BFt1-ZDa.js";
8
- import { $ as resolveCodexContinuityProjectionMaxChars, B as isMessageOnlyCodexSourceReply, D as resolveCodexAppServerThreadModelSelection, F as codexDynamicToolsFingerprint, G as buildContextEngineBinding, I as codexLegacyDynamicToolsFingerprint, J as fitCodexProjectedContextForTurnStart, K as isContextEngineBindingCompatible, L as filterCodexDynamicTools, P as areCodexDynamicToolFingerprintsCompatible, Q as resolveCodexContextEngineProjectionReserveTokens, S as buildDeveloperInstructions, V as isSystemAgentOnlyCodexDynamicToolAllowlist, W as resolveCodexDynamicToolsLoadingForRuntime, X as projectContextEngineAssemblyForCodex, Y as neutralizeCodexExplicitMentionSigils, Z as resolveCodexContextEngineProjectionMaxChars, a as isCodexAppServerProfilerEnabled, at as buildPluginAppPolicyContext, c as buildCodexNativeHookRelayConfig, d as emitCodexNativePreToolUseFailureDiagnostic, et as buildCodexPluginAppsConfigPatchFromPolicyContext, f as resolveCodexNativeHookRelayEvents, i as startOrResumeThread, l as buildCodexNativeHookRelayDisabledConfig, m as scheduleCodexNativeHookRelayUnregister, n as buildTurnStartParams, nt as buildCodexPluginThreadConfigInputFingerprint, ot as mergeCodexThreadConfigs, p as resolveCodexNativeHookRelayTtlMs, pt as resolveRecoverableCodexPluginConfigKeys, q as buildCodexContinuityCalibration, r as buildCodexUserInput, rt as buildCodexPluginThreadConfigTimeoutFallback, s as CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS, st as shouldBuildCodexPluginThreadConfig, t as buildTurnCollaborationMode, tt as buildCodexPluginThreadConfig, u as createCodexNativeHookRelay, x as resolveCodexWebSearchPlan } from "./thread-lifecycle-CGGKP720.js";
9
- import { l as isIncognitoSessionKey, r as retainCodexAppServerBindingSubscription } from "./thread-ownership-RkaoZCmQ.js";
1
+ import { $t as isJsonObject, A as TURN_FINALIZE_DRAIN_ABORT_GRACE_MS, B as isCodexAppServerIndeterminateRequestCancellationError, Bt as resolveCodexAppServerAuthProfileIdForAgent, Ct as ensureCodexAppServerClientRuntime, Dt as protectCodexAppServerLiveThread, F as withCodexStartupTimeout, Ft as readRecentCodexRateLimits, H as isCodexAppServerOverloadError, Ht as resolveCodexAppServerFallbackApiKeyCacheKey, I as getCodexAppServerClientInstanceId, It as rememberCodexRateLimitsRead, K as resolveCodexAppServerClientInstanceId, L as isCodexAppServerApprovalRequest, M as isCodexAppServerStartupError, Mt as withAbortableTimeout, P as resolveCodexStartupTimeoutMs, Pt as readCodexRateLimitsRevision, Qt as flattenCodexDynamicToolFunctions, R as isCodexAppServerBrokenPipeError, Rt as resolveCodexAppServerAuthAccountCacheKey, Ut as resolveCodexAppServerPreparedApiKeyCacheKey, V as isCodexAppServerIndeterminateTransportError, W as isCodexAppServerRequestTimeoutError, Wt as resolveCodexAppServerPreparedAuthHandoff, _t as resolveCodexToolAbortTerminalReason, a as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, at as readCodexTurnCompletedNotification, c as getLeasedSharedCodexAppServerClient, ct as hasPendingDynamicToolTerminalDiagnostic, dt as resolveDynamicToolCallTimeoutMs, f as readCodexAppServerClientDesktopGenerationFingerprint, ft as resolveTerminalDynamicToolBatchAction, gt as toCodexDynamicToolProtocolResponse, h as releaseLeasedSharedCodexAppServerClient, ht as toCodexDynamicToolProgressResponse, i as clearSharedCodexAppServerClientIfCurrent, it as readCodexTurn, j as TURN_TERMINAL_SETTLEMENT_TIMEOUT_MS, k as CodexAppServerStartupError, l as getSharedCodexAppServerClient, lt as isDynamicToolTerminalDiagnosticEvent, mt as shouldReleaseTurnAfterTerminalDynamicTool, nt as readCodexDynamicToolCallParams, ot as createCodexElicitationResponse, pt as shouldBlockTerminalReleaseForNonTerminalDynamicToolResult, q as CodexAppServerRpcError, s as createIsolatedCodexAppServerClient, st as handleDynamicToolCallWithTimeout, tt as assertCodexTurnStartResponse, u as isCodexAppServerStartSelectionChangedError, ut as isMatchingDynamicToolTerminalDiagnostic, v as retainSharedCodexAppServerClientIfCurrent, y as retireSharedCodexAppServerClientIfCurrent, z as isCodexAppServerConnectionClosedError, zt as resolveCodexAppServerAuthProfileId } from "./shared-client-DxcfF9KX.js";
2
+ import { C as isCodexPairedNodeRemoteExecPlacementSandbox, D as resolveCodexPluginsPolicy, E as readCodexPluginConfig, F as resolveCodexAppServerHomeDir, O as resolveOpenClawExecPolicyForCodexAppServer, T as isCodexSandboxExecServerEnabled, d as shouldAutoApproveCodexAppServerApprovals, f as withMcpElicitationsApprovalPolicy, h as resolveCodexModelBackedReviewerPolicyContext, l as hasCodexMcpToolApprovalOverrides, m as canUseCodexModelBackedApprovalsReviewerForModel, o as resolveCodexComputerUseConfig, r as resolveCodexAppServerHomeScope, w as isCodexRemoteExecPlacementSandbox, y as readCodexRequirementsToml } from "./config-BhmfC1s6.js";
3
+ import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, l as resolveCodexRunSessionBindingAuthority, u as scopeCodexRunBindingStore } from "./session-binding-BHuTCRZQ.js";
4
+ import { B as readNonNegativeInteger, E as itemStatus, H as splitPlanText, I as readItem, L as readItemString, M as extractRawAssistantText, O as matchesCodexSnapshotTurn, P as readCodexErrorNotificationMessage, T as itemName, V as readNullableString, a as closeCodexStartupClientBestEffort, d as unsubscribeCodexThreadBestEffort, f as CODEX_APP_SERVER_NATIVE_TURN_WAIT_TIMEOUT_MS, g as readCodexNotificationThreadId, h as isCodexNotificationForTurn, j as shouldSynthesizeToolProgressForItem, k as shouldClearTerminalPresentationForNativeItem, l as retireUnsafeCodexTurnClientBestEffort, m as CodexProjectionDiagnostics, o as interruptCodexTurnAndWaitBestEffort, p as getCodexAppServerTurnRouter, r as CodexAppServerUnsafeSubscriptionError, u as terminateCodexBackgroundTerminals } from "./attempt-client-cleanup-DYHcRQAa.js";
5
+ import { a as codexExecutionToolName, c as isPendingOpenClawDynamicToolCompletionNotification, d as isTerminalTurnStatus, f as readCodexNotificationItem, h as updateActiveTurnItemIds, l as isRawFunctionToolOutputCompletionNotification, m as readRawResponseToolCallId, p as readNotificationItemId, r as projectCodexThreadUsageUpdate, s as isCodexTurnAbortMarkerNotification, t as CodexResponseCompletionProjection } from "./event-projector-usage-C5mcrGjp.js";
6
+ import { i as buildCodexPluginAppCacheKey, r as buildCodexAppServerRuntimeFingerprint, s as defaultCodexAppInventoryCache } from "./thread-resume-C7pyVKeW.js";
7
+ import { A as readCodexMcpToolConnectorId, B as emitCodexNativePreToolUseFailureDiagnostic, C as buildDeveloperInstructions, Ct as buildCodexPluginThreadConfig, D as resolveCodexAppServerThreadModelSelection, Dt as mergeCodexThreadConfigs, H as resolveCodexNativeHookRelayTtlMs, I as buildCodexNativeHookRelayConfig, L as buildCodexNativeHookRelayDisabledConfig, M as isCodexAppServerProfilerEnabled, Ot as shouldBuildCodexPluginThreadConfig, P as CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS, Pt as resolveRecoverableCodexPluginConfigKeys, S as resolveCodexWebSearchPlan, Tt as buildCodexPluginThreadConfigTimeoutFallback, U as scheduleCodexNativeHookRelayUnregister, V as resolveCodexNativeHookRelayEvents, X as loadCodexNativeToolCatalog, Y as hasCodexNativeToolCatalog, _t as neutralizeCodexExplicitMentionSigils, at as filterCodexDynamicTools, bt as resolveCodexContextEngineProjectionReserveTokens, c as assertScheduledCodexAppAuthorityRuntime, d as buildScheduledCodexAppServerConnectionIdentity, f as captureScheduledCodexAppAuthority, ft as resolveCodexDynamicToolsLoadingForRuntime, gt as fitCodexProjectedContextForTurnStart, h as resolveScheduledCodexAppCreatorCaptureDecision, ht as buildCodexContinuityCalibration, i as buildCodexUserInput, j as readCodexMcpToolUiVisibility, l as buildLegacyScheduledCodexAppRecoveryPrompt, lt as isSystemAgentOnlyCodexDynamicToolAllowlist, m as readCurrentCodexScheduledAppPolicy$1, n as buildTurnCollaborationMode, nt as codexLegacyDynamicToolsFingerprint, p as intersectCodexPluginThreadConfigWithScheduledAuthority, pt as buildContextEngineBinding, r as buildTurnStartParams, s as startOrResumeThread, tt as codexDynamicToolsFingerprint, u as buildScheduledCodexAppAuthorityInputFingerprint, vt as projectContextEngineAssemblyForCodex, wt as buildCodexPluginThreadConfigInputFingerprint, xt as resolveCodexContinuityProjectionMaxChars, yt as resolveCodexContextEngineProjectionMaxChars, z as createCodexNativeHookRelay } from "./thread-lifecycle-DAcEJ_Om.js";
8
+ import { l as isIncognitoSessionKey, r as retainCodexAppServerBindingSubscription } from "./thread-ownership-zGokd8i1.js";
9
+ import { a as buildCodexUserPromptMessage, i as mirrorPromptAtTurnStartBestEffort, l as hasCodexMirrorOrigin, n as createCodexAppServerUserMessagePersistenceNotifier, o as promptSnapshot, s as applyCodexTranscriptTaint, t as codexTranscriptMirrorRuntime } from "./transcript-mirror-DTUxFWa_.js";
10
+ import { n as CodexThreadPolicyHandoffError } from "./thread-policy-g4KbJPzp.js";
10
11
  import { r as readMirrorIdentity, t as attachCodexMirrorIdentity } from "./upstream-prompt-provenance-LphB8slG.js";
11
- import { a as buildCodexUserPromptMessage, i as mirrorPromptAtTurnStartBestEffort, l as hasCodexMirrorOrigin, n as createCodexAppServerUserMessagePersistenceNotifier, o as promptSnapshot, s as applyCodexTranscriptTaint, t as codexTranscriptMirrorRuntime } from "./transcript-mirror-1ep4XjzF.js";
12
- import { t as CODEX_CONTROL_METHODS } from "./capabilities-D6m_TdVy.js";
13
- import { a as shouldRefreshCodexRateLimitsForUsageLimitMessage, i as resolveCodexUsageLimitResetAtMs, n as formatCodexUsageLimitErrorMessage } from "./rate-limits-BqHEHXQJ.js";
14
- import { r as formatCodexDisplayText } from "./command-formatters--UToCokS.js";
15
- import { a as resolveCodexAppServerForModelProvider, i as resolveCodexSessionPermissionCwd, n as applyCodexSessionPermissionPolicy, r as resolveCodexEffectiveSessionPermissionPolicy } from "./session-permission-policy-ScnAyG90.js";
16
- import { n as resolveCodexBindingAppServerConnection } from "./binding-connection-CF7BmrDk.js";
12
+ import { t as CODEX_CONTROL_METHODS } from "./capabilities-dIjNVCYH.js";
13
+ import { a as shouldRefreshCodexRateLimitsForUsageLimitMessage, i as resolveCodexUsageLimitResetAtMs, n as formatCodexUsageLimitErrorMessage } from "./rate-limits-CawQJfUS.js";
14
+ import { a as formatCodexDisplayText } from "./command-formatters-CLHhEB1Q.js";
15
+ import { a as resolveCodexAppServerForModelProvider, i as resolveCodexSessionPermissionCwd, n as applyCodexSessionPermissionPolicy, r as resolveCodexEffectiveSessionPermissionPolicy } from "./session-permission-policy-DeWVTCMj.js";
16
+ import { n as resolveCodexBindingAppServerConnection } from "./binding-connection-E6uDsPlx.js";
17
17
  import { n as prepareCodexAppServerAuthBinding } from "./auth-binding-Ds_QVm-X.js";
18
- import { t as persistCodexContextCompactionActivity } from "./context-compaction-activity-BOeJf7yo.js";
19
- import { i as createAssistantMirrorMessage, n as createAssistantCommentaryMessage, r as createAssistantMessage, t as createAssistantAsyncMessage } from "./event-projector-assistant-message-C5-Ds8mA.js";
20
- import { a as CodexToolProgressProjection, c as isCodexCommandBearingToolCall, d as sanitizeCodexToolResponse, i as CodexToolTranscriptProjection, l as resolveCodexToolProgressDetailMode, n as CodexEventProjection, o as shouldEmitTranscriptToolProgress, r as emitCodexAgentEvent, s as inferCodexDynamicToolMeta, t as codexNativeSubagentMonitorRuntime, u as sanitizeCodexToolArguments } from "./native-subagent-monitor-vXYUZH37.js";
21
- import { n as readCodexMirroredSessionHistoryMessages } from "./session-history-C5J1AKzG.js";
22
- import { C as shouldRequireCodexSandboxExecServerEnvironment, D as CodexNativeToolLifecycleProjector, E as readBoundedCodexRemoteWorkspaceFile, S as shouldEnableCodexAppServerNativeToolSurface, _ as resolveCodexAppServerHookChannelId, a as emitDynamicToolStartedDiagnostic, c as resolveCodexProviderWebSearchSupport, d as releaseCodexSandboxExecServerEnvironment, f as buildDynamicTools, g as resolveCodexAppServerExecutionCwd, h as formatCodexDynamicToolBuildStageSummary, i as emitDynamicToolErrorDiagnostic, m as disableCodexPluginThreadConfig, n as projectCodexExecutableDynamicTools, o as emitDynamicToolTerminalDiagnostic, p as createCodexDynamicToolBuildStageTracker, r as routeCodexAppServerElicitationRequest, s as handleCodexAppServerApprovalRequest, t as createCodexDynamicToolBridge, u as ensureCodexSandboxExecServerEnvironment, v as resolveCodexExternalSandboxPolicyForOpenClawSandbox, w as shouldWarnCodexDynamicToolBuildStageSummary, x as resolveCodexSandboxEnvironmentSelection, y as resolveCodexMessageToolProvider } from "./dynamic-tools-BmeXjwNZ.js";
23
- import { n as captureCodexSettledTurnFinalizationContext } from "./settled-turn-context-Bk3IUwod.js";
24
- import { a as runCodexComputerUseLiveTest, n as ensureCodexComputerUse, t as defaultCodexPluginMetadataCache } from "./plugin-metadata-cache-DQQm4gWT.js";
25
- import { asBoolean, asFiniteNumber, asOptionalRecord, hasNonEmptyString, normalizeOptionalString, readNonBlankString, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
18
+ import { t as persistCodexContextCompactionActivity } from "./context-compaction-activity-CMla5FPJ.js";
19
+ import { i as createAssistantReasoningMessage, n as createAssistantCommentaryMessage, r as createAssistantMessage, t as createAssistantAsyncMessage } from "./event-projector-assistant-message-DHRFXKbS.js";
20
+ import { a as CodexToolProgressProjection, c as isCodexCommandBearingToolCall, d as sanitizeCodexToolResponse, i as CodexToolTranscriptProjection, l as resolveCodexToolProgressDetailMode, n as CodexEventProjection, o as shouldEmitTranscriptToolProgress, r as emitCodexAgentEvent, s as inferCodexDynamicToolMeta, t as codexNativeSubagentMonitorRuntime, u as sanitizeCodexToolArguments } from "./native-subagent-monitor-CnH147jH.js";
21
+ import { a as requestPluginApprovalOutcome } from "./plugin-approval-roundtrip-CNe-Xrjx.js";
22
+ import { S as shouldWarnCodexDynamicToolBuildStageSummary, T as CodexNativeToolLifecycleProjector, _ as resolveCodexMessageToolProvider, a as emitDynamicToolStartedDiagnostic, b as shouldEnableCodexAppServerNativeToolSurface, c as ensureCodexSandboxExecServerEnvironment, d as createCodexDynamicToolBuildStageTracker, f as disableCodexPluginThreadConfig, g as resolveCodexExternalSandboxPolicyForOpenClawSandbox, h as resolveCodexAppServerHookChannelId, i as emitDynamicToolErrorDiagnostic, l as releaseCodexSandboxExecServerEnvironment, m as resolveCodexAppServerExecutionCwd, n as projectCodexExecutableDynamicTools, o as emitDynamicToolTerminalDiagnostic, p as formatCodexDynamicToolBuildStageSummary, r as routeCodexAppServerElicitationRequest, s as handleCodexAppServerApprovalRequest, t as createCodexDynamicToolBridge, u as buildDynamicTools, w as readBoundedCodexRemoteWorkspaceFile, x as shouldRequireCodexSandboxExecServerEnvironment, y as resolveCodexSandboxEnvironmentSelection } from "./dynamic-tools-BU9tmlYm.js";
23
+ import { t as resolveCodexProviderWebSearchSupport } from "./provider-capabilities-DW5p4k9w.js";
24
+ import { a as getCodexWorkspaceMemoryToolNames, c as readContextEngineThreadBootstrapProjection, d as resolveCodexDeliveryHintPreservedInputRange, f as resolveContextEngineBootstrapProjectionDecision, i as buildCodexWorkspaceBootstrapContext, l as readMirroredSessionHistoryMessages, n as buildCodexSystemPromptReport, r as buildCodexWatchedSessionsContext, s as prependCodexOpenClawPromptContext, t as buildCodexOpenClawPromptContext, u as renderCodexSkillsCollaborationInstructions } from "./attempt-context-DmQUNWwT.js";
25
+ import { n as captureCodexSettledTurnFinalizationContext } from "./settled-turn-context-D3SNWtUn.js";
26
+ import { a as runCodexComputerUseLiveTest, n as ensureCodexComputerUse, t as defaultCodexPluginMetadataCache } from "./plugin-metadata-cache-BFrPhb2c.js";
27
+ import { asOptionalRecord, normalizeOptionalString, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
26
28
  import fs from "node:fs/promises";
27
29
  import path from "node:path";
28
30
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
29
- import { asDateTimestampMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
30
- import { AgentHarnessPreflightError, CODEX_APP_SERVER_CONTEXT_ENGINE_HOST, FAST_MODE_AUTO_PROGRESS_KIND, agentHarnessAttemptTerminal, agentHarnessStructuredInput, assembleHarnessContextEngine, assertContextEngineHostSupport, awaitAgentEndSideEffects, bootstrapHarnessContextEngine, buildBootstrapContextForFiles, buildEmbeddedForegroundPromptContext, buildHarnessContextEngineRuntimeContext, buildWatchedSessionsHarnessContext, callGatewayTool, cancelPendingAgentQuestionForSession, claimPendingAgentQuestionAnswer, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, detectAndLoadAgentHarnessPromptImages, embeddedAgentLog, emitAgentEvent, emptyAgentHarnessUserInputAnswers, formatErrorMessage, formatFastModeAutoProgressText, getAgentHarnessHookRunner, hasBeforeToolCallPolicy, isActiveHarnessContextEngine, isHostScopedAgentToolActive, loadCodexBundleMcpThreadConfig, materializeRequesterScopedMcpToolsForHarnessRun, prepareHarnessNativeMcpAppPreview, projectAgentHarnessTranscriptMessageForDisplay, resolveAgentDir, resolveAgentHarnessBeforePromptBuildResult, resolveAgentRunAbortLifecycleFields, resolveAttemptFsWorkspaceOnly, resolveBootstrapFilesForRun, resolveContextEngineOwnerPluginId, resolveFastModeForElapsed, resolveSandboxContext, resolveUserPath, runAgentCleanupStep, runAgentEndSideEffects, runAgentHarnessAfterCompactionHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
31
- import crypto, { createHash } from "node:crypto";
32
- import { markAuthProfileBlockedUntil, resolveAgentDir as resolveAgentDir$1, resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/agent-runtime";
31
+ import { MAX_TIMER_TIMEOUT_MS, asDateTimestampMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
32
+ import { AgentHarnessPreflightError, CODEX_APP_SERVER_CONTEXT_ENGINE_HOST, FAST_MODE_AUTO_PROGRESS_KIND, agentHarnessAttemptTerminal, agentHarnessStructuredInput, assembleHarnessContextEngine, assertContextEngineHostSupport, awaitAgentEndSideEffects, bootstrapHarnessContextEngine, buildAgentHookContextChannelFields, buildEmbeddedForegroundPromptContext, buildHarnessContextEngineRuntimeContext, callGatewayTool, cancelPendingAgentQuestionForSession, claimPendingAgentQuestionAnswer, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, detectAndLoadAgentHarnessPromptImages, embeddedAgentLog, emitAgentEvent, emptyAgentHarnessUserInputAnswers, formatErrorMessage, formatFastModeAutoProgressText, getAgentHarnessHookRunner, hasBeforeToolCallPolicy, isActiveHarnessContextEngine, isHostScopedAgentToolActive, loadCodexBundleMcpThreadConfig, materializeRequesterScopedMcpToolsForHarnessRun, prepareHarnessNativeMcpAppPreview, projectAgentHarnessTranscriptMessageForDisplay, resolveAgentDir, resolveAgentHarnessBeforePromptBuildResult, resolveAgentRunAbortLifecycleFields, resolveAttemptFsWorkspaceOnly, resolveContextEngineOwnerPluginId, resolveFastModeForElapsed, resolveSandboxContext, resolveUserPath, runAgentCleanupStep, runAgentEndSideEffects, runAgentHarnessAfterCompactionHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
33
+ import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
34
+ import { createHash } from "node:crypto";
35
+ import { markAuthProfileBlockedUntil, resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
33
36
  import { resolveAgentConfig as resolveAgentConfig$1, resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
34
37
  import { loadExecApprovals } from "openclaw/plugin-sdk/exec-approvals-runtime";
35
38
  import { parseSqliteSessionFileMarker } from "openclaw/plugin-sdk/session-store-runtime";
36
39
  import { addAbortListener } from "node:events";
37
40
  import { truncateUtf16Safe, truncateUtf8Prefix } from "openclaw/plugin-sdk/text-utility-runtime";
38
41
  import { createDiagnosticTraceContextFromActiveScope, emitTrustedDiagnosticEvent, emitTrustedDiagnosticEventWithPrivateData, freezeDiagnosticTraceContext, onInternalDiagnosticEvent, resolveDiagnosticModelContentCapturePolicy } from "openclaw/plugin-sdk/diagnostic-runtime";
39
- import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
40
- import { captureFinalCodexCronCreatorToolAllowlist, materializeStaticMcpToolsForScheduledHarnessRun, resolveCodexMcpToolOverridesForAgent, resolveCodexTtsProvenanceTransfer } from "openclaw/plugin-sdk/codex-mcp-projection";
42
+ import { captureFinalCodexCronCreatorToolAllowlist, formatMcpCodexApprovalRemedy, materializeStaticMcpToolsForHarnessRun, resolveCodexMcpToolOverridesForAgent, resolveCodexTtsProvenanceTransfer } from "openclaw/plugin-sdk/codex-mcp-projection";
41
43
  import { hasPromptImageInput } from "openclaw/plugin-sdk/session-transcript-runtime";
42
44
  import { Buffer as Buffer$1 } from "node:buffer";
43
45
  import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
44
- import { prepareMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";
45
46
  import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-local-roots";
46
47
  import { isSilentReplyPayloadText } from "openclaw/plugin-sdk/reply-chunking";
47
48
  import { generatedImageAssetFromBase64 } from "openclaw/plugin-sdk/image-generation";
48
49
  import { resolveGeneratedMediaMaxBytes } from "openclaw/plugin-sdk/media-generation-runtime";
49
50
  import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
50
51
  import { stripInvisibleUnicode } from "openclaw/plugin-sdk/web-content-extractor";
51
- import { MESSAGE_TOOL_DELIVERY_HINTS } from "openclaw/plugin-sdk/message-tool-delivery-hints";
52
52
  //#region extensions/codex/src/app-server/attempt-steering.ts
53
53
  /**
54
54
  * Debounced steering queue for forwarding user messages to an active Codex
@@ -265,9 +265,8 @@ var CodexAssistantProjection = class {
265
265
  this.assistantPhaseByItem = /* @__PURE__ */ new Map();
266
266
  this.assistantDeliveryByItem = /* @__PURE__ */ new Map();
267
267
  this.latestTerminalAssistantCandidateSuperseded = false;
268
- this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff = false;
269
268
  this.terminalAssistantCandidateEarlierActiveItemIds = /* @__PURE__ */ new Set();
270
- this.lastCommentaryProgressTextByItem = /* @__PURE__ */ new Map();
269
+ this.lastCommentaryProgressEventByItem = /* @__PURE__ */ new Map();
271
270
  this.lastAnswerCandidateEventByItem = /* @__PURE__ */ new Map();
272
271
  this.pendingRawCommentaryEchoes = 0;
273
272
  this.rawPromotedAssistantItemIds = /* @__PURE__ */ new Set();
@@ -278,27 +277,6 @@ var CodexAssistantProjection = class {
278
277
  this.persistableAssistantBarrierItemIds = /* @__PURE__ */ new Set();
279
278
  this.completedAssistantItemIds = /* @__PURE__ */ new Set();
280
279
  }
281
- hasCompletedTerminalAssistantText(completedItemIds) {
282
- const latestCompletedItemId = this.latestCompletedTerminalAssistantItemId;
283
- if (!latestCompletedItemId) return false;
284
- const finalItem = this.resolveFinalAssistantTextItem();
285
- return this.latestCompletedItemId === latestCompletedItemId && finalItem?.itemId === latestCompletedItemId && completedItemIds.has(latestCompletedItemId);
286
- }
287
- getLatestTerminalAssistantCandidate() {
288
- const itemId = this.latestTerminalAssistantCandidateItemId;
289
- if (!itemId) return;
290
- const text = this.assistantTextByItem.get(itemId)?.trim();
291
- return {
292
- itemId,
293
- hasText: Boolean(text && !this.isToolProgressEchoText(itemId, text))
294
- };
295
- }
296
- hasLatestTerminalAssistantCandidateText() {
297
- return !this.latestTerminalAssistantCandidateSuperseded && this.getLatestTerminalAssistantCandidate()?.hasText === true;
298
- }
299
- canReleaseLatestTerminalAssistantAfterToolHandoff() {
300
- return this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff && this.hasLatestTerminalAssistantCandidateText();
301
- }
302
280
  handleNotification(method, params) {
303
281
  if (method === "model/rerouted") this.responseModel = readStringField(params, "toModel") ?? this.responseModel;
304
282
  }
@@ -321,12 +299,13 @@ var CodexAssistantProjection = class {
321
299
  if (isCommentary) {
322
300
  this.emitCommentaryProgress({
323
301
  itemId,
324
- text
302
+ text,
303
+ phase: "update"
325
304
  });
326
305
  return;
327
306
  }
328
- if (this.isFinalAnswerAssistantItem(itemId)) this.emitAnswerCandidate(itemId, "candidate");
329
- const knownFinalAnswer = this.shouldStreamAssistantPartial(itemId);
307
+ const knownFinalAnswer = this.isFinalAnswerAssistantItem(itemId);
308
+ if (knownFinalAnswer) this.emitAnswerCandidate(itemId, "candidate");
330
309
  const replace = this.streamedPartialAssistantItemId !== void 0 && this.streamedPartialAssistantItemId !== itemId;
331
310
  if (replace && (!knownFinalAnswer || this.streamedPartialAssistantItemReplaceable)) this.streamedPartialAssistantItemReplaceable = true;
332
311
  else if (this.streamedPartialAssistantItemId === void 0) this.streamedPartialAssistantItemReplaceable = !knownFinalAnswer;
@@ -362,9 +341,7 @@ var CodexAssistantProjection = class {
362
341
  this.noteNativeWorkBarrier(item);
363
342
  this.rememberAssistantPhase(item);
364
343
  if (item?.type === "agentMessage" && itemId && !this.isNonTerminalAssistantItem(itemId) && itemId !== this.pendingRawTerminalAssistantEchoItemId) this.pendingRawTerminalAssistantEchoItemId = void 0;
365
- if (itemId && !this.isNonTerminalAssistantItem(itemId)) this.latestCompletedItemId = itemId;
366
344
  if (item?.type === "agentMessage" && !this.isNonTerminalAssistantItem(item.id)) {
367
- this.latestCompletedTerminalAssistantItemId = item.id;
368
345
  this.markLatestTerminalAssistantCandidate(item.id, activeItemIds);
369
346
  this.pendingRawTerminalAssistantEchoItemId = item.id;
370
347
  } else if (itemId && !this.isNonTerminalAssistantItem(itemId)) {
@@ -377,7 +354,8 @@ var CodexAssistantProjection = class {
377
354
  if (item.text && this.isCommentaryAssistantItem(item.id)) {
378
355
  this.emitCommentaryProgress({
379
356
  itemId: item.id,
380
- text: item.text
357
+ text: item.text,
358
+ phase: "end"
381
359
  });
382
360
  this.pendingRawCommentaryEchoes += 1;
383
361
  } else if (item.text && !this.isAsyncAssistantItem(item.id) && this.isFinalAnswerAssistantItem(item.id)) this.emitAnswerCandidate(item.id, "candidate");
@@ -401,10 +379,7 @@ var CodexAssistantProjection = class {
401
379
  const pendingTerminalAssistantEchoItemId = this.pendingRawTerminalAssistantEchoItemId;
402
380
  const isPendingTerminalAssistantEcho = role === "assistant" && phase !== "commentary" && pendingTerminalAssistantEchoItemId !== void 0 && (rawItemId === void 0 || rawItemId === pendingTerminalAssistantEchoItemId);
403
381
  if (pendingTerminalAssistantEchoItemId !== void 0 && !isPendingTerminalAssistantEcho) this.pendingRawTerminalAssistantEchoItemId = void 0;
404
- if (!isPendingTerminalAssistantEcho) {
405
- this.latestCompletedItemId = void 0;
406
- this.markTerminalAssistantCandidateSupersededBy(rawItemId);
407
- }
382
+ if (!isPendingTerminalAssistantEcho) this.markTerminalAssistantCandidateSupersededBy(rawItemId);
408
383
  if (role !== "assistant") return;
409
384
  if (phase === "commentary" && this.pendingRawCommentaryEchoes > 0) {
410
385
  this.pendingRawCommentaryEchoes -= 1;
@@ -431,9 +406,10 @@ var CodexAssistantProjection = class {
431
406
  this.rawPromotedAssistantItemIds.add(itemId);
432
407
  if (phase === "commentary") this.emitCommentaryProgress({
433
408
  itemId,
434
- text
409
+ text,
410
+ phase: "end"
435
411
  });
436
- else this.markLatestTerminalAssistantCandidate(itemId, activeItemIds, { canReleaseAfterToolHandoff: isIdlessTerminalAssistantAfterCompletedWork });
412
+ else this.markLatestTerminalAssistantCandidate(itemId, activeItemIds);
437
413
  }
438
414
  collectAssistantTexts() {
439
415
  const afterHandoff = this.collectPersistableAssistantTexts(this.persistableAssistantBarrier);
@@ -544,9 +520,6 @@ var CodexAssistantProjection = class {
544
520
  responseModel: this.responseModel
545
521
  } : message;
546
522
  }
547
- createAssistantMirrorMessage(title, text) {
548
- return createAssistantMirrorMessage(this.params, title, text);
549
- }
550
523
  rememberAssistantPhase(item) {
551
524
  if (item?.type !== "agentMessage") return;
552
525
  const phase = readItemString(item, "phase");
@@ -566,20 +539,21 @@ var CodexAssistantProjection = class {
566
539
  isFinalAnswerAssistantItem(itemId) {
567
540
  return this.assistantPhaseByItem.get(itemId) === "final_answer";
568
541
  }
569
- shouldStreamAssistantPartial(itemId) {
570
- return this.assistantPhaseByItem.get(itemId) === "final_answer";
571
- }
572
542
  emitCommentaryProgress(params) {
573
543
  const progressText = params.text.trim();
574
- if (!progressText || this.lastCommentaryProgressTextByItem.get(params.itemId) === progressText) return;
575
- this.lastCommentaryProgressTextByItem.set(params.itemId, progressText);
544
+ const previous = this.lastCommentaryProgressEventByItem.get(params.itemId);
545
+ if (!progressText || previous?.phase === params.phase && previous.text === progressText) return;
546
+ this.lastCommentaryProgressEventByItem.set(params.itemId, {
547
+ phase: params.phase,
548
+ text: progressText
549
+ });
576
550
  this.emitAgentEvent({
577
551
  stream: "item",
578
552
  data: {
579
553
  itemId: params.itemId,
580
554
  kind: "preamble",
581
555
  title: "Preamble",
582
- phase: "update",
556
+ phase: params.phase,
583
557
  progressText,
584
558
  source: "codex-app-server"
585
559
  }
@@ -592,9 +566,12 @@ var CodexAssistantProjection = class {
592
566
  this.supersedeVisibleAnswerCandidate();
593
567
  this.visibleAnswerCandidateItemId = itemId;
594
568
  }
595
- const signature = `${status}\0${text}`;
596
- if (this.lastAnswerCandidateEventByItem.get(itemId) === signature) return;
597
- this.lastAnswerCandidateEventByItem.set(itemId, signature);
569
+ const previous = this.lastAnswerCandidateEventByItem.get(itemId);
570
+ if (previous?.status === status && previous.text === text) return;
571
+ this.lastAnswerCandidateEventByItem.set(itemId, {
572
+ status,
573
+ text
574
+ });
598
575
  this.emitAgentEvent({
599
576
  stream: "item",
600
577
  data: {
@@ -615,10 +592,9 @@ var CodexAssistantProjection = class {
615
592
  this.emitAnswerCandidate(itemId, "superseded");
616
593
  this.visibleAnswerCandidateItemId = void 0;
617
594
  }
618
- markLatestTerminalAssistantCandidate(itemId, activeItemIds, options) {
595
+ markLatestTerminalAssistantCandidate(itemId, activeItemIds) {
619
596
  this.latestTerminalAssistantCandidateItemId = itemId;
620
597
  this.latestTerminalAssistantCandidateSuperseded = false;
621
- this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff = options?.canReleaseAfterToolHandoff === true;
622
598
  this.terminalAssistantCandidateEarlierActiveItemIds = new Set(activeItemIds);
623
599
  }
624
600
  markTerminalAssistantCandidateSupersededBy(itemId, options) {
@@ -628,7 +604,6 @@ var CodexAssistantProjection = class {
628
604
  return;
629
605
  }
630
606
  this.latestTerminalAssistantCandidateSuperseded = true;
631
- this.latestTerminalAssistantCandidateCanReleaseAfterToolHandoff = false;
632
607
  this.terminalAssistantCandidateEarlierActiveItemIds.clear();
633
608
  this.supersedeVisibleAnswerCandidate();
634
609
  }
@@ -1031,7 +1006,7 @@ const attemptTerminal = agentHarnessAttemptTerminal;
1031
1006
  //#region extensions/codex/src/app-server/event-projector-snapshot.ts
1032
1007
  function buildCodexMessagesSnapshot(params) {
1033
1008
  const messages = promptSnapshot(params.runParams, params.turnId, params.upstreamUserText);
1034
- if (params.reasoningText) messages.push(attachCodexMirrorIdentity(params.createAssistantMirrorMessage("Codex reasoning", params.reasoningText), `${params.turnId}:reasoning`));
1009
+ if (params.reasoningText) messages.push(attachCodexMirrorIdentity(createAssistantReasoningMessage(params.runParams, params.reasoningText), `${params.turnId}:reasoning`));
1035
1010
  const commentaryMessages = params.runParams.config?.ui?.prefs?.chatPersistCommentary === false ? [] : params.commentaryMessages.map(({ itemId, message }) => attachCodexMirrorIdentity(message, `${params.turnId}:commentary:${itemId}`));
1036
1011
  const asyncMessages = params.asyncMessages.map(({ itemId, message }) => attachCodexMirrorIdentity(message, `${params.turnId}:async:${itemId}`));
1037
1012
  const visibleWorkMessages = [
@@ -1066,8 +1041,7 @@ function buildCodexSteeringMessagesSnapshot(params) {
1066
1041
  commentaryMessages,
1067
1042
  assistantMessages,
1068
1043
  toolMessages: params.toolMessages,
1069
- lastAssistant: void 0,
1070
- createAssistantMirrorMessage: (title, text) => params.assistantProjection.createAssistantMirrorMessage(title, text)
1044
+ lastAssistant: void 0
1071
1045
  }).filter((message) => message.role !== "user"),
1072
1046
  assistantBoundaryItemId: assistantMessages.at(-1)?.itemId
1073
1047
  };
@@ -1115,8 +1089,7 @@ function buildCodexAttemptResult(input) {
1115
1089
  asyncMessages,
1116
1090
  commentaryMessages,
1117
1091
  toolMessages: input.toolTranscriptProjection.transcriptMessages,
1118
- lastAssistant,
1119
- createAssistantMirrorMessage: (title, text) => input.assistantProjection.createAssistantMirrorMessage(title, text)
1092
+ lastAssistant
1120
1093
  });
1121
1094
  const turnFailed = input.completedTurn?.status === "failed";
1122
1095
  const promptError = input.promptError ?? storedMissingToolResultError ?? (turnFailed ? input.completedTurn?.error?.message ?? "codex app-server turn failed" : null);
@@ -1264,6 +1237,10 @@ const CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS = 5e3;
1264
1237
  function createCodexUsageLimitPromptError(message) {
1265
1238
  return Object.assign(new Error(message), { status: 429 });
1266
1239
  }
1240
+ function resolveCodexPromptError(source) {
1241
+ const usageLimitMessage = formatCodexUsageLimitErrorMessage(source);
1242
+ return usageLimitMessage ? createCodexUsageLimitPromptError(usageLimitMessage) : source.message ?? void 0;
1243
+ }
1267
1244
  function isCodexUsageLimitPromptError(error) {
1268
1245
  return error instanceof Error && "status" in error && error.status === 429;
1269
1246
  }
@@ -1403,6 +1380,7 @@ var CodexAppServerEventProjector = class {
1403
1380
  this.activeCompactionItemIds = /* @__PURE__ */ new Set();
1404
1381
  this.terminalPresentationClearedItemIds = /* @__PURE__ */ new Set();
1405
1382
  this.nativeToolOutcomeOrdinals = /* @__PURE__ */ new Map();
1383
+ this.projectionClosed = false;
1406
1384
  this.settledTurnFailureFinalizationAllowed = false;
1407
1385
  this.promptErrorSource = null;
1408
1386
  this.synthesizedMissingToolResultError = null;
@@ -1435,6 +1413,13 @@ var CodexAppServerEventProjector = class {
1435
1413
  getCompletedTurnStatus() {
1436
1414
  return this.completedTurn?.status;
1437
1415
  }
1416
+ getActiveMcpToolCall(serverName) {
1417
+ if (this.projectionClosed || this.aborted) return;
1418
+ return this.nativeToolLifecycleProjector.getActiveMcpToolCall(serverName);
1419
+ }
1420
+ recordMcpToolCallReceipt(notification) {
1421
+ if (!this.projectionClosed) this.nativeToolLifecycleProjector.recordMcpToolCallReceipt(notification);
1422
+ }
1438
1423
  buildSteeringTranscriptPrefix() {
1439
1424
  const snapshot = buildCodexSteeringMessagesSnapshot({
1440
1425
  runParams: this.params,
@@ -1454,25 +1439,10 @@ var CodexAppServerEventProjector = class {
1454
1439
  this.pendingSteeringAssistantBoundaryItemId = void 0;
1455
1440
  }
1456
1441
  }
1457
- hasCompletedTerminalAssistantText() {
1458
- return this.assistantProjection.hasCompletedTerminalAssistantText(this.completedItemIds);
1459
- }
1460
- getLatestTerminalAssistantCandidate() {
1461
- return this.assistantProjection.getLatestTerminalAssistantCandidate();
1462
- }
1463
- hasLatestTerminalAssistantCandidateText() {
1464
- return this.assistantProjection.hasLatestTerminalAssistantCandidateText();
1465
- }
1466
- canReleaseLatestTerminalAssistantAfterToolHandoff() {
1467
- return this.assistantProjection.canReleaseLatestTerminalAssistantAfterToolHandoff();
1468
- }
1469
- /** Restores a completed final item after only the enclosing turn timeout fired. */
1470
- recoverCompletedTerminalAssistantAfterTurnWatchTimeout() {
1471
- if (!this.aborted || this.promptError !== "codex app-server attempt timed out" || !this.hasCompletedTerminalAssistantText()) return false;
1472
- this.aborted = false;
1473
- this.promptError = void 0;
1474
- this.promptErrorSource = null;
1475
- return true;
1442
+ /** Fence delayed projections before the turn's final snapshot leaves its owner. */
1443
+ closeProjection() {
1444
+ this.projectionClosed = true;
1445
+ return this.transcriptCheckpoint.flush(true);
1476
1446
  }
1477
1447
  /** Resolves the shared model-order position for a native tool item. */
1478
1448
  recordNativeToolOutcome(item) {
@@ -1488,6 +1458,7 @@ var CodexAppServerEventProjector = class {
1488
1458
  this.nativeToolLifecycleProjector.recordPreToolUseFailure(failure);
1489
1459
  }
1490
1460
  async handleNotification(notification) {
1461
+ if (this.projectionClosed) return;
1491
1462
  const params = isJsonObject(notification.params) ? notification.params : void 0;
1492
1463
  if (!params) return;
1493
1464
  if (notification.method === "hook/started" || notification.method === "hook/completed") {
@@ -1573,7 +1544,11 @@ var CodexAppServerEventProjector = class {
1573
1544
  const codexErrorInfo = isJsonObject(params.error) ? params.error.codexErrorInfo : void 0;
1574
1545
  const compactionFailure = codexErrorInfo === "other" && this.isCompacting();
1575
1546
  this.settledTurnFailureFinalizationAllowed = codexErrorInfo === "serverOverloaded" || compactionFailure;
1576
- this.promptError = this.formatCodexErrorMessage(params) ?? "codex app-server error";
1547
+ this.promptError = resolveCodexPromptError({
1548
+ message: readCodexErrorNotificationMessage(params),
1549
+ codexErrorInfo,
1550
+ rateLimits: this.options.readRecentRateLimits?.()
1551
+ }) ?? "codex app-server error";
1577
1552
  this.promptErrorSource = compactionFailure ? "compaction" : "prompt";
1578
1553
  break;
1579
1554
  }
@@ -1662,16 +1637,7 @@ var CodexAppServerEventProjector = class {
1662
1637
  await runAgentHarnessBeforeCompactionHook({
1663
1638
  sessionFile: this.params.sessionFile,
1664
1639
  messages: await this.toolTranscriptProjection.readMirroredSessionMessages(),
1665
- ctx: {
1666
- runId: this.params.runId,
1667
- agentId: this.params.agentId,
1668
- sessionKey: this.params.sessionKey,
1669
- sessionId: this.params.sessionId,
1670
- workspaceDir: this.params.workspaceDir,
1671
- messageProvider: this.params.messageProvider ?? void 0,
1672
- trigger: this.params.trigger,
1673
- channelId: this.params.messageChannel ?? this.params.messageProvider ?? void 0
1674
- }
1640
+ ctx: this.options.agentHookContext ?? {}
1675
1641
  });
1676
1642
  this.emitAgentEvent({
1677
1643
  stream: "compaction",
@@ -1693,6 +1659,7 @@ var CodexAppServerEventProjector = class {
1693
1659
  phase: "start",
1694
1660
  item
1695
1661
  });
1662
+ if (this.projectionClosed) return;
1696
1663
  this.toolTranscriptProjection.recordNativeToolCall(item);
1697
1664
  this.toolProgressProjection.emitToolResultSummary(item);
1698
1665
  this.emitAgentEvent({
@@ -1717,27 +1684,22 @@ var CodexAppServerEventProjector = class {
1717
1684
  if (!this.asyncDeliveryProjection.allows(item, true)) return;
1718
1685
  const asyncMessage = this.assistantProjection.recordItemCompleted(item, itemId, this.activeItemIds);
1719
1686
  if (asyncMessage) await this.asyncDeliveryProjection.deliver(asyncMessage);
1687
+ if (this.projectionClosed) return;
1720
1688
  this.reasoningProjection.recordItem(item);
1721
1689
  await this.generatedMediaProjection.recordNative(item);
1690
+ if (this.projectionClosed) return;
1722
1691
  if (item?.type === "contextCompaction" && itemId) {
1723
1692
  this.activeCompactionItemIds.delete(itemId);
1724
1693
  this.completedCompactionCount += 1;
1725
1694
  await this.options.onContextCompacted?.();
1695
+ if (this.projectionClosed) return;
1726
1696
  await runAgentHarnessAfterCompactionHook({
1727
1697
  sessionFile: this.params.sessionFile,
1728
1698
  messages: await this.toolTranscriptProjection.readMirroredSessionMessages(),
1729
1699
  compactedCount: -1,
1730
- ctx: {
1731
- runId: this.params.runId,
1732
- agentId: this.params.agentId,
1733
- sessionKey: this.params.sessionKey,
1734
- sessionId: this.params.sessionId,
1735
- workspaceDir: this.params.workspaceDir,
1736
- messageProvider: this.params.messageProvider ?? void 0,
1737
- trigger: this.params.trigger,
1738
- channelId: this.params.messageChannel ?? this.params.messageProvider ?? void 0
1739
- }
1700
+ ctx: this.options.agentHookContext ?? {}
1740
1701
  });
1702
+ if (this.projectionClosed) return;
1741
1703
  await persistCodexContextCompactionActivity({
1742
1704
  sessionTarget: this.params.sessionTarget,
1743
1705
  config: this.params.config,
@@ -1760,8 +1722,10 @@ var CodexAppServerEventProjector = class {
1760
1722
  phase: "result",
1761
1723
  item
1762
1724
  });
1725
+ if (this.projectionClosed) return;
1763
1726
  this.toolTranscriptProjection.recordNativeToolCall(item);
1764
1727
  await this.toolTranscriptProjection.recordNativeToolResultWithDetails(item);
1728
+ if (this.projectionClosed) return;
1765
1729
  this.toolProgressProjection.emitToolResultSummary(item);
1766
1730
  this.toolProgressProjection.emitToolResultOutput(item);
1767
1731
  this.emitAgentEvent({
@@ -1781,12 +1745,11 @@ var CodexAppServerEventProjector = class {
1781
1745
  this.settledTurnFailureFinalizationAllowed = turn.status === "failed" && (turn.error?.codexErrorInfo === "serverOverloaded" || compactionFailure);
1782
1746
  if (turn.status !== "completed") this.responseCompletions.clear();
1783
1747
  if (turn.status === "failed") {
1784
- const usageLimitMessage = formatCodexUsageLimitErrorMessage({
1748
+ this.promptError = resolveCodexPromptError({
1785
1749
  message: turn.error?.message,
1786
1750
  codexErrorInfo: turn.error?.codexErrorInfo,
1787
1751
  rateLimits: this.options.readRecentRateLimits?.()
1788
- });
1789
- this.promptError = usageLimitMessage ? createCodexUsageLimitPromptError(usageLimitMessage) : turn.error?.message ?? "codex app-server turn failed";
1752
+ }) ?? "codex app-server turn failed";
1790
1753
  this.promptErrorSource = compactionFailure ? "compaction" : "prompt";
1791
1754
  }
1792
1755
  if (compactionFailure) {
@@ -1799,39 +1762,40 @@ var CodexAppServerEventProjector = class {
1799
1762
  }
1800
1763
  const turnItems = turn.items ?? [];
1801
1764
  const unsettledAsyncDeliveries = this.asyncDeliveryProjection.pending();
1802
- for (let index = turnItems.length - 1; index >= 0; index -= 1) {
1803
- const item = turnItems[index];
1804
- if (!item || !this.isCurrentTurnSnapshotItem(item)) continue;
1805
- if (item?.type === "dynamicToolCall") break;
1806
- if (shouldClearTerminalPresentationForNativeItem(item)) {
1807
- this.clearTerminalPresentationForNativeItem(item);
1808
- break;
1809
- }
1810
- }
1765
+ const lastToolItem = turnItems.findLast((item) => matchesCodexSnapshotTurn(item, this.turnId) && (item.type === "dynamicToolCall" || shouldClearTerminalPresentationForNativeItem(item)));
1766
+ if (lastToolItem?.type !== "dynamicToolCall") this.clearTerminalPresentationForNativeItem(lastToolItem);
1811
1767
  for (const item of turnItems) {
1812
1768
  if (!this.asyncDeliveryProjection.allows(item)) continue;
1813
1769
  this.diagnostics.warnUnknownItemStatus(item);
1814
1770
  const asyncMessage = this.assistantProjection.recordSnapshotItem(item);
1815
1771
  if (asyncMessage) await this.asyncDeliveryProjection.deliver(asyncMessage);
1772
+ if (this.projectionClosed) return;
1816
1773
  this.reasoningProjection.recordItem(item);
1817
1774
  await this.generatedMediaProjection.recordNative(item);
1775
+ if (this.projectionClosed) return;
1818
1776
  this.toolProgressProjection.recordToolMeta(item);
1819
1777
  this.toolProgressProjection.rememberCommandAggregateOutputEcho(item);
1820
1778
  await this.emitSnapshotOnlyNativeToolProgress(item);
1779
+ if (this.projectionClosed) return;
1821
1780
  this.toolTranscriptProjection.recordNativeToolCall(item);
1822
1781
  await this.toolTranscriptProjection.recordNativeToolResultWithDetails(item);
1782
+ if (this.projectionClosed) return;
1823
1783
  this.toolTranscriptProjection.emitAfterToolCallObservation(item);
1824
1784
  this.toolProgressProjection.emitToolResultSummary(item);
1825
1785
  this.toolProgressProjection.emitToolResultOutput(item);
1826
1786
  }
1827
1787
  this.toolProgressProjection.approvalTimeoutKinds.clear();
1828
- for (const delivery of unsettledAsyncDeliveries) await this.asyncDeliveryProjection.deliver(delivery);
1788
+ for (const delivery of unsettledAsyncDeliveries) {
1789
+ if (this.projectionClosed) return;
1790
+ await this.asyncDeliveryProjection.deliver(delivery);
1791
+ }
1792
+ if (this.projectionClosed) return;
1829
1793
  this.assistantProjection.finalizeAnswerCandidate(turn);
1830
1794
  this.activeCompactionItemIds.clear();
1831
1795
  await this.reasoningProjection.maybeEndReasoning();
1832
1796
  }
1833
1797
  async emitSnapshotOnlyNativeToolProgress(item) {
1834
- if (!shouldSynthesizeToolProgressForItem(item) || !this.isCurrentTurnSnapshotItem(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
1798
+ if (!shouldSynthesizeToolProgressForItem(item) || !matchesCodexSnapshotTurn(item, this.turnId) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
1835
1799
  if (!this.activeItemIds.has(item.id)) {
1836
1800
  this.eventProjection.emitStandardItemEvent({
1837
1801
  phase: "start",
@@ -1842,6 +1806,7 @@ var CodexAppServerEventProjector = class {
1842
1806
  item
1843
1807
  });
1844
1808
  }
1809
+ if (this.projectionClosed) return;
1845
1810
  this.activeItemIds.delete(item.id);
1846
1811
  this.eventProjection.emitStandardItemEvent({
1847
1812
  phase: "end",
@@ -1853,10 +1818,6 @@ var CodexAppServerEventProjector = class {
1853
1818
  });
1854
1819
  this.completedItemIds.add(item.id);
1855
1820
  }
1856
- isCurrentTurnSnapshotItem(item) {
1857
- const itemTurnId = readItemString(item, "turnId");
1858
- return itemTurnId === void 0 || itemTurnId === this.turnId;
1859
- }
1860
1821
  async handleRawResponseItemCompleted(params) {
1861
1822
  const item = isJsonObject(params.item) ? params.item : void 0;
1862
1823
  if (!item) return;
@@ -1877,17 +1838,8 @@ var CodexAppServerEventProjector = class {
1877
1838
  presentationOnly: true
1878
1839
  });
1879
1840
  }
1880
- formatCodexErrorMessage(params) {
1881
- const error = isJsonObject(params.error) ? params.error : void 0;
1882
- const usageLimitMessage = formatCodexUsageLimitErrorMessage({
1883
- message: error ? readStringField(error, "message") : void 0,
1884
- codexErrorInfo: error?.codexErrorInfo,
1885
- rateLimits: this.options.readRecentRateLimits?.()
1886
- });
1887
- return usageLimitMessage ? createCodexUsageLimitPromptError(usageLimitMessage) : readCodexErrorNotificationMessage(params);
1888
- }
1889
1841
  emitAgentEvent(event) {
1890
- emitCodexAgentEvent(this.params, event);
1842
+ if (!this.projectionClosed) emitCodexAgentEvent(this.params, event);
1891
1843
  }
1892
1844
  isHookNotificationForCurrentThread(params) {
1893
1845
  const threadId = readStringField(params, "threadId");
@@ -1896,22 +1848,8 @@ var CodexAppServerEventProjector = class {
1896
1848
  }
1897
1849
  };
1898
1850
  //#endregion
1899
- //#region extensions/codex/src/app-server/mcp-tool-metadata.ts
1900
- /** Hosted app ownership is authoritative only on metadata supplied by Codex. */
1901
- function readCodexMcpToolConnectorId(tool) {
1902
- const metadata = asOptionalRecord(asOptionalRecord(tool)?.["_meta"]);
1903
- return normalizeOptionalString(metadata?.connector_id) ?? normalizeOptionalString(metadata?.connectorId);
1904
- }
1905
- /** Preserve MCP App visibility so model-only tools cannot become widget authority. */
1906
- function readCodexMcpToolUiVisibility(tool) {
1907
- const metadata = asOptionalRecord(asOptionalRecord(tool)?.["_meta"]);
1908
- const visibility = asOptionalRecord(metadata?.ui)?.visibility;
1909
- if (!Array.isArray(visibility)) return;
1910
- return [...new Set(visibility.filter((value) => value === "app" || value === "model"))].toSorted();
1911
- }
1912
- //#endregion
1913
1851
  //#region extensions/codex/src/app-server/native-mcp-app.ts
1914
- const CODEX_APPS_MCP_SERVER$1 = "codex_apps";
1852
+ const CODEX_APPS_MCP_SERVER = "codex_apps";
1915
1853
  function readMcpAppResourceUri(item) {
1916
1854
  const appContext = asOptionalRecord(item.appContext);
1917
1855
  const uri = normalizeOptionalString(appContext?.resourceUri) ?? normalizeOptionalString(item.mcpAppResourceUri);
@@ -1995,7 +1933,7 @@ function createNativeMcpRuntime(params) {
1995
1933
  return { tools: status ? statusTools(status) : [] };
1996
1934
  },
1997
1935
  readResource: async (serverName, uri) => {
1998
- const isCodexAppsServer = serverName === CODEX_APPS_MCP_SERVER$1;
1936
+ const isCodexAppsServer = serverName === CODEX_APPS_MCP_SERVER;
1999
1937
  const response = await params.client.request("mcpServer/resource/read", {
2000
1938
  threadId: params.threadId,
2001
1939
  ...isCodexAppsServer ? { originCallId: params.originCallId } : {},
@@ -2025,7 +1963,7 @@ function createCodexNativeMcpAppResultDetailsPreparer(params) {
2025
1963
  const connectorId = readMcpAppConnectorId(item);
2026
1964
  const toolResult = readMcpToolResult(item);
2027
1965
  if (!serverName || !toolName || !uiResourceUri || !toolResult) return;
2028
- if (serverName === CODEX_APPS_MCP_SERVER$1 && !connectorId) return;
1966
+ if (serverName === CODEX_APPS_MCP_SERVER && !connectorId) return;
2029
1967
  const runtime = createNativeMcpRuntime({
2030
1968
  ...params,
2031
1969
  originCallId: item.id,
@@ -2034,7 +1972,7 @@ function createCodexNativeMcpAppResultDetailsPreparer(params) {
2034
1972
  const tools = (await runtime.listTools?.(serverName))?.tools ?? [];
2035
1973
  const allowedAppToolNames = new Set(tools.filter((tool) => {
2036
1974
  const uiVisibility = readCodexMcpToolUiVisibility(tool);
2037
- return (uiVisibility === void 0 || uiVisibility.includes("app")) && (serverName !== CODEX_APPS_MCP_SERVER$1 || readCodexMcpToolConnectorId(tool) === connectorId);
1975
+ return (uiVisibility === void 0 || uiVisibility.includes("app")) && (serverName !== CODEX_APPS_MCP_SERVER || readCodexMcpToolConnectorId(tool) === connectorId);
2038
1976
  }).map((tool) => tool.name));
2039
1977
  if (!allowedAppToolNames.has(toolName)) return;
2040
1978
  return await prepareHarnessNativeMcpAppPreview({
@@ -2401,14 +2339,14 @@ function declineElicitation(message) {
2401
2339
  }
2402
2340
  //#endregion
2403
2341
  //#region extensions/codex/src/app-server/run-attempt-active-turn.ts
2404
- async function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notifications, turn) {
2342
+ function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notifications, turn) {
2405
2343
  const { prompt, state: resourceState, projectorRef, trajectoryRecorder, pendingNativePreToolUseFailures } = resources;
2406
2344
  const { context, turnState } = prompt;
2407
- const { runtime, attemptTools } = context;
2345
+ const { runtime, attemptTools, hookContext } = context;
2408
2346
  const { connection } = runtime;
2409
- const { params, runAbortController, terminalState, abortExplicitly, abortFromUpstream, bindingStore, bindingIdentity, sessionAgentId, contextSessionKey, effectiveCwd } = connection;
2347
+ const { params, runAbortController, terminalState, abortExplicitly, abortFromUpstream, sessionAgentId, contextSessionKey, effectiveCwd } = connection;
2410
2348
  const { dynamicToolParams, compactionPlanState, computerContextEpoch, toolBridge } = attemptTools;
2411
- const { state, userInputBridgeRef, steeringQueueRef, turnWatches, completeTurn, interruptTurn } = turnRuntime;
2349
+ const { state, completion, userInputBridgeRef, steeringQueueRef, deadlines, noteProgress, completeTurn, interruptTurn } = turnRuntime;
2412
2350
  const { emitExecutionPhaseOnce, emitLifecycleStart, maybeAnnounceFastModeAutoOff } = lifecycle;
2413
2351
  const { enqueueNotification } = notifications;
2414
2352
  const activeTurnId = turn.turn.id;
@@ -2446,6 +2384,7 @@ async function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notif
2446
2384
  return dynamicToolParams.onAgentEvent?.(event);
2447
2385
  }
2448
2386
  }, resourceState.thread.threadId, activeTurnId, {
2387
+ agentHookContext: hookContext,
2449
2388
  initialContextTokens: connection.mutable.startupContextTokens,
2450
2389
  nativePostToolUseRelayEnabled: resourceState.nativeHookRelay?.allowedEvents.includes("post_tool_use") === true && resourceState.nativeHookRelay.shouldRelayEvent("post_tool_use"),
2451
2390
  asyncUserMessageAllowed: params.disableTools !== true && (params.toolsAllow === void 0 || toolBridge.availableTools.some((tool) => tool.name === "message")),
@@ -2507,13 +2446,27 @@ async function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notif
2507
2446
  }
2508
2447
  }
2509
2448
  });
2510
- if (isTerminalTurnStatus(turn.turn.status)) state.terminalTurnNotificationQueued = true;
2449
+ if (isTerminalTurnStatus(turn.turn.status)) {
2450
+ state.terminalTurnNotificationQueued = true;
2451
+ deadlines.beginSettlement(Date.now());
2452
+ }
2511
2453
  emitLifecycleStart();
2512
2454
  const activeProjector = projectorRef.current;
2513
- turnWatches.armTerminalIdleWatch();
2514
- turnWatches.touchActivity("turn:start", { arm: true });
2515
- turnWatches.armAttemptIdleWatch();
2516
- turnWatches.touchActivity("turn:start", { attemptProgress: true });
2455
+ noteProgress("turn:start");
2456
+ const abortListener = () => {
2457
+ state.abortCleanup = interruptTurn(activeTurnId).then(async (confirmed) => {
2458
+ if (!terminalState.explicitCancellationObserved && !state.permissionChangeRestart && !state.timeout) return;
2459
+ if (!confirmed) throw new Error(state.permissionChangeRestart ? "Permission change could not confirm the previous Codex turn stopped." : "Codex cancellation could not confirm the turn stopped; background terminals may still be running.");
2460
+ await terminateCodexBackgroundTerminals(resourceState.client, resourceState.thread.threadId);
2461
+ if (state.permissionChangeRestart) state.permissionChangeRestart = "confirmed";
2462
+ });
2463
+ state.abortCleanup.then(completeTurn, (error) => {
2464
+ embeddedAgentLog.warn("codex app-server cancellation cleanup failed", { error });
2465
+ completeTurn();
2466
+ });
2467
+ };
2468
+ runAbortController.signal.addEventListener("abort", abortListener, { once: true });
2469
+ if (runAbortController.signal.aborted) abortListener();
2517
2470
  for (const failure of pendingNativePreToolUseFailures.splice(0)) activeProjector.recordNativeToolPreToolUseFailure(failure);
2518
2471
  const notifyUserMessagePersisted = createCodexAppServerUserMessagePersistenceNotifier(params);
2519
2472
  const promptMirrorPromise = mirrorPromptAtTurnStartBestEffort({
@@ -2526,35 +2479,40 @@ async function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notif
2526
2479
  turnId: activeTurnId,
2527
2480
  upstreamUserText: turnState.codexTurnPromptText
2528
2481
  });
2529
- await promptMirrorPromise;
2530
- if (resourceState.turnRoute) try {
2531
- await resourceState.turnRoute.bindTurn(activeTurnId);
2532
- } catch (error) {
2533
- if (!state.terminalTurnNotificationQueued) throw error;
2534
- await resourceState.turnRoute.drain();
2535
- if (!state.completed) {
2536
- turnWatches.clearAllTimers();
2537
- throw error;
2538
- }
2539
- }
2540
- if (!state.completed && isTerminalTurnStatus(turn.turn.status)) {
2541
- if (!isJsonObject(turn.turn)) throw new Error("Codex turn completion payload is not a JSON object");
2542
- await enqueueNotification({
2543
- method: "turn/completed",
2544
- params: {
2545
- threadId: resourceState.thread.threadId,
2546
- turnId: activeTurnId,
2547
- turn: turn.turn
2482
+ const bindProjection = async () => {
2483
+ state.activeLocalProjections += 1;
2484
+ try {
2485
+ await Promise.race([promptMirrorPromise, completion]);
2486
+ if (state.completed) return;
2487
+ if (resourceState.turnRoute) try {
2488
+ await Promise.race([resourceState.turnRoute.bindTurn(activeTurnId), completion]);
2489
+ } catch (error) {
2490
+ if (!state.terminalTurnNotificationQueued && !runAbortController.signal.aborted) throw error;
2491
+ await Promise.race([resourceState.turnRoute.drain(), completion]);
2492
+ if (!state.completed) throw error;
2548
2493
  }
2549
- }, {
2550
- threadId: resourceState.thread.threadId,
2551
- turnId: activeTurnId
2552
- });
2553
- }
2494
+ if (!state.completed && isTerminalTurnStatus(turn.turn.status)) {
2495
+ if (!isJsonObject(turn.turn)) throw new Error("Codex turn completion payload is not a JSON object");
2496
+ await enqueueNotification({
2497
+ method: "turn/completed",
2498
+ params: {
2499
+ threadId: resourceState.thread.threadId,
2500
+ turnId: activeTurnId,
2501
+ turn: turn.turn
2502
+ }
2503
+ }, {
2504
+ threadId: resourceState.thread.threadId,
2505
+ turnId: activeTurnId
2506
+ });
2507
+ }
2508
+ } finally {
2509
+ state.activeLocalProjections -= 1;
2510
+ }
2511
+ };
2554
2512
  const assertSteeringActive = () => {
2555
2513
  params.hostCapabilities.assertActive();
2556
2514
  runAbortController.signal.throwIfAborted();
2557
- if (state.completed || state.terminalTurnNotificationQueued || state.timedOut) throw new Error("codex app-server turn is no longer accepting steering");
2515
+ if (state.completed || state.terminalTurnNotificationQueued) throw new Error("codex app-server turn is no longer accepting steering");
2558
2516
  };
2559
2517
  const workspaceOnly = resolveAttemptFsWorkspaceOnly({
2560
2518
  config: params.config,
@@ -2675,12 +2633,13 @@ async function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notif
2675
2633
  cancelPendingUserInput,
2676
2634
  queueMessage,
2677
2635
  messageInjection: {
2678
- isAvailable: () => !state.completed && !state.terminalTurnNotificationQueued && !state.timedOut && !runAbortController.signal.aborted,
2636
+ isAvailable: () => !state.completed && !state.terminalTurnNotificationQueued && !runAbortController.signal.aborted,
2679
2637
  queueMessage
2680
2638
  },
2681
2639
  isStreaming: () => !state.completed && !runAbortController.signal.aborted,
2682
2640
  isAborted: () => runAbortController.signal.aborted,
2683
- isStopped: () => state.completed || state.timedOut || runAbortController.signal.aborted,
2641
+ isStopped: () => state.completed || runAbortController.signal.aborted,
2642
+ ownsLiveness: () => deadlines.ownsExecutionWait() && resourceState.turnRoute?.signal.aborted === false && !state.completed && !state.terminalTurnNotificationQueued && state.activeAppServerTurnRequests === 0 && state.activeLocalProjections === 0,
2684
2643
  isAbortable: () => !terminalState.terminalOutcomeFrozen || terminalState.sharedAbortAllowedAfterTerminalOutcome,
2685
2644
  isCompacting: () => projectorRef.current?.isCompacting() ?? false,
2686
2645
  supportsTranscriptCommitWait: true,
@@ -2697,36 +2656,7 @@ async function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notif
2697
2656
  terminalState.terminalOutcomeFrozen = true;
2698
2657
  params.abortSignal?.removeEventListener("abort", abortFromUpstream);
2699
2658
  };
2700
- const abortListener = () => {
2701
- if (state.timedOut) {
2702
- (async () => {
2703
- if (resourceState.thread.connectionScope !== "supervision") await bindingStore.mutate(bindingIdentity, {
2704
- kind: "clear",
2705
- threadId: resourceState.thread.threadId
2706
- });
2707
- await retireCodexAppServerClientAfterTimedOutTurn(resourceState.client, {
2708
- threadId: resourceState.thread.threadId,
2709
- turnId: activeTurnId,
2710
- reason: String(runAbortController.signal.reason ?? "timeout"),
2711
- suspectPhysicalClient: state.turnWatchTimeoutKind === "terminal"
2712
- });
2713
- })().finally(completeTurn);
2714
- return;
2715
- }
2716
- state.abortCleanup = interruptTurn(activeTurnId).then(async (confirmed) => {
2717
- if (!terminalState.explicitCancellationObserved && !state.permissionChangeRestart) return;
2718
- if (!confirmed) throw new Error(state.permissionChangeRestart ? "Permission change could not confirm the previous Codex turn stopped." : "Codex cancellation could not confirm the turn stopped; background terminals may still be running.");
2719
- await terminateCodexBackgroundTerminals(resourceState.client, resourceState.thread.threadId);
2720
- if (state.permissionChangeRestart) state.permissionChangeRestart = "confirmed";
2721
- });
2722
- state.abortCleanup.then(completeTurn, (error) => {
2723
- embeddedAgentLog.warn("codex app-server cancellation cleanup failed", { error });
2724
- completeTurn();
2725
- });
2726
- };
2727
- runAbortController.signal.addEventListener("abort", abortListener, { once: true });
2728
- if (runAbortController.signal.aborted) abortListener();
2729
- else if (params.permissionChange && !params.permissionChange.applied()) {
2659
+ if (!runAbortController.signal.aborted && params.permissionChange && !params.permissionChange.applied()) {
2730
2660
  state.permissionChangeRestart = "requested";
2731
2661
  runAbortController.abort("permission-change");
2732
2662
  }
@@ -2737,7 +2667,8 @@ async function activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notif
2737
2667
  handle,
2738
2668
  freezeRunTerminalOutcome,
2739
2669
  notifyUserMessagePersisted,
2740
- abortListener
2670
+ abortListener,
2671
+ ready: bindProjection()
2741
2672
  };
2742
2673
  }
2743
2674
  //#endregion
@@ -2746,12 +2677,15 @@ async function cleanupCodexAttempt(resources, turnRuntime, lifecycle, requestRun
2746
2677
  const { prompt, state: resourceState, trajectoryRecorder, releaseCurrentRoute, releaseSharedClientLeaseAndRetireOneShotClient, releaseSandboxExecEnvironment, runCleanupStep } = resources;
2747
2678
  const { connection } = prompt.context.runtime;
2748
2679
  const { params, options, runAbortController, terminalState, bindingStore, bindingIdentity } = connection;
2749
- const { state, steeringQueueRef, userInputBridgeRef, turnWatches } = turnRuntime;
2680
+ const { state, steeringQueueRef, userInputBridgeRef, deadlines } = turnRuntime;
2750
2681
  const { maybeEmitFastModeAutoResetBestEffort, emitLifecycleTerminal, buildLifecycleTerminalMeta } = lifecycle;
2751
2682
  const { codexModelCallDiagnostics } = requestRuntime;
2752
2683
  const { activeTurnId, abortListener, handle, freezeRunTerminalOutcome } = activeTurn;
2753
2684
  prompt.context.attemptTools.scheduledAppAuthoritySourceRef.current = void 0;
2754
2685
  freezeRunTerminalOutcome();
2686
+ const projectionClose = state.projectionClosed ? void 0 : activeTurn.activeProjector.closeProjection();
2687
+ state.projectionClosed = true;
2688
+ const checkpointCleanup = projectionClose ? runCleanupStep("codex-transcript-checkpoint", () => projectionClose) : void 0;
2755
2689
  await state.abortCleanup.catch(() => void 0);
2756
2690
  try {
2757
2691
  steeringQueueRef.current?.cancel();
@@ -2762,32 +2696,32 @@ async function cleanupCodexAttempt(resources, turnRuntime, lifecycle, requestRun
2762
2696
  error: "codex app-server run completed without lifecycle terminal event",
2763
2697
  ...buildLifecycleTerminalMeta({
2764
2698
  aborted: runAbortController.signal.aborted && !state.clientClosedAbort,
2765
- timedOut: state.timedOut
2699
+ timedOut: state.timeout !== void 0
2766
2700
  })
2767
2701
  });
2768
2702
  if (trajectoryRecorder && !resourceState.trajectoryEndRecorded) trajectoryRecorder.recordEvent("session.ended", {
2769
- status: state.timedOut || runAbortController.signal.aborted && !state.clientClosedAbort ? "interrupted" : "cleanup",
2703
+ status: state.timeout || runAbortController.signal.aborted && !state.clientClosedAbort ? "interrupted" : "cleanup",
2770
2704
  threadId: resourceState.thread.threadId,
2771
2705
  turnId: activeTurnId,
2772
- timedOut: state.timedOut,
2706
+ timedOut: state.timeout !== void 0,
2773
2707
  aborted: runAbortController.signal.aborted && !state.clientClosedAbort
2774
2708
  });
2775
2709
  await runCleanupStep("codex-trajectory-flush", () => trajectoryRecorder?.flush());
2776
2710
  const retainLiveIncognitoThread = (terminalState.turnSucceeded || state.permissionChangeRestart === "confirmed" && !params.abortSignal?.aborted) && isIncognitoSessionKey(params.sessionKey);
2777
- const retainedPersistentThread = terminalState.turnSucceeded && !isIncognitoSessionKey(params.sessionKey) && params.cleanupBundleMcpOnRunEnd !== true && resourceState.thread.liveThreadConfigFingerprint !== void 0 && resourceState.thread.clientId === resolveCodexAppServerClientInstanceId(resourceState.client) && resourceState.thread.preserveNativeModel !== true && resourceState.thread.connectionScope !== "supervision" && !resourceState.thread.ringZeroConfigFingerprint ? (await bindingStore.read(bindingIdentity))?.threadId === resourceState.thread.threadId && await bindingStore.withLease(bindingIdentity, async () => {
2711
+ const retainLiveThread = ((retainLiveIncognitoThread && resourceState.thread.liveThreadEphemeralPolicy !== void 0 || terminalState.turnSucceeded && !isIncognitoSessionKey(params.sessionKey) && params.cleanupBundleMcpOnRunEnd !== true && resourceState.thread.liveThreadConfigFingerprint !== void 0 && resourceState.thread.preserveNativeModel !== true && resourceState.thread.connectionScope !== "supervision" && !resourceState.thread.ringZeroConfigFingerprint) && resourceState.thread.clientId === resolveCodexAppServerClientInstanceId(resourceState.client) ? (await bindingStore.read(bindingIdentity))?.threadId === resourceState.thread.threadId && await bindingStore.withLease(bindingIdentity, async () => {
2778
2712
  if ((await bindingStore.read(bindingIdentity))?.threadId !== resourceState.thread.threadId) return false;
2779
2713
  return await retainCodexAppServerBindingSubscription(resourceState.client, resourceState.thread.threadId, {
2780
2714
  release: resourceState.thread.liveThreadOwnership?.release,
2781
2715
  configFingerprint: resourceState.thread.liveThreadConfigFingerprint,
2782
- serviceTier: connection.mutable.pluginAppServer.serviceTier
2716
+ serviceTier: connection.mutable.pluginAppServer.serviceTier,
2717
+ ephemeralPolicy: resourceState.thread.liveThreadEphemeralPolicy
2783
2718
  });
2784
- }) : false;
2785
- const retainLiveThread = retainLiveIncognitoThread || retainedPersistentThread;
2786
- const bindingReleased = isIncognitoSessionKey(params.sessionKey) && !retainLiveIncognitoThread ? await bindingStore.mutate(bindingIdentity, {
2719
+ }) : false) || retainLiveIncognitoThread && resourceState.thread.liveThreadEphemeralPolicy === void 0;
2720
+ const bindingReleased = isIncognitoSessionKey(params.sessionKey) && !retainLiveThread ? await bindingStore.mutate(bindingIdentity, {
2787
2721
  kind: "clear",
2788
2722
  threadId: resourceState.thread.threadId
2789
2723
  }) : true;
2790
- if (!state.timedOut && !retainLiveThread) {
2724
+ if (!retainLiveThread) {
2791
2725
  if (bindingReleased) {
2792
2726
  if (!await unsubscribeCodexThreadBestEffort(resourceState.client, {
2793
2727
  threadId: resourceState.thread.threadId,
@@ -2797,14 +2731,14 @@ async function cleanupCodexAttempt(resources, turnRuntime, lifecycle, requestRun
2797
2731
  }
2798
2732
  } finally {
2799
2733
  await runCleanupStep("codex-user-input-cancel", () => userInputBridgeRef.current?.cancelPending());
2800
- await runCleanupStep("codex-turn-watch-clear", () => turnWatches.clearAllTimers());
2734
+ await runCleanupStep("codex-turn-deadline-clear", () => deadlines.dispose());
2801
2735
  await runCleanupStep("codex-dynamic-tool-cleanup", async () => {
2802
- const cleanupReason = terminalState.turnSucceeded ? "completion" : state.timedOut ? "timeout" : runAbortController.signal.aborted ? "cancel" : "error";
2736
+ const cleanupReason = terminalState.turnSucceeded ? "completion" : state.timeout ? "timeout" : runAbortController.signal.aborted ? "cancel" : "error";
2803
2737
  const cleanups = prompt.context.attemptTools.runCleanups.splice(0);
2804
2738
  await Promise.allSettled(cleanups.map(async (cleanup) => await cleanup(cleanupReason)));
2805
2739
  });
2806
2740
  await runCleanupStep("codex-route-release", releaseCurrentRoute);
2807
- await runCleanupStep("codex-transcript-checkpoint", () => activeTurn.activeProjector.transcriptCheckpoint.flush(true));
2741
+ await checkpointCleanup;
2808
2742
  await runCleanupStep("codex-shared-client-release", releaseSharedClientLeaseAndRetireOneShotClient);
2809
2743
  const nativeHookRelay = resourceState.nativeHookRelay;
2810
2744
  resourceState.nativeHookRelay = void 0;
@@ -2818,7 +2752,7 @@ async function cleanupCodexAttempt(resources, turnRuntime, lifecycle, requestRun
2818
2752
  });
2819
2753
  await runCleanupStep("codex-sandbox-release", releaseSandboxExecEnvironment);
2820
2754
  await runCleanupStep("codex-scoped-mcp-dispose", () => prompt.context.attemptTools.scopedMcpTools?.dispose());
2821
- await runCleanupStep("codex-scheduled-mcp-dispose", () => prompt.context.attemptTools.scheduledConfiguredMcp?.dispose());
2755
+ await runCleanupStep("codex-configured-mcp-dispose", () => prompt.context.attemptTools.configuredMcp?.dispose());
2822
2756
  await runCleanupStep("codex-abort-listener-remove", () => {
2823
2757
  runAbortController.signal.removeEventListener("abort", abortListener);
2824
2758
  });
@@ -3249,7 +3183,7 @@ async function prepareCodexAttemptConnection({ params, options }) {
3249
3183
  assertLocalTargetSupported(sandbox?.enabled === true || remoteExec);
3250
3184
  const preparedShellEnvironment = preparedEnvironment ? {
3251
3185
  ...preparedEnvironment.credentialScrubEnv,
3252
- ...!remoteExec ? preparedEnvironment.localIdentityEnv : void 0,
3186
+ ...sandbox?.enabled || remoteExec ? void 0 : preparedEnvironment.localIdentityEnv,
3253
3187
  ...preparedEnvironment.localProcessEnv
3254
3188
  } : void 0;
3255
3189
  const shellEnvironment = preparedShellEnvironment && Object.keys(preparedShellEnvironment).length > 0 ? preparedShellEnvironment : void 0;
@@ -3277,7 +3211,7 @@ async function prepareCodexAttemptConnection({ params, options }) {
3277
3211
  preDynamicStartupStages.mark("session-agent");
3278
3212
  let activeContextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
3279
3213
  const isInactiveThreadBootstrapBinding = (binding) => !activeContextEngine && binding?.contextEngine?.projection?.mode === "thread_bootstrap";
3280
- if (bindingIdentity.kind === "session" && bindingIdentity.sessionKey && (params.sessionTarget?.storePath || params.config?.session?.store)) {
3214
+ if (bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
3281
3215
  const authority = resolveCodexRunSessionBindingAuthority({
3282
3216
  identity: bindingIdentity,
3283
3217
  config: params.config,
@@ -3554,615 +3488,6 @@ async function prepareCodexAttemptConnection({ params, options }) {
3554
3488
  };
3555
3489
  }
3556
3490
  //#endregion
3557
- //#region extensions/codex/src/app-server/attempt-context.ts
3558
- /**
3559
- * Builds Codex app-server prompt context, workspace bootstrap injections,
3560
- * system-prompt reports, and context-engine projection decisions.
3561
- */
3562
- const CODEX_NATIVE_PROJECT_DOC_BASENAMES = /* @__PURE__ */ new Set(["agents.md"]);
3563
- const CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = /* @__PURE__ */ new Set([
3564
- "identity.md",
3565
- "soul.md",
3566
- "user.md"
3567
- ]);
3568
- const CODEX_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = new Set(CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES);
3569
- const CODEX_MEMORY_CONTEXT_BASENAME = "memory.md";
3570
- const CODEX_MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set(["memory_search", "memory_get"]);
3571
- const CODEX_BOOTSTRAP_CONTEXT_ORDER = /* @__PURE__ */ new Map([
3572
- ["soul.md", 10],
3573
- ["identity.md", 20],
3574
- ["user.md", 30],
3575
- ["bootstrap.md", 50],
3576
- ["memory.md", 60]
3577
- ]);
3578
- /** Reads mirrored Codex session history for harness hooks. */
3579
- async function readMirroredSessionHistoryMessages(params) {
3580
- const { admission, ...target } = params;
3581
- const messages = await readCodexMirroredSessionHistoryMessages(target, admission, "model-context");
3582
- if (!messages) embeddedAgentLog.warn("failed to read mirrored session history for codex harness hooks", { sessionFile: params.sessionFile });
3583
- return messages;
3584
- }
3585
- /** Reads a valid thread-bootstrap projection request from context-engine output. */
3586
- function readContextEngineThreadBootstrapProjection(projection) {
3587
- if (projection?.mode !== "thread_bootstrap") return;
3588
- const epoch = projection.epoch?.trim();
3589
- if (!epoch) {
3590
- embeddedAgentLog.warn("context engine requested Codex thread-bootstrap projection without an epoch; using per-turn projection");
3591
- return;
3592
- }
3593
- const fingerprint = projection.fingerprint?.trim();
3594
- return {
3595
- mode: "thread_bootstrap",
3596
- epoch,
3597
- ...fingerprint ? { fingerprint } : {}
3598
- };
3599
- }
3600
- /**
3601
- * Decides whether an existing Codex thread can reuse its context-engine
3602
- * bootstrap projection or must be reprojected.
3603
- */
3604
- function resolveContextEngineBootstrapProjectionDecision(params) {
3605
- const bindingProjection = params.startupBinding?.contextEngine?.projection;
3606
- if (!params.startupBinding?.threadId || !bindingProjection) return {
3607
- project: true,
3608
- reason: !params.startupBinding?.threadId ? "missing-thread-binding" : "missing-projection-binding"
3609
- };
3610
- if (!params.expectedBinding || !isContextEngineBindingCompatible(params.startupBinding.contextEngine, params.expectedBinding)) return {
3611
- project: true,
3612
- reason: "context-engine-binding-mismatch"
3613
- };
3614
- if (!areCodexDynamicToolFingerprintsCompatible({
3615
- previous: params.startupBinding.dynamicToolsFingerprint,
3616
- next: params.dynamicToolsFingerprint,
3617
- nextLegacy: params.legacyDynamicToolsFingerprint
3618
- })) return {
3619
- project: true,
3620
- reason: "dynamic-tools-mismatch"
3621
- };
3622
- return bindingProjection.mode !== "thread_bootstrap" || bindingProjection.epoch !== params.projection.epoch || bindingProjection.fingerprint !== params.projection.fingerprint ? {
3623
- project: true,
3624
- reason: "projection-mismatch"
3625
- } : {
3626
- project: false,
3627
- reason: "matching-thread-bootstrap-binding"
3628
- };
3629
- }
3630
- /**
3631
- * Loads workspace bootstrap files and partitions them into Codex-native prompt,
3632
- * developer-instruction, heartbeat, and memory-tool contexts.
3633
- */
3634
- async function buildCodexWorkspaceBootstrapContext(params) {
3635
- try {
3636
- const inheritsAgentWorkspace = (params.executionWorkspace ?? params.resolvedWorkspace) !== params.resolvedWorkspace;
3637
- const promptWorkspace = inheritsAgentWorkspace ? params.resolvedWorkspace : params.effectiveWorkspace;
3638
- const memoryToolsAvailable = params.memoryToolNames.length > 0 && canRouteCodexWorkspaceMemoryThroughTools({
3639
- config: params.params.config,
3640
- agentId: params.params.agentId ?? params.sessionAgentId,
3641
- workspaceDir: inheritsAgentWorkspace ? params.resolvedWorkspace : params.effectiveWorkspace
3642
- });
3643
- const bootstrapFiles = await resolveBootstrapFilesForRun({
3644
- workspaceDir: params.resolvedWorkspace,
3645
- config: params.params.config,
3646
- sessionKey: params.sessionKey,
3647
- sessionId: params.params.sessionId,
3648
- chatType: params.params.chatType,
3649
- agentId: params.params.agentId ?? params.sessionAgentId,
3650
- warn: (message) => embeddedAgentLog.warn(message),
3651
- contextMode: params.params.bootstrapContextMode,
3652
- runKind: params.params.bootstrapContextRunKind
3653
- });
3654
- const memoryToolRoutedBootstrapFiles = memoryToolsAvailable ? selectCodexWorkspaceMemoryReferenceFiles({
3655
- bootstrapFiles,
3656
- workspaceDir: params.resolvedWorkspace
3657
- }) : [];
3658
- const memoryReferenceFiles = memoryToolRoutedBootstrapFiles.map((file) => remapCodexContextFilePath({
3659
- file: toCodexEmbeddedContextFile(file),
3660
- sourceWorkspaceDir: params.resolvedWorkspace,
3661
- targetWorkspaceDir: promptWorkspace
3662
- }));
3663
- const contextFiles = buildBootstrapContextForFiles(memoryToolsAvailable ? bootstrapFiles.filter((file) => !isCodexWorkspaceRootMemoryBootstrapFile({
3664
- file,
3665
- workspaceDir: params.resolvedWorkspace
3666
- })) : bootstrapFiles, {
3667
- config: params.params.config,
3668
- agentId: params.params.agentId ?? params.sessionAgentId,
3669
- warn: (message) => embeddedAgentLog.warn(message)
3670
- }).map((file) => remapCodexContextFilePath({
3671
- file,
3672
- sourceWorkspaceDir: params.resolvedWorkspace,
3673
- targetWorkspaceDir: promptWorkspace
3674
- }));
3675
- const promptContextFiles = selectCodexWorkspacePromptContextFiles(contextFiles, {
3676
- excludeMemory: memoryToolsAvailable,
3677
- memoryWorkspaceDir: params.effectiveWorkspace
3678
- });
3679
- const injectOpenClawContext = shouldInjectCodexOpenClawPromptContext(params.params);
3680
- const restrictedProjectDocNeedsOpenClawCarrier = params.params.pluginHarnessToolPolicyRestricted === true && !params.params.disableTools && !isMessageOnlyCodexSourceReply(params.params) && params.params.bootstrapContextMode !== "lightweight";
3681
- const threadDeveloperInstructionFiles = injectOpenClawContext && !params.ringZeroActive && (inheritsAgentWorkspace || restrictedProjectDocNeedsOpenClawCarrier) ? selectCodexWorkspaceAgentProjectInstructionFiles(contextFiles, params.resolvedWorkspace) : [];
3682
- const turnScopedDeveloperInstructionFiles = injectOpenClawContext ? selectCodexWorkspaceTurnScopedDeveloperInstructionFiles(contextFiles) : [];
3683
- return {
3684
- bootstrapFiles,
3685
- contextFiles,
3686
- inheritsAgentWorkspace,
3687
- promptContextFiles,
3688
- threadDeveloperInstructionFiles,
3689
- turnScopedDeveloperInstructionFiles,
3690
- memoryReferenceFiles,
3691
- memoryToolRoutedBootstrapFiles,
3692
- memoryToolNames: [...params.memoryToolNames],
3693
- memoryToolRouted: memoryToolsAvailable,
3694
- promptContext: renderCodexWorkspaceBootstrapPromptContext(promptContextFiles),
3695
- threadDeveloperInstructions: renderCodexWorkspaceDeveloperInstructions({
3696
- files: threadDeveloperInstructionFiles,
3697
- header: "## OpenClaw Agent Workspace Instructions",
3698
- preamble: "OpenClaw loaded this bounded snapshot from the configured agent workspace."
3699
- }),
3700
- turnScopedDeveloperInstructions: renderCodexWorkspaceCollaborationDeveloperInstructions(turnScopedDeveloperInstructionFiles),
3701
- memoryCollaborationInstructions: shouldInjectCodexOpenClawPromptContext(params.params) ? await renderCodexWorkspaceMemoryCollaborationInstructions({
3702
- files: memoryReferenceFiles,
3703
- toolNames: params.memoryToolNames,
3704
- memoryToolRouted: memoryToolsAvailable,
3705
- citationsMode: params.params.config?.memory?.citations,
3706
- agentId: params.params.agentId ?? params.sessionAgentId,
3707
- agentSessionKey: params.sessionKey,
3708
- sandboxed: params.sandboxed
3709
- }) : void 0
3710
- };
3711
- } catch (error) {
3712
- embeddedAgentLog.warn("failed to load codex workspace bootstrap instructions", { error });
3713
- return {
3714
- bootstrapFiles: [],
3715
- contextFiles: [],
3716
- inheritsAgentWorkspace: false
3717
- };
3718
- }
3719
- }
3720
- /**
3721
- * Builds the prompt-size, bootstrap-file, skill, and tool-schema accounting
3722
- * report for a Codex run.
3723
- */
3724
- function buildCodexSystemPromptReport(params) {
3725
- const toolEntries = flattenCodexDynamicToolFunctions(params.tools).map(buildCodexToolReportEntry);
3726
- const schemaChars = toolEntries.reduce((sum, tool) => sum + tool.schemaChars, 0);
3727
- const skillsPrompt = params.skillsPrompt.trim();
3728
- const bootstrapMaxChars = readPositiveNumber(params.attempt.config?.agents?.defaults?.bootstrapMaxChars);
3729
- const bootstrapTotalMaxChars = readPositiveNumber(params.attempt.config?.agents?.defaults?.bootstrapTotalMaxChars);
3730
- return {
3731
- source: "run",
3732
- generatedAt: Date.now(),
3733
- sessionId: params.attempt.sessionId,
3734
- sessionKey: params.sessionKey,
3735
- provider: params.attempt.provider,
3736
- model: params.attempt.modelId,
3737
- workspaceDir: params.workspaceDir,
3738
- ...bootstrapMaxChars ? { bootstrapMaxChars } : {},
3739
- ...bootstrapTotalMaxChars ? { bootstrapTotalMaxChars } : {},
3740
- systemPrompt: {
3741
- chars: params.developerInstructions.length,
3742
- projectContextChars: 0,
3743
- nonProjectContextChars: params.developerInstructions.length,
3744
- hash: sha256Text(params.developerInstructions)
3745
- },
3746
- injectedWorkspaceFiles: buildCodexBootstrapInjectionStats({
3747
- bootstrapFiles: params.workspaceBootstrapContext.bootstrapFiles,
3748
- injectedFiles: params.workspaceBootstrapContext.promptContextFiles ?? [],
3749
- developerInstructionFiles: [...params.workspaceBootstrapContext.threadDeveloperInstructionFiles ?? [], ...params.workspaceBootstrapContext.turnScopedDeveloperInstructionFiles ?? []],
3750
- memoryToolRoutedBootstrapFiles: params.workspaceBootstrapContext.memoryToolRoutedBootstrapFiles ?? [],
3751
- memoryToolRouted: params.workspaceBootstrapContext.memoryToolRouted === true
3752
- }),
3753
- skills: {
3754
- promptChars: skillsPrompt.length,
3755
- hash: sha256Text(skillsPrompt),
3756
- entries: buildCodexSkillReportEntries(skillsPrompt)
3757
- },
3758
- tools: {
3759
- listChars: 0,
3760
- schemaChars,
3761
- entries: toolEntries
3762
- }
3763
- };
3764
- }
3765
- function buildCodexSkillReportEntries(skillsPrompt) {
3766
- if (!skillsPrompt) return [];
3767
- return Array.from(skillsPrompt.matchAll(/<skill>[\s\S]*?<\/skill>/gi)).map((match) => match[0] ?? "").map((block) => ({
3768
- name: block.match(/<name>\s*([^<]+?)\s*<\/name>/i)?.[1]?.trim() || "(unknown)",
3769
- blockChars: block.length
3770
- })).filter((entry) => entry.blockChars > 0);
3771
- }
3772
- function buildCodexToolReportEntry(tool) {
3773
- const summary = tool.description.trim();
3774
- if (tool.deferLoading === true) return {
3775
- name: tool.name,
3776
- summaryChars: summary.length,
3777
- summaryHash: sha256Text(summary),
3778
- schemaChars: 0,
3779
- schemaHash: stableJsonHash(null),
3780
- propertiesCount: null
3781
- };
3782
- return {
3783
- name: tool.name,
3784
- summaryChars: summary.length,
3785
- summaryHash: sha256Text(summary),
3786
- ...buildCodexToolSchemaStats(tool.inputSchema)
3787
- };
3788
- }
3789
- function buildCodexToolSchemaStats(schema) {
3790
- const schemaChars = (() => {
3791
- try {
3792
- return JSON.stringify(schema).length;
3793
- } catch {
3794
- return 0;
3795
- }
3796
- })();
3797
- const properties = isJsonObject(schema) && isJsonObject(schema.properties) ? schema.properties : null;
3798
- return {
3799
- schemaChars,
3800
- schemaHash: stableJsonHash(schema),
3801
- propertiesCount: properties ? Object.keys(properties).length : null
3802
- };
3803
- }
3804
- function sha256Text(value) {
3805
- return createHash("sha256").update(value).digest("hex");
3806
- }
3807
- function normalizeForStableHash(value) {
3808
- if (Array.isArray(value)) return value.map((entry) => normalizeForStableHash(entry));
3809
- if (value && typeof value === "object") {
3810
- const record = value;
3811
- return Object.fromEntries(Object.keys(record).toSorted((left, right) => left.localeCompare(right)).map((key) => [key, normalizeForStableHash(record[key])]));
3812
- }
3813
- return value;
3814
- }
3815
- function stableJsonHash(value) {
3816
- return sha256Text(JSON.stringify(normalizeForStableHash(value)) ?? "null");
3817
- }
3818
- function buildCodexBootstrapInjectionStats(params) {
3819
- const injectedIndex = indexCodexContextFileContent(params.injectedFiles);
3820
- const developerInstructionIndex = indexCodexContextFileContent(params.developerInstructionFiles ?? []);
3821
- const memoryToolRoutedPaths = new Set((params.memoryToolRoutedBootstrapFiles ?? []).map((file) => readNonBlankString(file.path)).filter(isNonEmptyString$1).map(normalizeCodexContextFilePath));
3822
- return params.bootstrapFiles.map((file) => {
3823
- const fileName = readNonBlankString(file.name);
3824
- const pathValue = readNonBlankString(file.path) ?? fileName ?? "";
3825
- const displayName = (fileName ?? getCodexContextFileDisplayBasename(pathValue)) || pathValue;
3826
- const baseName = getCodexContextFileBasename(pathValue || fileName || "");
3827
- const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
3828
- const memoryToolRoutedFile = baseName === CODEX_MEMORY_CONTEXT_BASENAME && params.memoryToolRouted === true && memoryToolRoutedPaths.has(normalizeCodexContextFilePath(pathValue));
3829
- const injected = memoryToolRoutedFile ? void 0 : readCodexIndexedContextFileContent(injectedIndex, pathValue, fileName) ?? readCodexIndexedContextFileContent(developerInstructionIndex, pathValue, fileName);
3830
- if (!file.missing && injected === void 0 && CODEX_NATIVE_PROJECT_DOC_BASENAMES.has(baseName)) return {
3831
- name: displayName,
3832
- path: pathValue,
3833
- missing: false,
3834
- rawChars,
3835
- injectionStatus: "native_unverified",
3836
- injectedChars: null,
3837
- truncated: null
3838
- };
3839
- const injectedChars = memoryToolRoutedFile ? 0 : injected?.length ?? 0;
3840
- const truncated = memoryToolRoutedFile ? false : !file.missing && injectedChars < rawChars;
3841
- return {
3842
- name: displayName,
3843
- path: pathValue,
3844
- missing: file.missing,
3845
- rawChars,
3846
- injectedChars,
3847
- truncated
3848
- };
3849
- });
3850
- }
3851
- function indexCodexContextFileContent(files) {
3852
- const byPath = /* @__PURE__ */ new Map();
3853
- const byBaseName = /* @__PURE__ */ new Map();
3854
- for (const file of files) {
3855
- const pathValue = readNonBlankString(file.path);
3856
- if (!pathValue) continue;
3857
- if (!byPath.has(pathValue)) byPath.set(pathValue, file.content);
3858
- const baseName = getCodexContextFileBasename(pathValue);
3859
- if (baseName && !byBaseName.has(baseName)) byBaseName.set(baseName, file.content);
3860
- }
3861
- return {
3862
- byPath,
3863
- byBaseName
3864
- };
3865
- }
3866
- function readCodexIndexedContextFileContent(index, pathValue, fileName) {
3867
- const pathContent = index.byPath.get(pathValue);
3868
- if (pathContent !== void 0) return pathContent;
3869
- if (fileName) {
3870
- const nameContent = index.byPath.get(fileName);
3871
- if (nameContent !== void 0) return nameContent;
3872
- }
3873
- const baseName = getCodexContextFileBasename(fileName ?? pathValue);
3874
- return baseName ? index.byBaseName.get(baseName) : void 0;
3875
- }
3876
- function readPositiveNumber(value) {
3877
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
3878
- }
3879
- /**
3880
- * Builds OpenClaw-provided workspace prompt context for the current Codex turn.
3881
- */
3882
- function buildCodexOpenClawPromptContext(params) {
3883
- if (!shouldInjectCodexOpenClawPromptContext(params.params)) return;
3884
- const sections = [params.workspacePromptContext?.trim() ? [
3885
- "## OpenClaw Workspace Context",
3886
- "",
3887
- params.workspacePromptContext.trim()
3888
- ].join("\n") : void 0, params.watchedSessionsContext?.trim() || void 0].filter(isNonEmptyString$1);
3889
- if (sections.length === 0) return;
3890
- return [
3891
- "OpenClaw runtime context for this turn:",
3892
- "Treat this OpenClaw-provided context as supporting project/user reference for the current request.",
3893
- "",
3894
- ...sections
3895
- ].join("\n");
3896
- }
3897
- /**
3898
- * Renders the watched-sessions block for the Codex per-turn runtime context.
3899
- * Codex builds its own instruction layers, so the embedded prompt's Watched
3900
- * Sessions section must be re-surfaced here or Codex-backed main sessions
3901
- * keep refusing cross-session questions (openclaw#114797).
3902
- */
3903
- function buildCodexWatchedSessionsContext(params) {
3904
- if (!shouldInjectCodexOpenClawPromptContext(params.attempt)) return;
3905
- return buildWatchedSessionsHarnessContext({
3906
- config: params.attempt.config,
3907
- sessionKey: params.sessionKey,
3908
- sandboxed: params.sandboxed,
3909
- toolNames: flattenCodexDynamicToolFunctions(params.dynamicTools).map((tool) => normalizeCodexDynamicToolName(tool.name))
3910
- });
3911
- }
3912
- function shouldInjectCodexOpenClawPromptContext(params) {
3913
- return !(params.bootstrapContextMode === "lightweight" && params.bootstrapContextRunKind === "cron");
3914
- }
3915
- /** Renders loaded OpenClaw skill prompts as Codex collaboration instructions. */
3916
- function renderCodexSkillsCollaborationInstructions(params) {
3917
- if (!shouldInjectCodexOpenClawPromptContext(params.attempt)) return;
3918
- return params.skillsPrompt?.trim() ? [
3919
- "## OpenClaw Skills",
3920
- "",
3921
- params.skillsPrompt.trim()
3922
- ].join("\n") : void 0;
3923
- }
3924
- /**
3925
- * Prepends OpenClaw context while preserving leading delivery metadata as
3926
- * routing guidance instead of user request text.
3927
- */
3928
- function prependCodexOpenClawPromptContext(prompt, context, options = {}) {
3929
- const { deliveryHint, prompt: promptWithoutDeliveryHint } = splitLeadingCodexDeliveryHint(prompt);
3930
- if (!context?.trim() && (!deliveryHint || options.preservePromptWithoutContext)) return prompt;
3931
- const promptSection = promptWithoutDeliveryHint.startsWith("OpenClaw assembled context for this turn:") ? promptWithoutDeliveryHint : ["Current user request:", promptWithoutDeliveryHint].join("\n");
3932
- const deliverySection = deliveryHint ? [
3933
- "OpenClaw delivery metadata:",
3934
- "This delivery metadata is runtime routing guidance, not the user's request.",
3935
- deliveryHint
3936
- ].join("\n") : void 0;
3937
- return [
3938
- context?.trim(),
3939
- deliverySection,
3940
- promptSection
3941
- ].filter(Boolean).join("\n\n");
3942
- }
3943
- /**
3944
- * Maps the surviving user-request portion of an input range after delivery
3945
- * metadata has been relocated before the request.
3946
- */
3947
- function resolveCodexDeliveryHintPreservedInputRange(params) {
3948
- const { prompt, promptInputRange, decoratedPrompt } = params;
3949
- const { deliveryHint, prompt: promptWithoutDeliveryHint } = splitLeadingCodexDeliveryHint(prompt);
3950
- if (!deliveryHint || !promptInputRange || promptInputRange.start < 0 || promptInputRange.end < promptInputRange.start || promptInputRange.end > prompt.length || !decoratedPrompt.endsWith(promptWithoutDeliveryHint)) return;
3951
- const promptWithoutDeliveryHintStart = prompt.length - promptWithoutDeliveryHint.length;
3952
- const inputStart = Math.max(promptInputRange.start, promptWithoutDeliveryHintStart);
3953
- const inputEnd = Math.max(inputStart, Math.min(promptInputRange.end, promptWithoutDeliveryHint.length + promptWithoutDeliveryHintStart));
3954
- const decoratedPromptSuffixStart = decoratedPrompt.length - promptWithoutDeliveryHint.length;
3955
- const requestHeader = "Current user request:\n";
3956
- const requestHeaderStart = decoratedPromptSuffixStart - 22;
3957
- return {
3958
- start: inputStart === promptWithoutDeliveryHintStart && decoratedPrompt.slice(requestHeaderStart, decoratedPromptSuffixStart) === requestHeader ? requestHeaderStart : decoratedPromptSuffixStart + inputStart - promptWithoutDeliveryHintStart,
3959
- end: decoratedPromptSuffixStart + inputEnd - promptWithoutDeliveryHintStart
3960
- };
3961
- }
3962
- function splitLeadingCodexDeliveryHint(prompt) {
3963
- const trimmedStart = prompt.trimStart();
3964
- const matchedHint = MESSAGE_TOOL_DELIVERY_HINTS.find((hint) => trimmedStart.startsWith(hint));
3965
- if (!matchedHint) return { prompt };
3966
- return {
3967
- deliveryHint: matchedHint,
3968
- prompt: trimmedStart.slice(matchedHint.length).replace(/^\s*\n/, "").trimStart()
3969
- };
3970
- }
3971
- function renderCodexWorkspaceBootstrapPromptContext(contextFiles) {
3972
- const files = contextFiles;
3973
- if (files.length === 0) return;
3974
- const lines = [
3975
- "OpenClaw loaded these user-editable workspace files for the current turn. Codex loads project-local AGENTS.md natively. When execution uses another folder, OpenClaw supplies the agent workspace AGENTS.md as thread-level developer instructions. SOUL.md, IDENTITY.md, and USER.md remain turn-scoped collaboration instructions. Those files are not repeated here.",
3976
- "",
3977
- "# Project Context",
3978
- "",
3979
- "The following project context files have been loaded:"
3980
- ];
3981
- lines.push("");
3982
- for (const file of files) lines.push(`## ${file.path}`, "", file.content, "");
3983
- return lines.join("\n").trim();
3984
- }
3985
- function selectCodexWorkspacePromptContextFiles(contextFiles, options = {}) {
3986
- const excludeMemory = options.excludeMemory ?? true;
3987
- return contextFiles.filter((file) => {
3988
- const baseName = getCodexContextFileBasename(file.path);
3989
- return baseName && !CODEX_NATIVE_PROJECT_DOC_BASENAMES.has(baseName) && !CODEX_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES.has(baseName) && (!excludeMemory || !isCodexWorkspaceRootMemoryContextFile({
3990
- file,
3991
- workspaceDir: options.memoryWorkspaceDir
3992
- })) && !isMissingCodexBootstrapContextFile(file);
3993
- }).toSorted(compareCodexContextFiles);
3994
- }
3995
- function selectCodexWorkspaceTurnScopedDeveloperInstructionFiles(contextFiles) {
3996
- return selectCodexWorkspaceDeveloperInstructionFiles(contextFiles, CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES);
3997
- }
3998
- function selectCodexWorkspaceAgentProjectInstructionFiles(contextFiles, agentWorkspaceDir) {
3999
- const agentProjectDocPath = path.join(path.resolve(agentWorkspaceDir), "AGENTS.md");
4000
- return selectCodexWorkspaceDeveloperInstructionFiles(contextFiles, CODEX_NATIVE_PROJECT_DOC_BASENAMES).filter((file) => path.resolve(file.path) === agentProjectDocPath);
4001
- }
4002
- function selectCodexWorkspaceDeveloperInstructionFiles(contextFiles, basenames) {
4003
- return contextFiles.filter((file) => {
4004
- const baseName = getCodexContextFileBasename(file.path);
4005
- return baseName && basenames.has(baseName) && !isMissingCodexBootstrapContextFile(file) && file.content.trim().length > 0;
4006
- }).toSorted(compareCodexContextFiles);
4007
- }
4008
- function renderCodexWorkspaceCollaborationDeveloperInstructions(files) {
4009
- return renderCodexWorkspaceDeveloperInstructions({
4010
- files,
4011
- header: "## OpenClaw Agent Soul",
4012
- preamble: "OpenClaw loaded these workspace instruction files from the active agent workspace. They are the canonical definitions of who you are, how you think and work, and the human you work alongside. Internalize and follow them accordingly.",
4013
- wrapperTag: "AGENT_SOUL"
4014
- });
4015
- }
4016
- function renderCodexWorkspaceDeveloperInstructions(params) {
4017
- const { files, header, preamble, wrapperTag } = params;
4018
- if (files.length === 0) return;
4019
- const lines = [
4020
- header,
4021
- "",
4022
- preamble,
4023
- ""
4024
- ];
4025
- if (wrapperTag) lines.push(`<${wrapperTag}>`, "");
4026
- for (const file of files) lines.push(`### ${file.path}`, "", file.content, "");
4027
- if (wrapperTag) lines.push(`</${wrapperTag}>`);
4028
- return lines.join("\n").trim();
4029
- }
4030
- function selectCodexWorkspaceMemoryReferenceFiles(params) {
4031
- return params.bootstrapFiles.filter((file) => {
4032
- return isCodexWorkspaceRootMemoryBootstrapFile({
4033
- file,
4034
- workspaceDir: params.workspaceDir
4035
- }) && !file.missing && (file.content ?? "").trim().length > 0;
4036
- }).toSorted(compareCodexBootstrapFiles);
4037
- }
4038
- /**
4039
- * Renders a memory-file reference that points Codex at memory tools instead of
4040
- * embedding MEMORY.md contents.
4041
- */
4042
- function renderCodexWorkspaceMemoryReference(params) {
4043
- if (params.files.length === 0) return;
4044
- const lines = [
4045
- "## OpenClaw Workspace Memory",
4046
- "",
4047
- `MEMORY.md exists in the active agent workspace as a memory file, not an instruction file. OpenClaw does not paste its contents into native Codex turns; use ${(params.toolNames?.length ? params.toolNames : Array.from(CODEX_MEMORY_TOOL_NAMES)).join(" or ")} when durable memory is relevant and the tools are available.`,
4048
- ""
4049
- ];
4050
- for (const file of params.files) lines.push(`- ${file.path}`);
4051
- return lines.join("\n").trim();
4052
- }
4053
- async function renderCodexWorkspaceMemoryCollaborationInstructions(params) {
4054
- const sections = [params.memoryToolRouted ? await renderCodexMemoryRecallInstructions({
4055
- toolNames: params.toolNames,
4056
- citationsMode: params.citationsMode,
4057
- agentId: params.agentId,
4058
- agentSessionKey: params.agentSessionKey,
4059
- sandboxed: params.sandboxed
4060
- }) : void 0, renderCodexWorkspaceMemoryReference({
4061
- files: params.files,
4062
- toolNames: params.toolNames
4063
- })].filter(isNonEmptyString$1);
4064
- return sections.length > 0 ? sections.join("\n\n") : void 0;
4065
- }
4066
- async function renderCodexMemoryRecallInstructions(params) {
4067
- const availableTools = new Set(params.toolNames);
4068
- const memoryPrompt = await prepareMemorySystemPromptAddition({
4069
- availableTools,
4070
- citationsMode: params.citationsMode,
4071
- agentId: params.agentId,
4072
- agentSessionKey: params.agentSessionKey,
4073
- sandboxed: params.sandboxed
4074
- });
4075
- if (!memoryPrompt) return;
4076
- return [memoryPrompt, renderCodexMemoryToolSearchBridge(params.toolNames)].filter(isNonEmptyString$1).join("\n").trim();
4077
- }
4078
- function renderCodexMemoryToolSearchBridge(toolNames) {
4079
- const memoryToolNames = toolNames.map((name) => normalizeCodexDynamicToolName(name)).filter((name) => CODEX_MEMORY_TOOL_NAMES.has(name)).toSorted();
4080
- if (memoryToolNames.length === 0) return;
4081
- return `Codex may expose ${memoryToolNames.join(" and ")} as deferred tools. When the memory guidance above calls for memory recall, use an already-loaded memory tool directly. If the needed memory tool is deferred and not currently callable, use \`tool_search\` to load it, then call that memory tool.`;
4082
- }
4083
- /** Lists available memory tool names understood by Codex workspace memory routing. */
4084
- function getCodexWorkspaceMemoryToolNames(tools) {
4085
- const availableToolNames = new Set(flattenCodexDynamicToolFunctions(tools).map((tool) => normalizeCodexDynamicToolName(tool.name)));
4086
- return Array.from(CODEX_MEMORY_TOOL_NAMES).filter((name) => availableToolNames.has(name));
4087
- }
4088
- function canRouteCodexWorkspaceMemoryThroughTools(params) {
4089
- if (!params.config) return false;
4090
- return isSameCodexWorkspacePath(resolveAgentWorkspaceDir(params.config, params.agentId), params.workspaceDir);
4091
- }
4092
- function isMissingCodexBootstrapContextFile(file) {
4093
- return file.content.trimStart().startsWith("[MISSING] Expected at:");
4094
- }
4095
- function toCodexEmbeddedContextFile(file) {
4096
- return {
4097
- path: readNonBlankString(file.path) ?? readNonBlankString(file.name) ?? "",
4098
- content: file.content ?? ""
4099
- };
4100
- }
4101
- function isCodexWorkspaceRootMemoryBootstrapFile(params) {
4102
- return isCodexWorkspaceRootMemoryPath({
4103
- filePath: readNonBlankString(params.file.path) ?? readNonBlankString(params.file.name) ?? "",
4104
- workspaceDir: params.workspaceDir
4105
- });
4106
- }
4107
- function isCodexWorkspaceRootMemoryContextFile(params) {
4108
- if (!params.workspaceDir) return false;
4109
- return isCodexWorkspaceRootMemoryPath({
4110
- filePath: params.file.path,
4111
- workspaceDir: params.workspaceDir
4112
- });
4113
- }
4114
- function isCodexWorkspaceRootMemoryPath(params) {
4115
- const filePath = params.filePath.trim();
4116
- if (!filePath) return false;
4117
- return (path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(params.workspaceDir, filePath)) === path.join(path.resolve(params.workspaceDir), "MEMORY.md");
4118
- }
4119
- function isSameCodexWorkspacePath(left, right) {
4120
- return path.resolve(left) === path.resolve(right);
4121
- }
4122
- /**
4123
- * Remaps bootstrap file paths from the resolved workspace to the effective Codex
4124
- * workspace while preserving platform path separators.
4125
- */
4126
- function remapCodexContextFilePath(params) {
4127
- const relativePath = path.relative(params.sourceWorkspaceDir, params.file.path);
4128
- if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath) || params.sourceWorkspaceDir === params.targetWorkspaceDir) return params.file;
4129
- const targetUsesPosixSeparators = params.targetWorkspaceDir.includes("/") && !params.targetWorkspaceDir.includes("\\");
4130
- const normalizedRelativePath = targetUsesPosixSeparators ? relativePath.replaceAll("\\", "/") : relativePath.replaceAll("/", "\\");
4131
- return {
4132
- ...params.file,
4133
- path: targetUsesPosixSeparators ? path.posix.join(params.targetWorkspaceDir, normalizedRelativePath) : path.win32.join(params.targetWorkspaceDir, normalizedRelativePath)
4134
- };
4135
- }
4136
- function compareCodexContextFiles(left, right) {
4137
- const leftPath = normalizeCodexContextFilePath(left.path);
4138
- const rightPath = normalizeCodexContextFilePath(right.path);
4139
- const leftBase = getCodexContextFileBasename(left.path);
4140
- const rightBase = getCodexContextFileBasename(right.path);
4141
- const leftOrder = CODEX_BOOTSTRAP_CONTEXT_ORDER.get(leftBase) ?? Number.MAX_SAFE_INTEGER;
4142
- const rightOrder = CODEX_BOOTSTRAP_CONTEXT_ORDER.get(rightBase) ?? Number.MAX_SAFE_INTEGER;
4143
- if (leftOrder !== rightOrder) return leftOrder - rightOrder;
4144
- if (leftBase !== rightBase) return leftBase.localeCompare(rightBase);
4145
- return leftPath.localeCompare(rightPath);
4146
- }
4147
- function compareCodexBootstrapFiles(left, right) {
4148
- return compareCodexContextFiles(toCodexEmbeddedContextFile(left), toCodexEmbeddedContextFile(right));
4149
- }
4150
- function normalizeCodexContextFilePath(filePath) {
4151
- return filePath.trim().replaceAll("\\", "/").toLowerCase();
4152
- }
4153
- function getCodexContextFileDisplayBasename(filePath) {
4154
- return filePath.trim().replaceAll("\\", "/").split("/").pop()?.trim() ?? "";
4155
- }
4156
- function getCodexContextFileBasename(filePath) {
4157
- return normalizeCodexContextFilePath(filePath).split("/").pop() ?? "";
4158
- }
4159
- function normalizeCodexDynamicToolName(name) {
4160
- return name.trim().toLowerCase();
4161
- }
4162
- function isNonEmptyString$1(value) {
4163
- return typeof value === "string" && value.length > 0;
4164
- }
4165
- //#endregion
4166
3491
  //#region extensions/codex/src/app-server/run-attempt-state.ts
4167
3492
  async function clearCodexBindingAfterInvalidImagePayload(bindingStore, identity, fields) {
4168
3493
  const currentBinding = await bindingStore.read(identity);
@@ -4209,21 +3534,6 @@ function isCodexActiveCompactTurnError(error) {
4209
3534
  const codexErrorInfo = isJsonObject(data?.codexErrorInfo) ? data.codexErrorInfo : void 0;
4210
3535
  return (isJsonObject(codexErrorInfo?.activeTurnNotSteerable) ? codexErrorInfo.activeTurnNotSteerable : void 0)?.turnKind === "compact";
4211
3536
  }
4212
- function readCodexFinalizationHookNotification(notification, threadId, turnId) {
4213
- if (notification.method !== "hook/started" && notification.method !== "hook/completed") return;
4214
- const params = isJsonObject(notification.params) ? notification.params : void 0;
4215
- const run = params && isJsonObject(params.run) ? params.run : void 0;
4216
- if (params?.threadId !== threadId || params.turnId !== turnId || run?.eventName !== "stop" && run?.eventName !== "subagentStop" || typeof run.id !== "string" || !run.id) return;
4217
- if (notification.method === "hook/started") return {
4218
- phase: "started",
4219
- runId: run.id
4220
- };
4221
- return {
4222
- phase: "completed",
4223
- runId: run.id,
4224
- status: typeof run.status === "string" ? run.status : void 0
4225
- };
4226
- }
4227
3537
  function joinPresentSections(...sections) {
4228
3538
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
4229
3539
  }
@@ -4231,34 +3541,6 @@ function prependCurrentInboundContext(prompt, context) {
4231
3541
  const text = context?.text.trim();
4232
3542
  return text ? [neutralizeCodexExplicitMentionSigils(text), prompt].filter(Boolean).join("\n\n") : prompt;
4233
3543
  }
4234
- function buildCodexAppServerTimeoutDiagnostics(params) {
4235
- const readNonBlankDetailString = (key) => {
4236
- const value = params.details?.[key];
4237
- return hasNonEmptyString(value) ? value : void 0;
4238
- };
4239
- const activeAppServerTurnRequests = asFiniteNumber(params.details?.activeAppServerTurnRequests);
4240
- const activeTurnItemCount = asFiniteNumber(params.details?.activeTurnItemCount);
4241
- const terminalTurnNotificationQueued = asBoolean(params.details?.terminalTurnNotificationQueued);
4242
- const completionIdleWatchArmed = asBoolean(params.details?.completionIdleWatchArmed);
4243
- const assistantCompletionIdleWatchArmed = asBoolean(params.details?.assistantCompletionIdleWatchArmed);
4244
- const terminalIdleWatchArmed = asBoolean(params.details?.terminalIdleWatchArmed);
4245
- return {
4246
- ...params.idleMs !== void 0 ? { idleMs: params.idleMs } : {},
4247
- ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
4248
- ...params.lastActivityReason ? { lastActivityReason: params.lastActivityReason } : {},
4249
- ...readNonBlankDetailString("lastNotificationMethod") ? { lastNotificationMethod: readNonBlankDetailString("lastNotificationMethod") } : {},
4250
- ...readNonBlankDetailString("lastNotificationItemId") ? { lastNotificationItemId: readNonBlankDetailString("lastNotificationItemId") } : {},
4251
- ...readNonBlankDetailString("lastNotificationItemType") ? { lastNotificationItemType: readNonBlankDetailString("lastNotificationItemType") } : {},
4252
- ...readNonBlankDetailString("lastNotificationItemRole") ? { lastNotificationItemRole: readNonBlankDetailString("lastNotificationItemRole") } : {},
4253
- ...readNonBlankDetailString("lastAssistantTextPreview") ? { lastAssistantTextPreview: readNonBlankDetailString("lastAssistantTextPreview") } : {},
4254
- ...activeAppServerTurnRequests !== void 0 ? { activeAppServerTurnRequests } : {},
4255
- ...activeTurnItemCount !== void 0 ? { activeTurnItemCount } : {},
4256
- ...terminalTurnNotificationQueued !== void 0 ? { terminalTurnNotificationQueued } : {},
4257
- ...completionIdleWatchArmed !== void 0 ? { completionIdleWatchArmed } : {},
4258
- ...assistantCompletionIdleWatchArmed !== void 0 ? { assistantCompletionIdleWatchArmed } : {},
4259
- ...terminalIdleWatchArmed !== void 0 ? { terminalIdleWatchArmed } : {}
4260
- };
4261
- }
4262
3544
  //#endregion
4263
3545
  //#region extensions/codex/src/app-server/run-attempt-context.ts
4264
3546
  async function prepareCodexAttemptContext(runtime, attemptTools) {
@@ -4292,9 +3574,17 @@ async function prepareCodexAttemptContext(runtime, attemptTools) {
4292
3574
  sessionKey: contextSessionKey,
4293
3575
  sessionId: params.sessionId,
4294
3576
  workspaceDir: params.workspaceDir,
4295
- messageProvider: params.messageProvider ?? void 0,
4296
3577
  trigger: params.trigger,
4297
- channelId: hookChannelId,
3578
+ ...buildAgentHookContextChannelFields({
3579
+ sessionKey: contextSessionKey,
3580
+ messageChannel: params.messageChannel,
3581
+ messageProvider: params.messageProvider,
3582
+ currentChannelId: hookChannelId,
3583
+ messageTo: params.messageTo,
3584
+ senderId: params.senderId,
3585
+ agentAccountId: params.agentAccountId
3586
+ }),
3587
+ channelContext: params.channelContext,
4298
3588
  ...hookContextWindowFields
4299
3589
  };
4300
3590
  const hookRunner = getAgentHarnessHookRunner();
@@ -4510,7 +3800,7 @@ function createCodexModelCallDiagnosticEmitter(params) {
4510
3800
  }
4511
3801
  /** Classifies model-call failures into timeout/abort buckets for diagnostics. */
4512
3802
  function classifyCodexModelCallFailureKind(params) {
4513
- if (params.timedOut || params.turnCompletionIdleTimedOut) return "timeout";
3803
+ if (params.timedOut) return "timeout";
4514
3804
  const errorMessage = params.error ? params.formatError(params.error).toLowerCase() : "";
4515
3805
  if (errorMessage.includes("timed out") || errorMessage.includes("timeout")) return "timeout";
4516
3806
  if (params.runAborted && !params.clientClosedAbort) return (typeof params.abortReason === "string" ? params.abortReason.toLowerCase() : params.abortReason ? params.formatError(params.abortReason).toLowerCase() : "").includes("timeout") ? "timeout" : "aborted";
@@ -4518,38 +3808,17 @@ function classifyCodexModelCallFailureKind(params) {
4518
3808
  }
4519
3809
  //#endregion
4520
3810
  //#region extensions/codex/src/app-server/attempt-results.ts
4521
- const CODEX_APP_SERVER_MISSING_TERMINAL_EVENT_USER_MESSAGE = "Codex stopped before confirming the turn was complete. The response may be incomplete; retry if needed.";
4522
- const CODEX_APP_SERVER_MISSING_TERMINAL_EVENT_SIDE_EFFECT_USER_MESSAGE = "Codex stopped before confirming the turn was complete. Some work may already have been performed; verify the current state before retrying.";
4523
- const CODEX_APP_SERVER_TERMINAL_IDLE_USER_MESSAGE = "Codex stopped responding: no activity arrived for the turn's liveness window, so the turn was ended and the connection was replaced. Retry to continue on a fresh session.";
4524
3811
  /** Joins terminal assistant text blocks into the final attempt answer. */
4525
3812
  function collectTerminalAssistantText(result) {
4526
3813
  return result.assistantTexts.join("\n\n").trim();
4527
3814
  }
4528
- /**
4529
- * Builds the user-facing timeout outcome when Codex stops without a terminal
4530
- * turn event.
4531
- */
4532
- function buildCodexAppServerPromptTimeoutOutcome(params) {
4533
- if (!params.turnCompletionIdleTimedOut) return;
4534
- if (params.turnWatchTimeoutKind === "terminal") {
4535
- if (collectTerminalAssistantText(params.result)) return;
4536
- const terminalReplayBlockedReason = resolveCodexAppServerReplayBlockedReason(params.result);
4537
- return {
4538
- message: CODEX_APP_SERVER_TERMINAL_IDLE_USER_MESSAGE,
4539
- ...terminalReplayBlockedReason ? {
4540
- replayInvalid: true,
4541
- livenessState: "abandoned"
4542
- } : {}
4543
- };
4544
- }
4545
- if (params.turnWatchTimeoutKind !== void 0 && params.turnWatchTimeoutKind !== "completion") return;
4546
- const replayBlockedReason = resolveCodexAppServerReplayBlockedReason(params.result);
3815
+ /** Reports the owner's deadline without guessing whether native work finished. */
3816
+ function buildCodexAppServerPromptTimeoutOutcome(timeout) {
3817
+ if (!timeout) return;
4547
3818
  return {
4548
- message: replayBlockedReason === "tool_activity" || replayBlockedReason === "potential_side_effect" || replayBlockedReason === "active_item" ? CODEX_APP_SERVER_MISSING_TERMINAL_EVENT_SIDE_EFFECT_USER_MESSAGE : CODEX_APP_SERVER_MISSING_TERMINAL_EVENT_USER_MESSAGE,
4549
- ...replayBlockedReason ? {
4550
- replayInvalid: true,
4551
- livenessState: "abandoned"
4552
- } : {}
3819
+ message: timeout.kind === "execution" ? "Codex reached the configured execution time limit. Some work may already have been performed; verify the current state before continuing." : "Codex finished its turn, but OpenClaw could not finish processing the result. Some work may already have been performed; verify the current state before continuing.",
3820
+ replayInvalid: true,
3821
+ livenessState: "abandoned"
4553
3822
  };
4554
3823
  }
4555
3824
  /** Explains why an incomplete app-server turn cannot be safely replayed. */
@@ -4671,52 +3940,44 @@ async function finalizeCodexAttempt(resources, turnRuntime, lifecycle, notificat
4671
3940
  const { effectiveRuntimeProviderId, effectiveRuntimeModelId } = runtime;
4672
3941
  const { params, terminalState, runAbortController, activeContextEngine, bindingStore, bindingIdentity, appServer, usesSupervisionConnection, sessionAgentId, contextSessionKey, effectiveCwd, agentDir, attemptStartedAt, startupAuthProfileId } = connection;
4673
3942
  const { toolBridge, toolState } = attemptTools;
4674
- const { state, completion, pendingOpenClawDynamicToolCompletionIds, activeTurnItemIds, activeCompletionBlockerItemIds, activeFinalizationHookRunIds, turnWatches } = turnRuntime;
3943
+ const { state, completion, deadlines } = turnRuntime;
4675
3944
  const { emitLifecycleTerminal, buildLifecycleTerminalMeta } = lifecycle;
4676
3945
  const { drainNotificationQueue } = notifications;
4677
3946
  const { codexModelCallDiagnostics } = requestRuntime;
4678
3947
  const { activeTurnId, activeProjector, streamState, freezeRunTerminalOutcome, notifyUserMessagePersisted } = activeTurn;
4679
3948
  await completion;
4680
- await state.abortCleanup;
4681
- const drain = drainNotificationQueue();
4682
3949
  const abortGraceElapsed = createDeferred();
3950
+ let settlementClosed = false;
4683
3951
  let abortGraceTimer;
4684
- const abortListener = addAbortListener(runAbortController.signal, () => {
3952
+ const beginAbortGrace = () => {
3953
+ if (settlementClosed) return;
4685
3954
  abortGraceTimer = setTimeout(() => abortGraceElapsed.resolve(), TURN_FINALIZE_DRAIN_ABORT_GRACE_MS);
4686
3955
  abortGraceTimer.unref?.();
3956
+ };
3957
+ const abortListener = addAbortListener(runAbortController.signal, () => {
3958
+ state.abortCleanup.then(beginAbortGrace, beginAbortGrace);
4687
3959
  });
3960
+ const closeProjection = () => {
3961
+ state.projectionClosed = true;
3962
+ return activeProjector.closeProjection();
3963
+ };
3964
+ const settlement = drainNotificationQueue().then(closeProjection);
4688
3965
  try {
4689
- await Promise.race([drain, abortGraceElapsed.promise]);
3966
+ await Promise.race([settlement, abortGraceElapsed.promise]);
3967
+ if (runAbortController.signal.aborted) await state.abortCleanup;
4690
3968
  } finally {
3969
+ settlementClosed = true;
4691
3970
  abortListener[Symbol.dispose]();
4692
3971
  clearTimeout(abortGraceTimer);
4693
- }
4694
- const hasQuiescentCompletedAssistant = activeProjector.hasCompletedTerminalAssistantText() && state.activeAppServerTurnRequests === 0 && activeTurnItemIds.size === 0 && activeCompletionBlockerItemIds.size === 0 && pendingOpenClawDynamicToolCompletionIds.size === 0 && activeFinalizationHookRunIds.size === 0 && state.unsettledFinalizationHookCount === 0 && state.rejectedFinalizationHookAssistant === void 0;
4695
- const hasRecoverableCompletedAssistant = !turnWatches.isCompletionIdleWatchPinnedByTerminalError() && turnWatches.isAssistantCompletionIdleWatchArmed() && hasQuiescentCompletedAssistant;
4696
- const recoveredTurnWatchTimeout = state.turnCompletionIdleTimedOut && !terminalState.explicitCancellationObserved && hasRecoverableCompletedAssistant && activeProjector.recoverCompletedTerminalAssistantAfterTurnWatchTimeout();
4697
- if (recoveredTurnWatchTimeout) {
4698
- embeddedAgentLog.warn("codex app-server recovered completed assistant output after missing turn completion", {
4699
- threadId: resourceState.thread.threadId,
4700
- turnId: activeTurnId,
4701
- timeoutKind: state.turnWatchTimeoutKind,
4702
- idleMs: state.turnWatchTimeoutIdleMs,
4703
- timeoutMs: state.turnWatchTimeoutMs
4704
- });
4705
- trajectoryRecorder?.recordEvent("turn.watch_timeout_recovered", {
4706
- threadId: resourceState.thread.threadId,
4707
- turnId: activeTurnId,
4708
- timeoutKind: state.turnWatchTimeoutKind,
4709
- idleMs: state.turnWatchTimeoutIdleMs,
4710
- timeoutMs: state.turnWatchTimeoutMs
4711
- });
3972
+ deadlines.dispose();
3973
+ if (!state.projectionClosed) await resources.runCleanupStep("codex-transcript-checkpoint", closeProjection);
4712
3974
  }
4713
3975
  const result = activeProjector.buildResult(toolBridge.telemetry, { yieldDetected: toolState.yieldDetected });
4714
3976
  const projectedTerminal = attemptTerminal.project(result.terminal);
4715
- const effectiveTimedOut = state.timedOut && !recoveredTurnWatchTimeout;
4716
- const effectiveTurnCompletionIdleTimedOut = state.turnCompletionIdleTimedOut && !recoveredTurnWatchTimeout;
4717
- const isFinalAborted = () => terminalState.explicitCancellationObserved || !resourceState.executionDisconnectError && (projectedTerminal.aborted || runAbortController.signal.aborted && !state.clientClosedAbort && !recoveredTurnWatchTimeout);
4718
- const clientClosedPromptErrorForFinal = state.clientClosedPromptError && hasRecoverableCompletedAssistant ? void 0 : state.clientClosedPromptError;
4719
- let finalPromptError = resourceState.executionDisconnectError ?? clientClosedPromptErrorForFinal ?? (effectiveTurnCompletionIdleTimedOut ? state.turnCompletionIdleTimeoutMessage : effectiveTimedOut ? "codex app-server attempt timed out" : projectedTerminal.promptError);
3977
+ const effectiveTimedOut = state.timeout !== void 0;
3978
+ const isFinalAborted = () => terminalState.explicitCancellationObserved || !resourceState.executionDisconnectError && (projectedTerminal.aborted || runAbortController.signal.aborted && !state.clientClosedAbort);
3979
+ const clientClosedPromptErrorForFinal = state.clientClosedPromptError;
3980
+ let finalPromptError = resourceState.executionDisconnectError ?? clientClosedPromptErrorForFinal ?? (state.timeout?.kind === "settlement" ? "codex app-server terminal settlement timed out" : state.timeout?.kind === "execution" ? "codex app-server execution budget timed out" : projectedTerminal.promptError);
4720
3981
  const finalPromptErrorMessage = typeof finalPromptError === "string" ? finalPromptError : finalPromptError instanceof Error ? finalPromptError.message : finalPromptError ? formatErrorMessage(finalPromptError) : void 0;
4721
3982
  if (isInvalidCodexImagePayloadError(finalPromptErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(bindingStore, bindingIdentity, {
4722
3983
  phase: "turn_completed",
@@ -4759,33 +4020,23 @@ async function finalizeCodexAttempt(resources, turnRuntime, lifecycle, notificat
4759
4020
  });
4760
4021
  finalPromptError = resourceState.executionDisconnectError ?? finalPromptError;
4761
4022
  const finalPromptErrorSource = effectiveTimedOut || clientClosedPromptErrorForFinal ? "prompt" : projectedTerminal.promptErrorSource;
4762
- const codexAppServerFailureKind = clientClosedPromptErrorForFinal ? "client_closed_before_turn_completed" : effectiveTurnCompletionIdleTimedOut ? "turn_completion_idle_timeout" : void 0;
4023
+ const codexAppServerFailureKind = clientClosedPromptErrorForFinal ? "client_closed_before_turn_completed" : state.timeout?.kind === "settlement" ? "turn_settlement_timeout" : void 0;
4763
4024
  const replayBlockedReason = codexAppServerFailureKind ? resolveCodexAppServerReplayBlockedReason(result) : void 0;
4764
- const promptTimeoutOutcome = buildCodexAppServerPromptTimeoutOutcome({
4765
- result,
4766
- turnCompletionIdleTimedOut: effectiveTurnCompletionIdleTimedOut,
4767
- turnWatchTimeoutKind: state.turnWatchTimeoutKind
4768
- });
4769
- const failureDiagnostics = codexAppServerFailureKind === "client_closed_before_turn_completed" && state.clientClosedDiagnostic ? { transportError: state.clientClosedDiagnostic } : codexAppServerFailureKind === "turn_completion_idle_timeout" && state.turnWatchTimeoutKind === "completion" ? buildCodexAppServerTimeoutDiagnostics({
4770
- idleMs: state.turnWatchTimeoutIdleMs,
4771
- timeoutMs: state.turnWatchTimeoutMs,
4772
- lastActivityReason: state.turnWatchTimeoutLastActivityReason,
4773
- details: state.turnWatchTimeoutDetails
4774
- }) : void 0;
4025
+ const promptTimeoutOutcome = buildCodexAppServerPromptTimeoutOutcome(state.timeout);
4026
+ const failureDiagnostics = codexAppServerFailureKind === "client_closed_before_turn_completed" && state.clientClosedDiagnostic ? { transportError: state.clientClosedDiagnostic } : state.timeout?.kind === "settlement" ? { timeoutMs: state.timeout.timeoutMs } : void 0;
4775
4027
  const codexAppServerFailure = codexAppServerFailureKind ? {
4776
4028
  kind: codexAppServerFailureKind,
4777
- ...codexAppServerFailureKind === "turn_completion_idle_timeout" && state.turnWatchTimeoutKind ? { turnWatchTimeoutKind: state.turnWatchTimeoutKind } : {},
4778
4029
  transport: appServer.start.transport,
4779
4030
  threadId: resourceState.thread.threadId,
4780
4031
  turnId: activeTurnId,
4781
- replaySafe: replayBlockedReason === void 0,
4032
+ replaySafe: codexAppServerFailureKind === "client_closed_before_turn_completed" && replayBlockedReason === void 0,
4782
4033
  ...replayBlockedReason ? { replayBlockedReason } : {},
4783
4034
  ...failureDiagnostics ? { diagnostics: failureDiagnostics } : {}
4784
4035
  } : void 0;
4785
4036
  const finalAborted = isFinalAborted();
4786
4037
  const completedTurnStatus = activeProjector.getCompletedTurnStatus();
4787
- const locallyCompletedTurn = state.completed && (state.localCompletionRequested || !state.terminalTurnNotificationQueued) && !state.timedOut && clientClosedPromptErrorForFinal === void 0;
4788
- const turnSucceeded = !finalAborted && !effectiveTimedOut && (finalPromptError === null || finalPromptError === void 0) && (completedTurnStatus === "completed" || recoveredTurnWatchTimeout || locallyCompletedTurn);
4038
+ const locallyCompletedTurn = state.completed && state.localCompletionRequested && !state.timeout && clientClosedPromptErrorForFinal === void 0;
4039
+ const turnSucceeded = !finalAborted && !effectiveTimedOut && (finalPromptError === null || finalPromptError === void 0) && (completedTurnStatus === "completed" || locallyCompletedTurn);
4789
4040
  if (toolBridge.telemetry.messagingToolSentTargets.some((target) => target.sourceReplyFinal === true)) result.agentHarnessResultClassification = void 0;
4790
4041
  const attemptSucceeded = turnSucceeded && result.agentHarnessResultClassification === void 0;
4791
4042
  terminalState.turnSucceeded = turnSucceeded;
@@ -4799,7 +4050,6 @@ async function finalizeCodexAttempt(resources, turnRuntime, lifecycle, notificat
4799
4050
  const modelCallFailureKind = classifyCodexModelCallFailureKind({
4800
4051
  error: finalPromptError,
4801
4052
  timedOut: effectiveTimedOut,
4802
- turnCompletionIdleTimedOut: effectiveTurnCompletionIdleTimedOut,
4803
4053
  runAborted: finalAborted,
4804
4054
  abortReason: terminalState.explicitCancellationReason ?? runAbortController.signal.reason,
4805
4055
  clientClosedAbort: state.clientClosedAbort,
@@ -4952,9 +4202,6 @@ async function finalizeCodexAttempt(resources, turnRuntime, lifecycle, notificat
4952
4202
  }
4953
4203
  //#endregion
4954
4204
  //#region extensions/codex/src/app-server/attempt-notification-state.ts
4955
- /**
4956
- * State machine for Codex app-server turn notifications and idle-watch updates.
4957
- */
4958
4205
  /** Emits coarse execution phases exactly once from app-server notifications. */
4959
4206
  function reportCodexExecutionNotification(params) {
4960
4207
  const { notification } = params;
@@ -4978,82 +4225,7 @@ function reportCodexExecutionNotification(params) {
4978
4225
  }
4979
4226
  /** Returns true when a notification ends the current app-server turn. */
4980
4227
  function isTerminalCodexTurnNotificationForTurn(params) {
4981
- if (!isCodexNotificationForTurn(params.notification.params, params.threadId, params.turnId)) return false;
4982
- return params.notification.method === "turn/completed";
4983
- }
4984
- /**
4985
- * Applies one notification to active item tracking, idle watches, and terminal
4986
- * turn state.
4987
- */
4988
- function applyCodexTurnNotificationState(params) {
4989
- const { notification, turnWatches } = params;
4990
- const isCurrentTurnNotification = isCodexNotificationForTurn(notification.params, params.threadId, params.turnId);
4991
- const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
4992
- let turnCrossedToolHandoff = params.turnCrossedToolHandoff;
4993
- if (isCurrentTurnNotification) {
4994
- updateActiveTurnItemIds(notification, params.activeTurnItemIds);
4995
- updateActiveCompletionBlockerItemIds(notification, params.activeCompletionBlockerItemIds);
4996
- turnWatches.touchActivity(`notification:${notification.method}`, {
4997
- details: describeNotificationActivity(notification),
4998
- attemptProgress: true
4999
- });
5000
- params.onReportExecutionNotification(notification);
5001
- if (notification.method === "item/completed" && params.activeTurnItemIds.size === 0) params.onScheduleTerminalDynamicToolReleaseCheck();
5002
- }
5003
- const unblockedAssistantCompletionRelease = isCurrentTurnNotification && turnWatches.isAssistantCompletionIdleWatchArmed() && notification.method === "item/completed" && params.activeTurnItemIds.size === 0;
5004
- const trackedDynamicToolCompletion = isPendingOpenClawDynamicToolCompletionNotification(notification, params.pendingOpenClawDynamicToolCompletionIds);
5005
- const rawToolOutputCompletion = isRawToolOutputCompletionNotification(notification);
5006
- if (isCurrentTurnNotification && (rawToolOutputCompletion || isNativeToolProgressNotification(notification))) turnCrossedToolHandoff = true;
5007
- const assistantCompletionCanRelease = isAssistantCompletionReleaseNotification(notification, turnCrossedToolHandoff);
5008
- const postToolProgressNeedsTerminalGuard = isCurrentTurnNotification && turnCrossedToolHandoff && ((isRawAssistantProgressNotification(notification) || isRawReasoningCompletionNotification(notification)) && params.activeTurnItemIds.size === 0 || isReasoningProgressNotification(notification));
5009
- const postToolPatchUpdateNeedsTerminalGuard = isCurrentTurnNotification && turnCrossedToolHandoff && isFileChangePatchUpdatedNotification(notification);
5010
- const rawResponseItemCompletedWithNoActiveItems = isCurrentTurnNotification && notification.method === "rawResponseItem/completed" && params.activeTurnItemIds.size === 0 && params.activeAppServerTurnRequests === 0 && !assistantCompletionCanRelease && !postToolProgressNeedsTerminalGuard && !rawToolOutputCompletion;
5011
- const shouldArmNoToolPostProgressReplyWatch = isCurrentTurnNotification && !turnCrossedToolHandoff && params.activeTurnItemIds.size === 0 && (isReasoningItemCompletionNotification(notification) || isAssistantCommentaryCompletionNotification(notification));
5012
- const shouldArmNoToolPostRawProgressReplyWatch = !turnCrossedToolHandoff && rawResponseItemCompletedWithNoActiveItems && (isRawReasoningCompletionNotification(notification) || isRawAssistantProgressNotification(notification));
5013
- const shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem = isCurrentTurnNotification && notification.method === "item/completed" && params.activeTurnItemIds.size === 0 && !trackedDynamicToolCompletion && !assistantCompletionCanRelease && !shouldArmNoToolPostProgressReplyWatch;
5014
- const shouldUsePostToolContinuationWatch = turnCrossedToolHandoff && (postToolProgressNeedsTerminalGuard || postToolPatchUpdateNeedsTerminalGuard || rawToolOutputCompletion || trackedDynamicToolCompletion || shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem);
5015
- const armPostToolContinuationWatch = () => {
5016
- turnWatches.armCompletionIdleWatch({ timeoutMs: params.postToolRawAssistantCompletionIdleTimeoutMs });
5017
- turnWatches.extendAttemptIdleWatch(params.postToolRawAssistantCompletionIdleTimeoutMs);
5018
- };
5019
- const armPostProgressReplyWatch = () => {
5020
- turnWatches.armCompletionIdleWatch({ timeoutMs: CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS });
5021
- turnWatches.extendAttemptIdleWatch(CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS);
5022
- };
5023
- if (isCurrentTurnNotification && notification.method === "error") {
5024
- if (isRetryableErrorNotification(notification.params)) turnWatches.disarmCompletionIdleWatch();
5025
- else turnWatches.armCompletionIdleWatch({ pinnedByTerminalError: true });
5026
- turnWatches.disarmAssistantCompletionIdleWatch();
5027
- } else if (isTurnCompletion) turnWatches.disarmAssistantCompletionIdleWatch();
5028
- else if (isCurrentTurnNotification && assistantCompletionCanRelease) turnWatches.armAssistantCompletionIdleWatch(describeNotificationActivity(notification));
5029
- else if (postToolProgressNeedsTerminalGuard || postToolPatchUpdateNeedsTerminalGuard) armPostToolContinuationWatch();
5030
- else if (shouldArmNoToolPostProgressReplyWatch || shouldArmNoToolPostRawProgressReplyWatch) armPostProgressReplyWatch();
5031
- else if (trackedDynamicToolCompletion) armPostToolContinuationWatch();
5032
- else if (unblockedAssistantCompletionRelease) turnWatches.armAssistantCompletionIdleWatch(describeNotificationActivity(notification));
5033
- else if (shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) {
5034
- if (shouldUsePostToolContinuationWatch) armPostToolContinuationWatch();
5035
- else turnWatches.armCompletionIdleWatch();
5036
- } else if (rawResponseItemCompletedWithNoActiveItems) turnWatches.armCompletionIdleWatch();
5037
- else if (isCurrentTurnNotification && rawToolOutputCompletion) armPostToolContinuationWatch();
5038
- else if (isCurrentTurnNotification && shouldDisarmAssistantCompletionIdleWatch(notification)) turnWatches.disarmAssistantCompletionIdleWatch();
5039
- if (turnWatches.isCompletionIdleWatchArmed() && !turnWatches.isCompletionIdleWatchPinnedByTerminalError() && notification.method !== "turn/completed" && isCurrentTurnNotification && !trackedDynamicToolCompletion && !rawToolOutputCompletion && !postToolProgressNeedsTerminalGuard && !postToolPatchUpdateNeedsTerminalGuard && !rawResponseItemCompletedWithNoActiveItems && !shouldArmNoToolPostProgressReplyWatch && !shouldArmNoToolPostRawProgressReplyWatch && !shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) turnWatches.disarmCompletionIdleWatch();
5040
- if (trackedDynamicToolCompletion) {
5041
- const itemId = readNotificationItemId(notification);
5042
- if (itemId) {
5043
- params.pendingOpenClawDynamicToolCompletionIds.delete(itemId);
5044
- params.onScheduleTerminalDynamicToolReleaseCheck();
5045
- }
5046
- }
5047
- return {
5048
- isCurrentTurnNotification,
5049
- isTurnAbortMarker: isCurrentTurnNotification && isCodexTurnAbortMarkerNotification(notification, { currentPromptTexts: params.currentPromptTexts }),
5050
- isTurnTerminal: isTerminalCodexTurnNotificationForTurn({
5051
- notification,
5052
- threadId: params.threadId,
5053
- turnId: params.turnId
5054
- }),
5055
- turnCrossedToolHandoff
5056
- };
4228
+ return params.notification.method === "turn/completed" && isCodexNotificationForTurn(params.notification.params, params.threadId, params.turnId) && readCodexTurnCompletedNotification(params.notification.params) !== void 0;
5057
4229
  }
5058
4230
  //#endregion
5059
4231
  //#region extensions/codex/src/app-server/run-attempt-lifecycle-terminal.ts
@@ -5248,10 +4420,9 @@ function createCodexAttemptNotificationController(resources, turnRuntime, lifecy
5248
4420
  const { prompt, state: resourceState, projectorRef, registerNativeSubagentMonitor } = resources;
5249
4421
  const { context, turnState } = prompt;
5250
4422
  const { attemptTools, runtime } = context;
5251
- const { connection } = runtime;
5252
- const { appServer, runAbortController } = connection;
4423
+ const { appServer, runAbortController } = runtime.connection;
5253
4424
  const { allocateCodexToolOutcomeOrdinal } = attemptTools;
5254
- const { state, turnIdRef, userInputBridgeRef, steeringQueueRef, turnWatches, activeTurnItemIds, activeCompletionBlockerItemIds, activeFinalizationHookRunIds, finalizationHookBatchStatuses, pendingOpenClawDynamicToolCompletionIds, postToolRawAssistantCompletionIdleTimeoutMs, completeTurn } = turnRuntime;
4425
+ const { state, turnIdRef, userInputBridgeRef, steeringQueueRef, activeTurnItemIds, pendingOpenClawDynamicToolCompletionIds, completeTurn, noteProgress, deadlines } = turnRuntime;
5255
4426
  const { scheduleTerminalDynamicToolReleaseCheck, reportExecutionNotification, maybeAnnounceFastModeAutoOff } = lifecycle;
5256
4427
  const isTerminalTurnNotificationForTurn = (notification, notificationTurnId) => isTerminalCodexTurnNotificationForTurn({
5257
4428
  notification,
@@ -5259,81 +4430,47 @@ function createCodexAttemptNotificationController(resources, turnRuntime, lifecy
5259
4430
  turnId: notificationTurnId
5260
4431
  });
5261
4432
  const handleNotification = async (notification) => {
4433
+ if (state.projectionClosed) return;
5262
4434
  const projector = projectorRef.current;
5263
4435
  const turnId = turnIdRef.current;
5264
- const steeringQueue = steeringQueueRef.current;
5265
4436
  userInputBridgeRef.current?.handleNotification(notification);
5266
4437
  if (!projector || !turnId) {
5267
4438
  if (notification.method === "error") state.latestStartupErrorNotification = notification;
5268
4439
  return;
5269
4440
  }
5270
- if ((state.timedOut || state.localCompletionRequested) && notification.method === "turn/completed" && readCodexTurnCompletedNotification(notification.params)?.turn.status === "interrupted") {
4441
+ const isCurrentTurn = isCodexNotificationForTurn(notification.params, resourceState.thread.threadId, turnId);
4442
+ const isTerminal = isTerminalTurnNotificationForTurn(notification, turnId);
4443
+ if ((state.timeout || state.localCompletionRequested) && isTerminal && readCodexTurnCompletedNotification(notification.params)?.turn.status === "interrupted") {
5271
4444
  completeTurn();
5272
4445
  return;
5273
4446
  }
5274
- const notificationState = applyCodexTurnNotificationState({
5275
- notification,
5276
- threadId: resourceState.thread.threadId,
5277
- turnId,
5278
- currentPromptTexts: [turnState.codexTurnPromptText],
5279
- turnWatches,
5280
- activeTurnItemIds,
5281
- activeCompletionBlockerItemIds,
5282
- activeAppServerTurnRequests: state.activeAppServerTurnRequests,
5283
- pendingOpenClawDynamicToolCompletionIds,
5284
- turnCrossedToolHandoff: state.turnCrossedToolHandoff,
5285
- postToolRawAssistantCompletionIdleTimeoutMs,
5286
- onScheduleTerminalDynamicToolReleaseCheck: scheduleTerminalDynamicToolReleaseCheck,
5287
- onReportExecutionNotification: reportExecutionNotification
5288
- });
5289
- state.turnCrossedToolHandoff = notificationState.turnCrossedToolHandoff;
5290
- if (notificationState.isCurrentTurnNotification && notification.method === "item/completed") {
5291
- const item = readCodexNotificationItem(notification.params);
5292
- if (item?.type === "userMessage" && typeof item.clientId === "string") await steeringQueue?.confirmConsumed(item.clientId);
5293
- }
5294
- if (notificationState.isTurnAbortMarker) state.sawCodexInterruptMarker = true;
5295
- const hookNotification = readCodexFinalizationHookNotification(notification, resourceState.thread.threadId, turnId);
5296
- if (hookNotification?.phase === "started") {
5297
- if (activeFinalizationHookRunIds.size === 0) finalizationHookBatchStatuses.clear();
5298
- activeFinalizationHookRunIds.add(hookNotification.runId);
5299
- turnWatches.disarmAssistantCompletionIdleWatch();
5300
- }
5301
- if (notificationState.isTurnTerminal) state.terminalTurnNotificationQueued = true;
4447
+ state.activeLocalProjections += 1;
5302
4448
  try {
5303
- await projector.handleNotification(notification);
5304
- const canRelease = isAssistantCompletionReleaseNotification(notification, state.turnCrossedToolHandoff) || notificationState.isCurrentTurnNotification && state.turnCrossedToolHandoff && notification.method === "rawResponseItem/completed" && projector.canReleaseLatestTerminalAssistantAfterToolHandoff();
5305
- if (notificationState.isCurrentTurnNotification && canRelease) {
5306
- const itemId = projector.getLatestTerminalAssistantCandidate()?.itemId;
5307
- if (state.rejectedFinalizationHookAssistant && itemId && itemId !== state.rejectedFinalizationHookAssistant.itemId) state.rejectedFinalizationHookAssistant = void 0;
5308
- else if (state.rejectedFinalizationHookAssistant) turnWatches.disarmAssistantCompletionIdleWatch();
5309
- else if (activeFinalizationHookRunIds.size === 0 && !state.terminalTurnNotificationQueued && state.activeAppServerTurnRequests === 0 && activeTurnItemIds.size === 0 && activeCompletionBlockerItemIds.size === 0 && pendingOpenClawDynamicToolCompletionIds.size === 0 && projector.hasLatestTerminalAssistantCandidateText()) turnWatches.armAssistantCompletionIdleWatch(describeNotificationActivity(notification));
4449
+ if (isCurrentTurn) {
4450
+ updateActiveTurnItemIds(notification, activeTurnItemIds);
4451
+ noteProgress(`notification:${notification.method}`);
4452
+ reportExecutionNotification(notification);
4453
+ if (isPendingOpenClawDynamicToolCompletionNotification(notification, pendingOpenClawDynamicToolCompletionIds)) {
4454
+ const itemId = readNotificationItemId(notification);
4455
+ if (itemId) pendingOpenClawDynamicToolCompletionIds.delete(itemId);
4456
+ }
4457
+ if (notification.method === "item/completed") {
4458
+ if (activeTurnItemIds.size === 0) scheduleTerminalDynamicToolReleaseCheck();
4459
+ const item = readCodexNotificationItem(notification.params);
4460
+ if (item?.type === "userMessage" && typeof item.clientId === "string") await steeringQueueRef.current?.confirmConsumed(item.clientId);
4461
+ }
4462
+ if (isCodexTurnAbortMarkerNotification(notification, { currentPromptTexts: [turnState.codexTurnPromptText] })) state.sawCodexInterruptMarker = true;
5310
4463
  }
5311
- if (notificationState.isCurrentTurnNotification && activeTurnItemIds.size === 0 && isRawFunctionToolOutputCompletionNotification(notification)) await maybeAnnounceFastModeAutoOff();
4464
+ await projector.handleNotification(notification);
4465
+ if (isCurrentTurn && activeTurnItemIds.size === 0 && isRawFunctionToolOutputCompletionNotification(notification)) await maybeAnnounceFastModeAutoOff();
5312
4466
  } catch (error) {
5313
4467
  embeddedAgentLog.debug("codex app-server projector notification threw", {
5314
4468
  method: notification.method,
5315
4469
  error
5316
4470
  });
5317
4471
  } finally {
5318
- if (hookNotification?.phase === "completed") {
5319
- state.unsettledFinalizationHookCount = Math.max(0, state.unsettledFinalizationHookCount - 1);
5320
- activeFinalizationHookRunIds.delete(hookNotification.runId);
5321
- finalizationHookBatchStatuses.set(hookNotification.runId, hookNotification.status);
5322
- if (activeFinalizationHookRunIds.size === 0) {
5323
- const statuses = new Set(finalizationHookBatchStatuses.values());
5324
- if (statuses.has("blocked") && !statuses.has("stopped")) {
5325
- const itemId = projector.getLatestTerminalAssistantCandidate()?.itemId;
5326
- state.rejectedFinalizationHookAssistant = itemId ? { itemId } : {};
5327
- turnWatches.disarmAssistantCompletionIdleWatch();
5328
- } else state.rejectedFinalizationHookAssistant = void 0;
5329
- }
5330
- if (activeFinalizationHookRunIds.size === 0 && state.rejectedFinalizationHookAssistant === void 0 && !state.terminalTurnNotificationQueued && state.activeAppServerTurnRequests === 0 && activeTurnItemIds.size === 0 && activeCompletionBlockerItemIds.size === 0 && pendingOpenClawDynamicToolCompletionIds.size === 0 && projector.hasLatestTerminalAssistantCandidateText()) turnWatches.armAssistantCompletionIdleWatch({
5331
- lastNotificationMethod: notification.method,
5332
- hookRunId: hookNotification.runId,
5333
- hookStatus: hookNotification.status
5334
- });
5335
- }
5336
- if (notificationState.isTurnTerminal) {
4472
+ state.activeLocalProjections -= 1;
4473
+ if (isTerminal) {
5337
4474
  if ((readCodexTurnCompletedNotification(notification.params)?.turn)?.status === "interrupted" && state.sawCodexInterruptMarker) projector.markAborted();
5338
4475
  completeTurn();
5339
4476
  }
@@ -5358,10 +4495,12 @@ function createCodexAttemptNotificationController(resources, turnRuntime, lifecy
5358
4495
  const noteNotificationReceived = (notification, scope, receivedAtMs) => {
5359
4496
  const projector = projectorRef.current;
5360
4497
  const turnId = turnIdRef.current;
5361
- if (!projector || !turnId) return;
4498
+ if (!projector || !turnId || state.projectionClosed) return;
4499
+ projector.recordMcpToolCallReceipt(notification);
5362
4500
  if (isTerminalTurnNotificationForTurn(notification, turnId)) {
5363
4501
  state.terminalTurnNotificationQueued = true;
5364
4502
  steeringQueueRef.current?.sealAdmission();
4503
+ deadlines.beginSettlement(receivedAtMs);
5365
4504
  }
5366
4505
  if (scope.turnId === turnId) {
5367
4506
  const modelToolCallId = readRawResponseToolCallId(notification);
@@ -5369,11 +4508,6 @@ function createCodexAttemptNotificationController(resources, turnRuntime, lifecy
5369
4508
  const nativeItem = readCodexNotificationItem(notification.params);
5370
4509
  if (nativeItem?.type === "webSearch") projector.recordNativeToolOutcome(nativeItem);
5371
4510
  }
5372
- if (readCodexFinalizationHookNotification(notification, resourceState.thread.threadId, turnId)?.phase === "started") {
5373
- state.unsettledFinalizationHookCount += 1;
5374
- turnWatches.disarmAssistantCompletionIdleWatch();
5375
- }
5376
- turnWatches.noteNotificationReceived(notification.method, { receivedAtMs });
5377
4511
  };
5378
4512
  const enqueueNotification = async (notification, scope) => {
5379
4513
  embeddedAgentLog.trace("codex app-server raw notification received", {
@@ -5863,6 +4997,8 @@ function prepareCodexAttemptResources(prompt) {
5863
4997
  sessionId: params.sessionId,
5864
4998
  sessionKey: contextSessionKey,
5865
4999
  config: params.config,
5000
+ autoApproveMcpTools: shouldAutoApproveCodexAppServerApprovals(appServer),
5001
+ projectedMcpServers: runtime.bundleMcpThreadConfig.configPatch?.mcp_servers,
5866
5002
  runId: params.runId,
5867
5003
  channelId: hookChannelId,
5868
5004
  ...requester ? { requester } : {},
@@ -6004,297 +5140,6 @@ function prewarmCodexAttemptClient(params) {
6004
5140
  });
6005
5141
  }
6006
5142
  //#endregion
6007
- //#region extensions/codex/src/app-server/scheduled-app-authority.ts
6008
- const CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE = "codex.apps";
6009
- const CODEX_APPS_MCP_SERVER = "codex_apps";
6010
- const MCP_STATUS_PAGE_SIZE = 100;
6011
- const MCP_STATUS_MAX_PAGES = 100;
6012
- const CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS = 6e4;
6013
- const CODEX_APP_AUTHORITY_CAPTURE_MIN_TIMEOUT_MS = 100;
6014
- function resolveScheduledCodexAppCreatorCaptureDecision(params) {
6015
- if (!params.appsMayBeVisible) return {
6016
- required: false,
6017
- supported: false
6018
- };
6019
- const unavailableReason = params.authenticatedScheduledMode ? "A scheduled Codex continuation cannot create new app-authorized automations. Recreate it from a fresh authenticated owner turn; no automation changes were saved." : params.usesSupervisionConnection ? "Codex apps are visible through a supervised connection that cannot capture creator authority. Use an isolated prepared-profile Codex creator turn; no automation changes were saved." : params.homeScope === "user" ? "Codex apps are visible through a user-home runtime that cannot capture isolated creator authority. Use an agent-scoped prepared-profile Codex creator turn; no automation changes were saved." : !params.hasPreparedAccountIdentity ? "Codex app authority requires a genuine ChatGPT account identity. Reauthenticate the selected Codex profile, then retry; no automation changes were saved." : void 0;
6020
- return {
6021
- required: true,
6022
- supported: !unavailableReason,
6023
- ...unavailableReason ? { unavailableReason } : {}
6024
- };
6025
- }
6026
- function normalizeApprovalMode(value) {
6027
- return value === "allow" || value === "deny" || value === "auto" || value === "ask" ? value : void 0;
6028
- }
6029
- function normalizeAppToolApprovalMode(value) {
6030
- return value === "auto" || value === "prompt" || value === "writes" || value === "approve" ? value : void 0;
6031
- }
6032
- function defaultApprovalMode(entry) {
6033
- return entry.destructiveApprovalMode ?? (entry.allowDestructiveActions ? "allow" : "deny");
6034
- }
6035
- function parseScheduledCodexAppAuthority(authority) {
6036
- if (!authority || authority.runtimeId !== "codex") return;
6037
- if (authority.version !== 1) throw new Error("Unsupported Codex scheduled authority version; reauthorize this automation.");
6038
- if (authority.namespace !== CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE) throw new Error(`Unsupported Codex scheduled authority namespace ${authority.namespace}; reauthorize this automation.`);
6039
- const payload = asOptionalRecord(authority.payload);
6040
- const auth = asOptionalRecord(payload?.auth);
6041
- const profileId = normalizeOptionalString(auth?.profileId);
6042
- const accountId = normalizeOptionalString(auth?.accountId);
6043
- if (payload?.version !== 1 || !profileId || !accountId || !Array.isArray(payload.apps)) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
6044
- const seen = /* @__PURE__ */ new Set();
6045
- const apps = payload.apps.map((raw) => {
6046
- const app = asOptionalRecord(raw);
6047
- const id = normalizeOptionalString(app?.id);
6048
- const destructiveApprovalMode = normalizeApprovalMode(app?.destructiveApprovalMode);
6049
- const rawTools = asOptionalRecord(app?.tools);
6050
- if (!id || seen.has(id) || typeof app?.allowDestructiveActions !== "boolean" || typeof app.allowOpenWorld !== "boolean" || !destructiveApprovalMode || !rawTools) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
6051
- seen.add(id);
6052
- const tools = {};
6053
- for (const [name, rawMode] of Object.entries(rawTools)) {
6054
- const toolName = normalizeOptionalString(name);
6055
- const mode = normalizeAppToolApprovalMode(rawMode);
6056
- if (!toolName || !mode) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
6057
- tools[toolName] = mode;
6058
- }
6059
- return {
6060
- id,
6061
- allowDestructiveActions: app.allowDestructiveActions,
6062
- allowOpenWorld: app.allowOpenWorld,
6063
- destructiveApprovalMode,
6064
- tools
6065
- };
6066
- });
6067
- return {
6068
- version: 1,
6069
- auth: {
6070
- profileId,
6071
- accountId
6072
- },
6073
- apps
6074
- };
6075
- }
6076
- async function readCodexScheduledAppToolNamesByApp(params) {
6077
- const toolNamesByApp = /* @__PURE__ */ new Map();
6078
- const seenCursors = /* @__PURE__ */ new Set();
6079
- let cursor;
6080
- for (let page = 0; page < MCP_STATUS_MAX_PAGES; page += 1) {
6081
- const response = await params.request("mcpServerStatus/list", {
6082
- ...params.threadId ? { threadId: params.threadId } : {},
6083
- detail: "toolsAndAuthOnly",
6084
- limit: MCP_STATUS_PAGE_SIZE,
6085
- ...cursor ? { cursor } : {}
6086
- });
6087
- if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned invalid scheduled app inventory");
6088
- for (const status of response.data) {
6089
- if (!isJsonObject(status) || !isJsonObject(status.tools)) throw new Error("Codex scheduled app inventory contained an invalid server status");
6090
- if (status.name !== CODEX_APPS_MCP_SERVER) continue;
6091
- for (const [toolName, tool] of Object.entries(status.tools)) {
6092
- const connectorId = readCodexMcpToolConnectorId(tool);
6093
- if (connectorId) {
6094
- const names = toolNamesByApp.get(connectorId) ?? /* @__PURE__ */ new Set();
6095
- names.add(toolName);
6096
- toolNamesByApp.set(connectorId, names);
6097
- }
6098
- }
6099
- }
6100
- if (response.nextCursor !== void 0 && response.nextCursor !== null && typeof response.nextCursor !== "string") throw new Error("Codex scheduled app inventory returned an invalid pagination cursor");
6101
- cursor = response.nextCursor;
6102
- if (!cursor) return toolNamesByApp;
6103
- if (seenCursors.has(cursor)) throw new Error("Codex app connector inventory repeated its pagination cursor");
6104
- seenCursors.add(cursor);
6105
- }
6106
- throw new Error("Codex app connector inventory exceeded its bounded page limit");
6107
- }
6108
- /** Reads the current account policy and connector-backed tool names under one caller deadline. */
6109
- async function readCurrentCodexScheduledAppPolicy$1(params) {
6110
- const [configResponse, toolNamesByApp] = await Promise.all([params.request("config/read", {
6111
- includeLayers: false,
6112
- ...params.configCwd ? { cwd: params.configCwd } : {}
6113
- }), readCodexScheduledAppToolNamesByApp(params)]);
6114
- if (!isJsonObject(configResponse)) throw new Error("Codex config/read returned an invalid scheduled app policy response");
6115
- return {
6116
- config: isJsonObject(configResponse.config) ? configResponse.config : {},
6117
- toolNamesByApp
6118
- };
6119
- }
6120
- function readToolApprovalMode(config, appId, toolName, fallback = "auto") {
6121
- const app = asOptionalRecord(asOptionalRecord(config.apps)?.[appId]);
6122
- return normalizeAppToolApprovalMode(asOptionalRecord(asOptionalRecord(app?.tools)?.[toolName])?.approval_mode) ?? fallback;
6123
- }
6124
- /** Captures only apps callable on the exact active Codex client/thread. */
6125
- async function captureScheduledCodexAppAuthority(params) {
6126
- const requestedTimeoutMs = params.timeoutMs ?? CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS;
6127
- const timeoutMs = Math.min(CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS, Math.max(CODEX_APP_AUTHORITY_CAPTURE_MIN_TIMEOUT_MS, Number.isFinite(requestedTimeoutMs) ? Math.floor(requestedTimeoutMs) : CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS));
6128
- const deadlineMs = Date.now() + timeoutMs;
6129
- const boundedClient = { request: ((method, requestParams) => {
6130
- const remainingTimeoutMs = deadlineMs - Date.now();
6131
- if (remainingTimeoutMs <= 0) throw new CodexScheduledAppAuthorityCaptureTimeoutError();
6132
- return params.client.request(method, requestParams, {
6133
- timeoutMs: remainingTimeoutMs,
6134
- signal: params.signal
6135
- });
6136
- }) };
6137
- let installed;
6138
- let currentPolicy;
6139
- try {
6140
- [installed, currentPolicy] = await withAbortableTimeout({
6141
- promise: Promise.all([boundedClient.request("app/installed", {
6142
- threadId: params.threadId,
6143
- forceRefresh: false
6144
- }), readCurrentCodexScheduledAppPolicy$1({
6145
- request: (method, requestParams) => boundedClient.request(method, requestParams),
6146
- threadId: params.threadId,
6147
- configCwd: params.configCwd
6148
- })]),
6149
- timeoutMs,
6150
- signal: params.signal,
6151
- timeoutMessage: "Codex scheduled app authority capture deadline elapsed",
6152
- createTimeoutError: () => new CodexScheduledAppAuthorityCaptureTimeoutError()
6153
- });
6154
- } catch (error) {
6155
- if (params.signal?.aborted || !(error instanceof CodexScheduledAppAuthorityCaptureTimeoutError) && !isCodexAppServerRequestTimeoutError(error)) throw error;
6156
- throw new Error(`Codex app authority capture exceeded its ${timeoutMs} ms total budget. No automation changes were saved; retry after Codex app inventory is responsive.`, { cause: error });
6157
- }
6158
- const callableIds = new Set(installed.apps.filter((app) => app.enabled && app.callable).map((app) => app.id));
6159
- const apps = Object.entries(params.policyContext.apps).filter(([id]) => callableIds.has(id) && currentPolicy.toolNamesByApp.has(id)).map(([id, policy]) => ({
6160
- id,
6161
- allowDestructiveActions: policy.allowDestructiveActions,
6162
- allowOpenWorld: policy.allowOpenWorld !== false,
6163
- destructiveApprovalMode: defaultApprovalMode(policy),
6164
- tools: Object.fromEntries([...currentPolicy.toolNamesByApp.get(id) ?? []].toSorted().map((toolName) => [toolName, readToolApprovalMode(currentPolicy.config, id, toolName, appApprovalCeiling(defaultApprovalMode(policy)))]))
6165
- })).toSorted((left, right) => left.id.localeCompare(right.id));
6166
- if (apps.length === 0) return;
6167
- return {
6168
- version: 1,
6169
- runtimeId: "codex",
6170
- namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
6171
- payload: {
6172
- version: 1,
6173
- auth: {
6174
- profileId: params.profileId,
6175
- accountId: params.accountId
6176
- },
6177
- apps
6178
- }
6179
- };
6180
- }
6181
- var CodexScheduledAppAuthorityCaptureTimeoutError = class extends Error {
6182
- constructor() {
6183
- super("Codex scheduled app authority capture deadline elapsed");
6184
- this.name = "CodexScheduledAppAuthorityCaptureTimeoutError";
6185
- }
6186
- };
6187
- const APPROVAL_RANK = {
6188
- deny: 0,
6189
- ask: 1,
6190
- auto: 2,
6191
- allow: 3
6192
- };
6193
- function stricterApprovalMode(left, right) {
6194
- return APPROVAL_RANK[left] <= APPROVAL_RANK[right] ? left : right;
6195
- }
6196
- function intersectToolApprovalMode(captured, current) {
6197
- if (captured === current) return captured;
6198
- if (captured === "prompt" || current === "prompt") return "prompt";
6199
- if (captured === "approve") return current;
6200
- if (current === "approve") return captured;
6201
- return "prompt";
6202
- }
6203
- function appApprovalCeiling(mode) {
6204
- if (mode === "allow") return "approve";
6205
- return mode === "ask" ? "prompt" : "auto";
6206
- }
6207
- function stableStringify(value) {
6208
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
6209
- if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
6210
- return JSON.stringify(value);
6211
- }
6212
- /** Intersects a stored app-ID cap with current policy without admitting new apps. */
6213
- function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authority, currentPolicy = {
6214
- config: {},
6215
- toolNamesByApp: /* @__PURE__ */ new Map()
6216
- }) {
6217
- const scheduled = parseScheduledCodexAppAuthority(authority);
6218
- if (!scheduled) return config;
6219
- const omittedAppIds = scheduled.apps.map((app) => app.id).filter((id) => {
6220
- const currentTools = currentPolicy.toolNamesByApp.get(id);
6221
- return !Object.hasOwn(config.policyContext.apps, id) || !currentTools || currentTools.size === 0;
6222
- }).toSorted();
6223
- if (omittedAppIds.length > 0) {
6224
- const visibleIds = omittedAppIds.slice(0, 10).join(", ");
6225
- const remaining = omittedAppIds.length - Math.min(omittedAppIds.length, 10);
6226
- throw new AgentHarnessPreflightError(`Scheduled Codex apps are unavailable under the current policy or account: ${visibleIds}${remaining > 0 ? ` (and ${remaining} more)` : ""}. Restore access or reauthorize the automation from a fresh authenticated Codex owner turn.`);
6227
- }
6228
- const capturedById = new Map(scheduled.apps.map((app) => [app.id, app]));
6229
- const apps = {};
6230
- for (const [id, current] of Object.entries(config.policyContext.apps)) {
6231
- const captured = capturedById.get(id);
6232
- if (!captured) continue;
6233
- apps[id] = {
6234
- ...current,
6235
- allowDestructiveActions: current.allowDestructiveActions && captured.allowDestructiveActions,
6236
- allowOpenWorld: current.allowOpenWorld !== false && captured.allowOpenWorld,
6237
- destructiveApprovalMode: stricterApprovalMode(defaultApprovalMode(current), captured.destructiveApprovalMode)
6238
- };
6239
- }
6240
- const pluginAppIds = Object.fromEntries(Object.entries(config.policyContext.pluginAppIds).map(([key, ids]) => [key, ids.filter((id) => Object.hasOwn(apps, id))]).filter(([, ids]) => ids.length > 0));
6241
- const policyContext = buildPluginAppPolicyContext(apps, pluginAppIds);
6242
- const configPatch = buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext);
6243
- const appsPatch = asOptionalRecord(configPatch.apps);
6244
- for (const [appId, captured] of capturedById) {
6245
- const appPatch = asOptionalRecord(appsPatch?.[appId]);
6246
- if (!appPatch || !Object.hasOwn(apps, appId)) continue;
6247
- const currentApp = apps[appId];
6248
- if (!currentApp) continue;
6249
- const storedAppCeiling = appApprovalCeiling(captured.destructiveApprovalMode);
6250
- const currentAppCeiling = appApprovalCeiling(defaultApprovalMode(currentApp));
6251
- const toolNames = currentPolicy.toolNamesByApp.get(appId) ?? /* @__PURE__ */ new Set();
6252
- appPatch.tools = Object.fromEntries([...toolNames].toSorted().map((toolName) => {
6253
- return [toolName, { approval_mode: intersectToolApprovalMode(intersectToolApprovalMode(captured.tools[toolName] ?? storedAppCeiling, storedAppCeiling), intersectToolApprovalMode(readToolApprovalMode(currentPolicy.config, appId, toolName, currentAppCeiling), currentAppCeiling)) }];
6254
- }));
6255
- }
6256
- const fingerprint = crypto.createHash("sha256").update(stableStringify({
6257
- version: 1,
6258
- namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
6259
- authority: scheduled,
6260
- inputFingerprint: config.inputFingerprint,
6261
- policyContext,
6262
- configPatch
6263
- })).digest("hex");
6264
- return {
6265
- ...config,
6266
- fingerprint,
6267
- configPatch,
6268
- provisionalAppIds: Object.keys(apps).toSorted(),
6269
- policyContext
6270
- };
6271
- }
6272
- function readScheduledCodexAppAuthorityAuth(authority) {
6273
- return parseScheduledCodexAppAuthority(authority)?.auth;
6274
- }
6275
- function assertScheduledCodexAppAuthorityRuntime(connection, params) {
6276
- const scheduledAuth = readScheduledCodexAppAuthorityAuth(params.scheduledRuntimeAuthority);
6277
- if (!scheduledAuth) return;
6278
- if (params.trigger !== "cron" || connection.usesSupervisionConnection || connection.appServer.start.homeScope === "user") throw new AgentHarnessPreflightError("This automation's Codex app authority requires an isolated scheduled prepared-profile runtime. Reauthorize it from a supported Codex creator turn.");
6279
- const prepared = connection.startupPreparedAuth;
6280
- if (prepared?.kind !== "profile" || prepared.profileId !== scheduledAuth.profileId || prepared.snapshot?.loginParams.type !== "chatgptAuthTokens" || prepared.snapshot.chatgptAccountId !== scheduledAuth.accountId) throw new AgentHarnessPreflightError(`This automation was authorized for Codex profile ${scheduledAuth.profileId}, but that exact prepared account is not active. Restore the profile or reauthorize the automation from a fresh owner turn.`);
6281
- }
6282
- function buildLegacyScheduledCodexAppRecoveryPrompt(params) {
6283
- if (params.trigger !== "cron" || !params.scheduledRuntimeAuthorityRecoveryRequired || params.scheduledRuntimeAuthority) return;
6284
- return "Scheduled Codex app access is unavailable because this automation predates runtime-specific app authority capture. Tell the operator to recreate or reauthorize it from a fresh authenticated Codex owner turn; do not claim an app action succeeded.";
6285
- }
6286
- /** Makes stored-cap identity part of thread reuse admission, including cap removal. */
6287
- function buildScheduledCodexAppAuthorityInputFingerprint(baseFingerprint, authority) {
6288
- const scheduled = parseScheduledCodexAppAuthority(authority);
6289
- if (!scheduled) return baseFingerprint;
6290
- return crypto.createHash("sha256").update(stableStringify({
6291
- version: 1,
6292
- namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
6293
- baseFingerprint,
6294
- authority: scheduled
6295
- })).digest("hex");
6296
- }
6297
- //#endregion
6298
5143
  //#region extensions/codex/src/app-server/scheduled-configured-mcp-authority.ts
6299
5144
  /** Limits fresh scheduled-authority capture to authenticated local durable operator turns. */
6300
5145
  function canResolveScheduledConfiguredMcpCreatorAuthority(params) {
@@ -6380,6 +5225,7 @@ async function prepareCodexAttemptRuntime(connection) {
6380
5225
  const bundleManifestRegistry = resolveCodexAttemptBundleManifestRegistry(params.preparedModelRuntime);
6381
5226
  const bundleMcpThreadConfig = await loadCodexBundleMcpThreadConfig({
6382
5227
  workspaceDir: effectiveWorkspace,
5228
+ agentId: sessionAgentId,
6383
5229
  cfg: params.config,
6384
5230
  toolsEnabled: usesSupervisionConnection || supportsModelTools(params.model),
6385
5231
  disableTools: params.disableTools,
@@ -6388,7 +5234,7 @@ async function prepareCodexAttemptRuntime(connection) {
6388
5234
  toolOverrides: codexMcpToolOverrides
6389
5235
  });
6390
5236
  const authenticatedScheduledMode = params.trigger === "cron" && params.scheduledToolPolicy !== void 0 && Array.isArray(params.toolsAllow);
6391
- const ownsScheduledConfiguredMcpSurface = authenticatedScheduledMode && (bundleMcpThreadConfig.staticServerNames.length > 0 || mutable.startupBinding?.configuredMcpOwnershipVersion === 1);
5237
+ const scheduledConfiguredMcpSurface = authenticatedScheduledMode && (bundleMcpThreadConfig.staticServerNames.length > 0 || mutable.startupBinding?.configuredMcpOwnershipVersion === 1);
6392
5238
  const cronCreatorAuthorityCapability = params.cronCreatorAuthorityCapability;
6393
5239
  const hasFreshCreatorAuthority = cronCreatorAuthorityCapability?.active === true && cronCreatorAuthorityCapability.runId === params.runId && !cronCreatorAuthorityCapability.signal.aborted;
6394
5240
  const mayResolveScheduledConfiguredMcpCreatorAuthority = !authenticatedScheduledMode && canResolveScheduledConfiguredMcpCreatorAuthority({
@@ -6415,6 +5261,7 @@ async function prepareCodexAttemptRuntime(connection) {
6415
5261
  runtimeSessionKey: sandboxSessionKey,
6416
5262
  sandboxExecServerEnabled
6417
5263
  });
5264
+ const configuredMcpSurface = scheduledConfiguredMcpSurface ? "scheduled" : !nativeToolSurfaceEnabled && bundleMcpThreadConfig.staticServerNames.length > 0 ? "transient" : void 0;
6418
5265
  preDynamicStartupStages.mark("native-tool-surface");
6419
5266
  const nativeProviderWebSearchSupport = resolveCodexWebSearchPlan({
6420
5267
  config: params.config,
@@ -6464,7 +5311,7 @@ async function prepareCodexAttemptRuntime(connection) {
6464
5311
  bundleMcpThreadConfig,
6465
5312
  bundleManifestRegistry,
6466
5313
  authenticatedScheduledMode,
6467
- ownsScheduledConfiguredMcpSurface,
5314
+ configuredMcpSurface,
6468
5315
  canResolveScheduledConfiguredMcpCreatorAuthority: mayResolveScheduledConfiguredMcpCreatorAuthority,
6469
5316
  codexMcpToolOverrides,
6470
5317
  sandboxExecServerEnabled,
@@ -6558,7 +5405,6 @@ function resolveCodexDynamicToolDirectNames(params, hostSystemAgentActive = fals
6558
5405
  const names = [];
6559
5406
  if (hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) names.push("openclaw");
6560
5407
  if (params.sourceReplyDeliveryMode === "message_tool_only") names.push("message");
6561
- names.push("progress_card");
6562
5408
  return names;
6563
5409
  }
6564
5410
  //#endregion
@@ -6574,37 +5420,32 @@ function createCodexAttemptServerRequestController(resources, turnRuntime, lifec
6574
5420
  const { runtime, attemptTools } = context;
6575
5421
  const { connection } = runtime;
6576
5422
  const { params, computerUseConfig, runAbortController, appServer, sessionAgentId } = connection;
5423
+ const autoApprove = shouldAutoApproveCodexAppServerApprovals(appServer);
6577
5424
  const { compactionPlanState, toolBridge, toolOutcomeOrdinals, suppressedDynamicToolOutcomeOrdinals, allocateCodexToolOutcomeOrdinal } = attemptTools;
6578
- const { state, turnIdRef, userInputBridgeRef, openClawDynamicToolExecutions, pendingOpenClawDynamicToolCompletionIds, postToolRawAssistantCompletionIdleTimeoutMs, turnWatches } = turnRuntime;
5425
+ const { state, turnIdRef, userInputBridgeRef, openClawDynamicToolExecutions, pendingOpenClawDynamicToolCompletionIds, noteProgress } = turnRuntime;
6579
5426
  const { emitExecutionPhaseOnce, scheduleTurnReleaseAfterTerminalDynamicTool, scheduleTerminalDynamicToolReleaseCheck } = lifecycle;
6580
5427
  const handleServerRequest = async (request, scope, requestSignal = new AbortController().signal) => {
6581
5428
  const signal = AbortSignal.any([runAbortController.signal, requestSignal]);
6582
5429
  const turnId = turnIdRef.current;
6583
5430
  const projector = projectorRef.current;
6584
- let armCompletionWatchOnResponse = false;
6585
5431
  let requestCountsAsTurnActivity = false;
6586
- let requestKeepsAttemptWatchArmed = false;
6587
- const markCurrentTurnRequestProgress = (options) => {
5432
+ const markCurrentTurnRequestProgress = () => {
6588
5433
  state.activeAppServerTurnRequests += 1;
6589
- requestKeepsAttemptWatchArmed = options?.hasIndependentTimeout !== true;
6590
- if (requestKeepsAttemptWatchArmed) state.activeAppServerTurnRequestsWithoutTimeout += 1;
6591
- turnWatches.clearCompletionIdleTimer();
6592
- turnWatches.disarmAssistantCompletionIdleWatch();
6593
5434
  requestCountsAsTurnActivity = true;
6594
- turnWatches.touchActivity(`request:${request.method}:start`, { attemptProgress: true });
5435
+ noteProgress(`request:${request.method}:start`);
6595
5436
  };
6596
5437
  try {
6597
5438
  if (!turnId) return;
6598
5439
  if (request.method === "mcpServer/elicitation/request") {
6599
- if (!scope.turnId || scope.turnId === turnId) {
6600
- armCompletionWatchOnResponse = true;
6601
- markCurrentTurnRequestProgress();
6602
- }
5440
+ if (!scope.turnId || scope.turnId === turnId) markCurrentTurnRequestProgress();
6603
5441
  const approvalResult = await routeCodexAppServerElicitationRequest({
6604
5442
  requestParams: request.params,
6605
5443
  paramsForRun: params,
6606
5444
  threadId: resourceState.thread.threadId,
6607
5445
  turnId,
5446
+ autoApproveMcpTools: autoApprove,
5447
+ projectedMcpServers: runtime.bundleMcpThreadConfig.configPatch?.mcp_servers,
5448
+ getActiveMcpToolCall: (serverName) => projector?.getActiveMcpToolCall(serverName),
6608
5449
  pluginAppPolicyContext: resourceState.thread.pluginAppPolicyContext,
6609
5450
  ...computerUseConfig.enabled ? { computerUseMcpServerName: computerUseConfig.mcpServerName } : {},
6610
5451
  signal
@@ -6616,10 +5457,7 @@ function createCodexAttemptServerRequestController(resources, turnRuntime, lifec
6616
5457
  });
6617
5458
  }
6618
5459
  if (request.method === "item/tool/requestUserInput") {
6619
- if (scope.turnId === turnId) {
6620
- armCompletionWatchOnResponse = true;
6621
- markCurrentTurnRequestProgress();
6622
- }
5460
+ if (scope.turnId === turnId) markCurrentTurnRequestProgress();
6623
5461
  return await userInputBridgeRef.current?.handleRequest({
6624
5462
  id: request.id,
6625
5463
  params: request.params
@@ -6627,10 +5465,7 @@ function createCodexAttemptServerRequestController(resources, turnRuntime, lifec
6627
5465
  }
6628
5466
  if (request.method !== "item/tool/call") {
6629
5467
  if (isCodexAppServerApprovalRequest(request.method)) {
6630
- if (scope.turnId === turnId) {
6631
- armCompletionWatchOnResponse = true;
6632
- markCurrentTurnRequestProgress();
6633
- }
5468
+ if (scope.turnId === turnId) markCurrentTurnRequestProgress();
6634
5469
  return await handleCodexAppServerApprovalRequest({
6635
5470
  method: request.method,
6636
5471
  requestParams: request.params,
@@ -6638,7 +5473,7 @@ function createCodexAttemptServerRequestController(resources, turnRuntime, lifec
6638
5473
  threadId: resourceState.thread.threadId,
6639
5474
  turnId,
6640
5475
  nativeHookRelay: resourceState.nativeHookRelay,
6641
- autoApprove: shouldAutoApproveCodexAppServerApprovals(appServer),
5476
+ autoApprove,
6642
5477
  signal,
6643
5478
  onNativeToolFailureDisposition: (itemId, disposition, approvalKind) => projector?.recordNativeToolApprovalFailure(itemId, disposition, approvalKind)
6644
5479
  });
@@ -6649,15 +5484,11 @@ function createCodexAttemptServerRequestController(resources, turnRuntime, lifec
6649
5484
  if (!call || call.threadId !== resourceState.thread.threadId || call.turnId !== turnId) return;
6650
5485
  const replayedExecution = openClawDynamicToolExecutions.get(call);
6651
5486
  if (replayedExecution) {
6652
- armCompletionWatchOnResponse = true;
6653
- markCurrentTurnRequestProgress({ hasIndependentTimeout: true });
6654
- state.turnCrossedToolHandoff = true;
5487
+ markCurrentTurnRequestProgress();
6655
5488
  return toCodexDynamicToolProtocolResponse(await replayedExecution);
6656
5489
  }
6657
5490
  const toolCallOrdinal = allocateCodexToolOutcomeOrdinal?.(call.callId);
6658
- armCompletionWatchOnResponse = true;
6659
- markCurrentTurnRequestProgress({ hasIndependentTimeout: true });
6660
- state.turnCrossedToolHandoff = true;
5491
+ markCurrentTurnRequestProgress();
6661
5492
  pendingOpenClawDynamicToolCompletionIds.add(call.callId);
6662
5493
  trajectoryRecorder?.recordEvent("tool.call", {
6663
5494
  threadId: call.threadId,
@@ -6834,17 +5665,10 @@ function createCodexAttemptServerRequestController(resources, turnRuntime, lifec
6834
5665
  }
6835
5666
  } finally {
6836
5667
  if (requestCountsAsTurnActivity) {
6837
- state.activeAppServerTurnRequests = Math.max(0, state.activeAppServerTurnRequests - 1);
6838
- if (requestKeepsAttemptWatchArmed) state.activeAppServerTurnRequestsWithoutTimeout = Math.max(0, state.activeAppServerTurnRequestsWithoutTimeout - 1);
6839
- const postToolContinuationTimeoutMs = request.method === "item/tool/call" && state.turnCrossedToolHandoff ? postToolRawAssistantCompletionIdleTimeoutMs : void 0;
6840
- turnWatches.touchActivity(`request:${request.method}:response`, {
6841
- arm: armCompletionWatchOnResponse,
6842
- attemptProgress: true,
6843
- ...postToolContinuationTimeoutMs !== void 0 ? { attemptTimeoutMs: postToolContinuationTimeoutMs } : {}
6844
- });
6845
- if (armCompletionWatchOnResponse && postToolContinuationTimeoutMs !== void 0) turnWatches.armCompletionIdleWatch({ timeoutMs: postToolContinuationTimeoutMs });
5668
+ state.activeAppServerTurnRequests -= 1;
5669
+ noteProgress(`request:${request.method}:response`);
6846
5670
  scheduleTerminalDynamicToolReleaseCheck();
6847
- } else turnWatches.scheduleProgressWatches();
5671
+ }
6848
5672
  }
6849
5673
  };
6850
5674
  return { handleServerRequest };
@@ -6853,7 +5677,7 @@ function createCodexAttemptServerRequestController(resources, turnRuntime, lifec
6853
5677
  //#region extensions/codex/src/app-server/attempt-runtime-artifact.ts
6854
5678
  async function verifyStartupArtifact(params) {
6855
5679
  if (!params.request) return;
6856
- const { readCodexAppServerClientRuntimeArtifact, validateCodexAppServerRuntimeArtifact } = await import("./runtime-artifact-D9pYp4zL.js");
5680
+ const { readCodexAppServerClientRuntimeArtifact, validateCodexAppServerRuntimeArtifact } = await import("./runtime-artifact-C3jVimFn.js");
6857
5681
  const runtimeArtifact = readCodexAppServerClientRuntimeArtifact(params.client);
6858
5682
  const expected = params.request.expected;
6859
5683
  const matchesExpected = !expected || runtimeArtifact?.id === expected.id && runtimeArtifact.fingerprint === expected.fingerprint;
@@ -7100,14 +5924,13 @@ async function startCodexAttemptThread(params) {
7100
5924
  startupClientForAbandonedRequestCleanup = void 0;
7101
5925
  },
7102
5926
  operation: async () => {
7103
- const threadConfig = mergeCodexThreadConfigs(params.configuredMcpOwnershipVersion === 1 ? void 0 : params.bundleMcpThreadConfig?.configPatch);
5927
+ const threadConfig = mergeCodexThreadConfigs(params.configuredMcpDynamicSurface ? void 0 : params.bundleMcpThreadConfig?.configPatch);
7104
5928
  const { pluginThreadConfigRequired, pluginThreadConfigPluginConfig, resolvedPluginPolicy, enabledPluginConfigKeys } = resolveCodexPluginThreadConfigStartupPolicy({
7105
5929
  pluginConfig: params.pluginConfig,
7106
5930
  nativeToolSurfaceEnabled: params.nativeToolSurfaceEnabled,
7107
5931
  scheduledRuntimeAuthority: params.buildAttemptParams().scheduledRuntimeAuthority
7108
5932
  });
7109
- const computerUseMcpElicitationDelegationRequired = params.computerUseConfig.enabled;
7110
- pluginAppServer = resolvedPluginPolicy?.enabled === true || computerUseMcpElicitationDelegationRequired ? {
5933
+ pluginAppServer = resolvedPluginPolicy?.enabled === true || params.computerUseConfig.enabled || params.nativeToolSurfaceEnabled && params.configuredMcpOwnershipVersion !== 1 && hasCodexMcpToolApprovalOverrides(params.config?.mcp?.servers, params.bundleMcpThreadConfig.userStaticServerNames, params.bundleMcpThreadConfig.configPatch?.mcp_servers) ? {
7111
5934
  ...params.appServer,
7112
5935
  approvalPolicy: withMcpElicitationsApprovalPolicy(params.appServer.approvalPolicy)
7113
5936
  } : params.appServer;
@@ -7299,9 +6122,9 @@ async function startCodexAttemptThread(params) {
7299
6122
  nativeCodeModeEnabled: params.nativeToolSurfaceEnabled,
7300
6123
  nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
7301
6124
  nativeCodeModeOnlyEnabled: params.appServer.codeModeOnly,
7302
- userMcpServersEnabled: params.configuredMcpOwnershipVersion === 1 ? false : params.nativeToolSurfaceEnabled,
7303
- mcpServersFingerprint: params.configuredMcpOwnershipVersion === 1 ? void 0 : params.bundleMcpThreadConfig.fingerprint,
7304
- mcpServersFingerprintEvaluated: params.configuredMcpOwnershipVersion === 1 || params.bundleMcpThreadConfig.evaluated,
6125
+ userMcpServersEnabled: params.configuredMcpDynamicSurface ? false : params.nativeToolSurfaceEnabled,
6126
+ mcpServersFingerprint: params.configuredMcpDynamicSurface ? void 0 : params.bundleMcpThreadConfig.fingerprint,
6127
+ mcpServersFingerprintEvaluated: params.configuredMcpDynamicSurface || params.bundleMcpThreadConfig.evaluated,
7305
6128
  configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
7306
6129
  environmentSelection: startupEnvironmentSelection,
7307
6130
  appServerRuntimeFingerprint,
@@ -7380,10 +6203,7 @@ async function startCodexAttemptThread(params) {
7380
6203
  if (startupAbandoned || params.signal.aborted) {
7381
6204
  if (startupClientForAbandonedRequestCleanup === startupClient) startupClientForAbandonedRequestCleanup = void 0;
7382
6205
  await closeCodexStartupClientBestEffort(startupClient);
7383
- } else if (!isCodexAppServerStartSelectionChangedError(startupAttemptError) && (shouldClearSharedClientAfterStartupRace(startupAttemptError) || shouldClearSharedClientAfterStartupFailure({
7384
- error: startupAttemptError,
7385
- spawnedBy: params.spawnedBy
7386
- }))) {
6206
+ } else if (!isCodexAppServerStartSelectionChangedError(startupAttemptError) && (shouldClearSharedClientAfterStartupRace(startupAttemptError) || shouldClearSharedClientAfterStartupFailure(startupAttemptError, params.spawnedBy))) {
7387
6207
  if (startupClientForAbandonedRequestCleanup === startupClient) startupClientForAbandonedRequestCleanup = void 0;
7388
6208
  await closeCodexStartupClientBestEffort(startupClient);
7389
6209
  }
@@ -7432,10 +6252,7 @@ async function startCodexAttemptThread(params) {
7432
6252
  releaseSharedClientLease = void 0;
7433
6253
  await closeCodexStartupClientBestEffort(startupClientForAbandonedRequestCleanup);
7434
6254
  startupClientForAbandonedRequestCleanup = void 0;
7435
- } else if (!isCodexAppServerStartSelectionChangedError(error) && (shouldClearSharedClientAfterStartupRace(error) || shouldClearSharedClientAfterStartupFailure({
7436
- error,
7437
- spawnedBy: params.spawnedBy
7438
- }))) {
6255
+ } else if (!isCodexAppServerStartSelectionChangedError(error) && (shouldClearSharedClientAfterStartupRace(error) || shouldClearSharedClientAfterStartupFailure(error, params.spawnedBy))) {
7439
6256
  releaseSharedClientLease?.();
7440
6257
  releaseSharedClientLease = void 0;
7441
6258
  await closeCodexStartupClientBestEffort(startupClientForAbandonedRequestCleanup);
@@ -7449,9 +6266,8 @@ async function startCodexAttemptThread(params) {
7449
6266
  function shouldClearSharedClientAfterStartupRace(error) {
7450
6267
  return isCodexAppServerStartupError(error) || isCodexAppServerRequestTimeoutError(error);
7451
6268
  }
7452
- function shouldClearSharedClientAfterStartupFailure(params) {
7453
- if (isCodexAppServerOverloadError(params.error)) return false;
7454
- return isCodexAppServerBrokenPipeError(params.error) || !params.spawnedBy;
6269
+ function shouldClearSharedClientAfterStartupFailure(error, spawnedBy) {
6270
+ return !isCodexAppServerOverloadError(error) && !(error instanceof AgentHarnessPreflightError && error.scope === void 0) && (isCodexAppServerBrokenPipeError(error) || !spawnedBy);
7455
6271
  }
7456
6272
  //#endregion
7457
6273
  //#region extensions/codex/src/app-server/run-attempt-start.ts
@@ -7461,7 +6277,7 @@ async function startCodexAttemptRuntime(resources) {
7461
6277
  const { runtime, attemptTools, promptState } = context;
7462
6278
  const { connection, runtimeParams, preparedAuthBinding, buildActiveRunAttemptParams, startupAuthAccountCacheKey, startupEnvApiKeyCacheKey, bundleMcpThreadConfig, nativeToolSurfaceEnabled, nativeProviderWebSearchSupport, effectiveRuntimeModelId, sandboxExecServerEnabled } = runtime;
7463
6279
  const { toolBridge, toolState } = attemptTools;
7464
- const developerInstructions = joinPresentSections(turnState.promptBuild.developerInstructions, attemptTools.scheduledConfiguredMcp?.diagnosticNotice);
6280
+ const developerInstructions = joinPresentSections(turnState.promptBuild.developerInstructions, attemptTools.configuredMcp?.diagnosticNotice);
7465
6281
  const { params, attemptClientFactory, bindingStore, appServer, pluginConfig, computerUseConfig, startupClientAuthProfileId, runtimeArtifactRequest, startupPreparedAuth, agentDir, sessionAgentId, effectiveWorkspace, effectiveCwd, sandbox, runAbortController, usesSupervisionConnection, resolveReviewerPolicyContext, resolveRuntimeOptionsForCurrentBinding, startupAuthProfileId, startupAuthRequirement, abortFromUpstream } = connection;
7466
6282
  let pluginAppServer = withCodexAppServerFastModeServiceTier(appServer, runtimeParams);
7467
6283
  const loopDetectionEnabled = (sessionAgentId && params.config ? resolveAgentConfig$1(params.config, sessionAgentId)?.tools?.loopDetection?.enabled : void 0) ?? params.config?.tools?.loopDetection?.enabled ?? false;
@@ -7501,6 +6317,7 @@ async function startCodexAttemptRuntime(resources) {
7501
6317
  buildFinalConfigPatch: buildNativeHookRelayFinalConfigPatch,
7502
6318
  nativeHookRelayRequired: connection.options.nativeHookRelay?.enabled !== false && params.pluginHarnessToolPolicyRestricted !== true && connection.nativeHookRelayEvents.includes("pre_tool_use") && (hasBeforeToolCallPolicy() || appServer.loopDetectionPreToolUseRelay && Boolean(connection.sandboxSessionKey) && loopDetectionEnabled),
7503
6319
  bundleMcpThreadConfig,
6320
+ configuredMcpDynamicSurface: attemptTools.configuredMcp !== void 0,
7504
6321
  configuredMcpOwnershipVersion: attemptTools.configuredMcpOwnershipVersion,
7505
6322
  nativeToolSurfaceEnabled,
7506
6323
  nativeProviderWebSearchSupport,
@@ -7583,7 +6400,7 @@ async function startCodexAttemptRuntime(resources) {
7583
6400
  recordCodexTrajectoryContext(trajectoryRecorder, {
7584
6401
  attempt: params,
7585
6402
  cwd: effectiveCwd,
7586
- developerInstructions: joinPresentSections(buildRenderedCodexDeveloperInstructions(), attemptTools.scheduledConfiguredMcp?.diagnosticNotice),
6403
+ developerInstructions: joinPresentSections(buildRenderedCodexDeveloperInstructions(), attemptTools.configuredMcp?.diagnosticNotice),
7587
6404
  prompt: turnState.codexTurnPromptText,
7588
6405
  tools: toolBridge.availableSpecs
7589
6406
  });
@@ -7597,7 +6414,7 @@ async function startCodexAttemptRuntime(resources) {
7597
6414
  await runCleanupStep("codex-start-failure-sandbox-release", releaseSandboxExecEnvironment);
7598
6415
  await runCleanupStep("codex-start-failure-shared-client-release", releaseSharedClientLeaseAndRetireOneShotClient);
7599
6416
  await runCleanupStep("codex-start-failure-abort-listener", () => params.abortSignal?.removeEventListener("abort", abortFromUpstream));
7600
- throw state.executionDisconnectError ?? error;
6417
+ throw error instanceof CodexThreadPolicyHandoffError ? error : state.executionDisconnectError ?? error;
7601
6418
  }
7602
6419
  }
7603
6420
  //#endregion
@@ -7606,7 +6423,7 @@ function isAuthorityResolutionOperationAbort(error, signal) {
7606
6423
  return signal?.aborted === true && error === signal.reason;
7607
6424
  }
7608
6425
  async function prepareCodexAttemptTools(runtime) {
7609
- const { connection, bundleMcpThreadConfig, bundleManifestRegistry, runtimeParams, effectiveRuntimeModelId, nativeToolSurfaceEnabled, nativeProviderWebSearchSupport, hookChannelId, codexMcpToolOverrides, authenticatedScheduledMode, ownsScheduledConfiguredMcpSurface, canResolveScheduledConfiguredMcpCreatorAuthority } = runtime;
6426
+ const { connection, bundleMcpThreadConfig, bundleManifestRegistry, runtimeParams, effectiveRuntimeModelId, nativeToolSurfaceEnabled, nativeProviderWebSearchSupport, hookChannelId, codexMcpToolOverrides, authenticatedScheduledMode, configuredMcpSurface, canResolveScheduledConfiguredMcpCreatorAuthority } = runtime;
7610
6427
  const { params, preDynamicStartupStages, mutable, startupAuthProfileId, resolvedWorkspace, effectiveWorkspace, effectiveCwd, sandboxSessionKey, contextSessionKey, sandbox, sessionPermissionPolicy, runAbortController, sessionAgentId, policyAgentId, pluginConfig, profilerEnabled, agentDir } = connection;
7611
6428
  const preDynamicSummary = preDynamicStartupStages.snapshot();
7612
6429
  if (shouldWarnCodexDynamicToolBuildStageSummary(preDynamicSummary)) embeddedAgentLog.warn(`codex app-server pre-dynamic startup timings runId=${params.runId} sessionId=${params.sessionId} totalMs=${preDynamicSummary.totalMs} stages=${formatCodexDynamicToolBuildStageSummary(preDynamicSummary)}`, {
@@ -7655,16 +6472,24 @@ async function prepareCodexAttemptTools(runtime) {
7655
6472
  const cronCreatorToolAllowlistCaptureRef = {};
7656
6473
  const scheduledAppAuthoritySourceRef = {};
7657
6474
  const preparedChatgptAuth = connection.startupPreparedAuth?.kind === "profile" && connection.startupPreparedAuth.snapshot?.loginParams.type === "chatgptAuthTokens" && connection.startupPreparedAuth.snapshot.chatgptAccountId ? {
6475
+ kind: "prepared-profile",
7658
6476
  profileId: connection.startupPreparedAuth.profileId,
7659
6477
  accountId: connection.startupPreparedAuth.snapshot.chatgptAccountId
7660
6478
  } : void 0;
6479
+ const configuredAppServerAuth = !preparedChatgptAuth && connection.appServer.start.transport !== "stdio" ? {
6480
+ kind: "configured-app-server",
6481
+ connectionFingerprint: buildScheduledCodexAppServerConnectionIdentity(connection.appServer)
6482
+ } : void 0;
6483
+ const scheduledCodexAppAuth = preparedChatgptAuth ?? configuredAppServerAuth;
7661
6484
  const appPolicy = resolveCodexPluginsPolicy(pluginConfig);
6485
+ const codexAppsMayBeVisible = appPolicy.enabled && (appPolicy.allowAllPlugins || appPolicy.pluginPolicies.some((entry) => entry.enabled));
7662
6486
  const appCreatorCapture = resolveScheduledCodexAppCreatorCaptureDecision({
7663
- appsMayBeVisible: appPolicy.enabled && (appPolicy.allowAllPlugins || appPolicy.pluginPolicies.some((entry) => entry.enabled)),
6487
+ appsMayBeVisible: codexAppsMayBeVisible,
7664
6488
  authenticatedScheduledMode,
7665
6489
  usesSupervisionConnection: connection.usesSupervisionConnection,
7666
6490
  homeScope: connection.appServer.start.homeScope,
7667
- hasPreparedAccountIdentity: Boolean(preparedChatgptAuth)
6491
+ hasPreparedAccountIdentity: Boolean(preparedChatgptAuth),
6492
+ hasConfiguredAppServerIdentity: Boolean(configuredAppServerAuth)
7668
6493
  });
7669
6494
  const codexAppAuthorityUnavailableReason = appCreatorCapture.unavailableReason;
7670
6495
  const canResolveScheduledCodexAppAuthority = appCreatorCapture.supported;
@@ -7675,6 +6500,7 @@ async function prepareCodexAttemptTools(runtime) {
7675
6500
  let resolveCreatorAuthorityImpl;
7676
6501
  const runtimeYieldCompletionClaim = {};
7677
6502
  const commonToolParams = {
6503
+ nodeExecAvailability: {},
7678
6504
  params: dynamicToolParams,
7679
6505
  resolvedWorkspace,
7680
6506
  effectiveWorkspace,
@@ -7711,6 +6537,32 @@ async function prepareCodexAttemptTools(runtime) {
7711
6537
  return pending;
7712
6538
  } } : {}
7713
6539
  };
6540
+ let nativeSpecs;
6541
+ if (hasCodexNativeToolCatalog(mutable.startupBinding)) {
6542
+ runAbortController.signal.throwIfAborted();
6543
+ params.hostCapabilities.assertActive();
6544
+ const client = await connection.attemptClientFactory({
6545
+ startOptions: connection.appServer.start,
6546
+ authProfileId: connection.startupClientAuthProfileId,
6547
+ agentDir,
6548
+ config: params.config,
6549
+ timeoutMs: connection.appServer.requestTimeoutMs
6550
+ });
6551
+ try {
6552
+ nativeSpecs = await loadCodexNativeToolCatalog({
6553
+ client,
6554
+ binding: mutable.startupBinding,
6555
+ appServer: connection.appServer,
6556
+ agentDir,
6557
+ assertCurrent: () => {
6558
+ runAbortController.signal.throwIfAborted();
6559
+ params.hostCapabilities.assertActive();
6560
+ }
6561
+ });
6562
+ } finally {
6563
+ releaseLeasedSharedCodexAppServerClient(client);
6564
+ }
6565
+ }
7714
6566
  const tools = await buildDynamicTools({
7715
6567
  ...commonToolParams,
7716
6568
  registerRunCleanup: (cleanup) => runCleanups.push(cleanup),
@@ -7723,7 +6575,7 @@ async function prepareCodexAttemptTools(runtime) {
7723
6575
  toolState.webSearchAllowed = allowed;
7724
6576
  }
7725
6577
  });
7726
- const registeredTools = await buildDynamicTools({
6578
+ const registeredTools = nativeSpecs ? [] : await buildDynamicTools({
7727
6579
  ...commonToolParams,
7728
6580
  forceHeartbeatTool: true,
7729
6581
  ignoreDisableMessageTool: true,
@@ -7781,9 +6633,10 @@ async function prepareCodexAttemptTools(runtime) {
7781
6633
  ...params.memberRoleIds?.length ? { roleIds: [...params.memberRoleIds] } : {}
7782
6634
  };
7783
6635
  const hasRequester = Object.keys(requester).length > 0;
7784
- const scheduledConfiguredMcp = ownsScheduledConfiguredMcpSurface ? await materializeStaticMcpToolsForScheduledHarnessRun({
6636
+ const configuredMcp = configuredMcpSurface ? await materializeStaticMcpToolsForHarnessRun({
7785
6637
  sessionId: params.sessionId,
7786
6638
  sessionKey: params.sessionKey,
6639
+ agentId: sessionAgentId,
7787
6640
  workspaceDir: effectiveWorkspace,
7788
6641
  agentDir: policyContext.agentDir,
7789
6642
  cfg: params.config,
@@ -7792,6 +6645,29 @@ async function prepareCodexAttemptTools(runtime) {
7792
6645
  toolsAllow: params.toolsAllow,
7793
6646
  toolOverrides: codexMcpToolOverrides,
7794
6647
  autoApproveCodexAppServerApprovals: shouldAutoApproveCodexAppServerApprovals(connection.appServer),
6648
+ projectedMcpServers: bundleMcpThreadConfig.configPatch?.mcp_servers,
6649
+ ...configuredMcpSurface === "transient" ? { requestInteractiveCodexApproval: async (approval) => {
6650
+ const allowedDecisions = approval.mode === "prompt" ? ["allow-once", "deny"] : [
6651
+ "allow-once",
6652
+ "allow-always",
6653
+ "deny"
6654
+ ];
6655
+ const outcome = await requestPluginApprovalOutcome({
6656
+ hostCapabilities: params.hostCapabilities,
6657
+ signal: approval.signal,
6658
+ title: `Run MCP tool ${approval.serverName}/${approval.toolName}`,
6659
+ description: `Codex approval mode "${approval.mode}" requires an operator decision before this MCP tool runs. ${formatMcpCodexApprovalRemedy(approval.serverName)}`,
6660
+ allowedDecisions,
6661
+ toolName: approval.safeToolName,
6662
+ toolCallId: approval.toolCallId,
6663
+ mcpTool: {
6664
+ server: approval.serverName,
6665
+ tool: approval.toolName
6666
+ },
6667
+ isMcpToolApprovalActive: approval.isActive
6668
+ });
6669
+ if (outcome !== "approved-once" && outcome !== "approved-session") throw new Error(`${approval.serverName}/${approval.toolName}: interactive Codex approval (${approval.mode}) was not granted: ${outcome}`);
6670
+ } } : {},
7795
6671
  policyContext,
7796
6672
  warn: (message) => embeddedAgentLog.warn(message)
7797
6673
  }) : void 0;
@@ -7808,13 +6684,13 @@ async function prepareCodexAttemptTools(runtime) {
7808
6684
  requesterSenderId: params.senderId,
7809
6685
  agentAccountId: params.agentAccountId,
7810
6686
  messageChannel: params.messageChannel ?? params.messageProvider,
7811
- reservedToolNames,
6687
+ reservedToolNames: [...reservedToolNames, ...configuredMcp?.tools.map((tool) => tool.name) ?? []],
7812
6688
  toolsAllow: params.toolsAllow,
7813
6689
  policyContext,
7814
6690
  warn: (message) => embeddedAgentLog.warn(message)
7815
6691
  });
7816
- const scopedExecutable = filterCodexDynamicTools(scheduledConfiguredMcp?.tools ?? scopedMcpTools?.tools ?? [], pluginConfig);
7817
- const scopedAdvertised = filterCodexDynamicTools(scheduledConfiguredMcp?.tools ?? scopedMcpTools?.advertisedTools ?? [], pluginConfig);
6692
+ const scopedExecutable = filterCodexDynamicTools([...configuredMcp?.tools ?? [], ...scopedMcpTools?.tools ?? []], pluginConfig);
6693
+ const scopedAdvertised = filterCodexDynamicTools([...configuredMcp?.tools ?? [], ...scopedMcpTools?.advertisedTools ?? []], pluginConfig);
7818
6694
  const toolsWithScopedMcp = scopedExecutable.length > 0 ? [...tools, ...scopedExecutable] : tools;
7819
6695
  const registeredWithScopedMcp = scopedAdvertised.length > 0 ? [...registeredTools, ...scopedAdvertised] : registeredTools;
7820
6696
  const hookContext = {
@@ -7849,6 +6725,7 @@ async function prepareCodexAttemptTools(runtime) {
7849
6725
  toolBridge = createCodexDynamicToolBridge({
7850
6726
  tools: toolsWithScopedMcp,
7851
6727
  registeredTools: registeredWithScopedMcp,
6728
+ registeredSpecs: nativeSpecs,
7852
6729
  signal: runAbortController.signal,
7853
6730
  computerContextEpoch,
7854
6731
  loading: resolveCodexDynamicToolsLoadingForRuntime(pluginConfig, effectiveRuntimeModelId, { connectionClass: connection.appServer.connectionClass }),
@@ -7867,9 +6744,9 @@ async function prepareCodexAttemptTools(runtime) {
7867
6744
  await captureFinalCodexCronCreatorToolAllowlist(authorityTools, captureRef, toolBridge.availableTools);
7868
6745
  if (!captureRef.value) throw new Error("cron creator authority snapshot did not produce provenance");
7869
6746
  const appSource = scheduledAppAuthoritySourceRef.current;
7870
- const runtimeAuthority = canResolveScheduledCodexAppAuthority && preparedChatgptAuth ? appSource ? await captureScheduledCodexAppAuthority({
6747
+ const runtimeAuthority = canResolveScheduledCodexAppAuthority && scheduledCodexAppAuth ? appSource ? await captureScheduledCodexAppAuthority({
7871
6748
  ...appSource,
7872
- ...preparedChatgptAuth,
6749
+ auth: scheduledCodexAppAuth,
7873
6750
  signal: options?.signal
7874
6751
  }) : (() => {
7875
6752
  throw new Error("Codex app authority is unavailable before the exact creator thread is active. Retry this automation mutation from the current owner turn.");
@@ -7885,8 +6762,9 @@ async function prepareCodexAttemptTools(runtime) {
7885
6762
  const authorityRuntimeId = `cron-authority:${params.runId}`;
7886
6763
  let materialized;
7887
6764
  try {
7888
- materialized = await materializeStaticMcpToolsForScheduledHarnessRun({
6765
+ materialized = await materializeStaticMcpToolsForHarnessRun({
7889
6766
  sessionId: authorityRuntimeId,
6767
+ agentId: sessionAgentId,
7890
6768
  workspaceDir: effectiveWorkspace,
7891
6769
  agentDir: policyContext.agentDir,
7892
6770
  cfg: params.config,
@@ -7895,6 +6773,7 @@ async function prepareCodexAttemptTools(runtime) {
7895
6773
  toolsAllow: params.toolsAllow,
7896
6774
  toolOverrides: codexMcpToolOverrides,
7897
6775
  autoApproveCodexAppServerApprovals: shouldAutoApproveCodexAppServerApprovals(connection.appServer),
6776
+ projectedMcpServers: bundleMcpThreadConfig.configPatch?.mcp_servers,
7898
6777
  policyContext,
7899
6778
  warn: (message) => embeddedAgentLog.warn(message),
7900
6779
  retireSessionRuntimeAfterDispose: true
@@ -7926,8 +6805,8 @@ async function prepareCodexAttemptTools(runtime) {
7926
6805
  tools: toolsWithScopedMcp,
7927
6806
  registeredTools: registeredWithScopedMcp,
7928
6807
  scopedMcpTools,
7929
- scheduledConfiguredMcp,
7930
- configuredMcpOwnershipVersion: ownsScheduledConfiguredMcpSurface ? 1 : void 0,
6808
+ configuredMcp,
6809
+ configuredMcpOwnershipVersion: configuredMcpSurface === "scheduled" ? 1 : void 0,
7931
6810
  cronCreatorToolAllowlist,
7932
6811
  cronCreatorToolAllowlistCaptureRef,
7933
6812
  scheduledAppAuthoritySourceRef,
@@ -7945,7 +6824,7 @@ async function prepareCodexAttemptTools(runtime) {
7945
6824
  };
7946
6825
  } catch (error) {
7947
6826
  await scopedMcpTools?.dispose();
7948
- await scheduledConfiguredMcp?.dispose();
6827
+ await configuredMcp?.dispose();
7949
6828
  throw error;
7950
6829
  }
7951
6830
  }
@@ -7985,7 +6864,7 @@ async function prepareCodexAttemptTurnRequest(resources, turnRuntime, ensureCurr
7985
6864
  const { context, turnState, buildRenderedCodexDeveloperInstructions } = prompt;
7986
6865
  const { runtime, attemptTools, hookContextWindowFields, workspaceBootstrapContext } = context;
7987
6866
  const { connection, runtimeParams, effectiveRuntimeProviderId, effectiveRuntimeModelId } = runtime;
7988
- const { tools } = attemptTools;
6867
+ const { tools, toolBridge } = attemptTools;
7989
6868
  const { params, usesSupervisionConnection, codexModelCallId, codexModelCallTrace, codexModelContentCapture, appServer, runAbortController } = connection;
7990
6869
  const { state } = turnRuntime;
7991
6870
  const explicitSkillInputs = await resolveCodexExplicitSkillInputs({
@@ -8048,7 +6927,8 @@ async function prepareCodexAttemptTurnRequest(resources, turnRuntime, ensureCurr
8048
6927
  turnScopedDeveloperInstructions: workspaceBootstrapContext.turnScopedDeveloperInstructions,
8049
6928
  skillsCollaborationInstructions: context.skillsCollaborationInstructions,
8050
6929
  memoryCollaborationInstructions: workspaceBootstrapContext.memoryCollaborationInstructions,
8051
- preserveNativeTurnSettings: usesSupervisionConnection
6930
+ preserveNativeTurnSettings: usesSupervisionConnection,
6931
+ sessionStatusAvailable: toolBridge.availableTools.some((tool) => tool.name === "session_status")
8052
6932
  });
8053
6933
  codexModelCallDiagnostics.setRequestPayloadBytes(utf8JsonByteLength(turnStartParams));
8054
6934
  state.latestStartupErrorNotification = void 0;
@@ -8232,7 +7112,7 @@ async function startCodexAttemptTurn(resources, turnRuntime, notifications, requ
8232
7112
  trajectoryRecorder?.recordEvent("session.ended", {
8233
7113
  status: "error",
8234
7114
  threadId: resourceState.thread.threadId,
8235
- timedOut: state.timedOut,
7115
+ timedOut: state.timeout !== void 0,
8236
7116
  aborted: runAbortController.signal.aborted,
8237
7117
  promptError: message
8238
7118
  });
@@ -8253,8 +7133,7 @@ async function startCodexAttemptTurn(resources, turnRuntime, notifications, requ
8253
7133
  });
8254
7134
  const failureKind = classifyCodexModelCallFailureKind({
8255
7135
  error: turnStartError,
8256
- timedOut: state.timedOut,
8257
- turnCompletionIdleTimedOut: state.turnCompletionIdleTimedOut,
7136
+ timedOut: state.timeout !== void 0,
8258
7137
  runAborted: runAbortController.signal.aborted,
8259
7138
  abortReason: runAbortController.signal.reason,
8260
7139
  clientClosedAbort: state.clientClosedAbort,
@@ -8275,11 +7154,10 @@ async function startCodexAttemptTurn(resources, turnRuntime, notifications, requ
8275
7154
  ctx: hookContext,
8276
7155
  hookRunner
8277
7156
  });
8278
- const bindingReleased = isIncognitoSessionKey(params.sessionKey) ? await bindingStore.mutate(bindingIdentity, {
7157
+ if ((isIncognitoSessionKey(params.sessionKey) ? await bindingStore.mutate(bindingIdentity, {
8279
7158
  kind: "clear",
8280
7159
  threadId: resourceState.thread.threadId
8281
- }) : true;
8282
- if (!state.timedOut && bindingReleased && !resourceState.startupClientUnsafe) {
7160
+ }) : true) && !resourceState.startupClientUnsafe) {
8283
7161
  if (!await unsubscribeCodexThreadBestEffort(resourceState.client, {
8284
7162
  threadId: resourceState.thread.threadId,
8285
7163
  timeoutMs: 5e3
@@ -8352,279 +7230,56 @@ async function startCodexAttemptTurn(resources, turnRuntime, notifications, requ
8352
7230
  return { turn };
8353
7231
  }
8354
7232
  //#endregion
8355
- //#region extensions/codex/src/app-server/attempt-turn-watches.ts
8356
- /**
8357
- * Idle-watch controller for Codex app-server turn progress, completion, and
8358
- * terminal-event gaps.
8359
- */
8360
- /**
8361
- * Creates a controller that arms/disarms timers as Codex app-server
8362
- * notifications and tool handoffs progress.
8363
- */
8364
- function createCodexAttemptTurnWatchController(params) {
8365
- const timers = {};
8366
- let completionIdleWatchArmed = false;
8367
- let completionIdleWatchPinnedByTerminalError = false;
8368
- let completionIdleTimeoutOverrideMs;
8369
- let assistantCompletionIdleWatchArmed = false;
8370
- let assistantCompletionLastActivityAt = Date.now();
8371
- let assistantCompletionLastActivityDetails;
8372
- let attemptIdleWatchArmed = false;
8373
- let terminalIdleWatchArmed = false;
8374
- let completionLastActivityAt = Date.now();
8375
- let completionLastActivityReason = "startup";
8376
- let completionLastActivityDetails;
8377
- let attemptIdleTimeoutOverrideMs;
8378
- let attemptLastProgressAt = Date.now();
8379
- let attemptLastProgressReason = "startup";
8380
- let attemptLastProgressDetails;
8381
- const turnCompletionIdleTimeoutMs = resolveTimerTimeoutMs(params.turnCompletionIdleTimeoutMs, 1);
8382
- const turnAssistantCompletionIdleTimeoutMs = resolveTimerTimeoutMs(params.turnAssistantCompletionIdleTimeoutMs, 1);
8383
- const turnAttemptIdleTimeoutMs = resolveTimerTimeoutMs(params.turnAttemptIdleTimeoutMs, 1);
8384
- const turnTerminalIdleTimeoutMs = resolveTimerTimeoutMs(params.turnTerminalIdleTimeoutMs, 1);
8385
- const interruptTimeoutMs = resolveTimerTimeoutMs(params.interruptTimeoutMs, 1);
8386
- const resolveWatchTimeoutMs = (timeoutMs) => resolveTimerTimeoutMs(timeoutMs, 1);
8387
- const clearTimer = (kind) => {
8388
- const timer = timers[kind];
8389
- if (timer) {
8390
- clearTimeout(timer);
8391
- delete timers[kind];
8392
- }
7233
+ //#region extensions/codex/src/app-server/attempt-deadlines.ts
7234
+ function createCodexAttemptDeadlineController(params) {
7235
+ let deadline = { kind: "closed" };
7236
+ let timer;
7237
+ const armDeadline = (next) => {
7238
+ clearTimeout(timer);
7239
+ deadline = next;
7240
+ const deadlineAtMs = next.startedAtMs + next.timeoutMs;
7241
+ params.onDeadlineChanged?.({
7242
+ kind: "bounded",
7243
+ deadlineAtMs
7244
+ });
7245
+ timer = setTimeout(() => {
7246
+ if (deadline !== next || params.signal.aborted) return;
7247
+ deadline = { kind: "closed" };
7248
+ params.onTimeout({
7249
+ kind: next.kind,
7250
+ elapsedMs: Math.max(0, Date.now() - next.startedAtMs),
7251
+ timeoutMs: next.timeoutMs
7252
+ });
7253
+ }, resolveTimerTimeoutMs(Math.max(1, deadlineAtMs - Date.now()), 1));
7254
+ timer.unref?.();
8393
7255
  };
8394
- const clearCompletionIdleTimer = () => clearTimer("completion");
8395
- const clearAllTimers = () => {
8396
- for (const kind of Object.keys(timers)) clearTimer(kind);
7256
+ const dispose = () => {
7257
+ deadline = { kind: "closed" };
7258
+ clearTimeout(timer);
7259
+ params.signal.removeEventListener("abort", dispose);
8397
7260
  };
8398
- function scheduleWatch(kind, callback, lastActivityAt, timeoutMs, ready) {
8399
- clearTimer(kind);
8400
- if (!ready || params.isCompleted() || params.signal.aborted) return;
8401
- const elapsedMs = Math.max(0, Date.now() - lastActivityAt);
8402
- const timer = setTimeout(callback, Math.max(1, timeoutMs - elapsedMs));
8403
- timer.unref?.();
8404
- timers[kind] = timer;
8405
- }
8406
- function scheduleCompletionIdleWatch() {
8407
- scheduleWatch("completion", fireCompletionIdleTimeout, completionLastActivityAt, completionIdleTimeoutOverrideMs ?? turnCompletionIdleTimeoutMs, completionIdleWatchArmed && params.getActiveAppServerTurnRequests() === 0 && params.getActiveCompletionBlockerItemCount() === 0);
8408
- }
8409
- function scheduleAssistantCompletionIdleWatch() {
8410
- scheduleWatch("assistant", fireAssistantCompletionIdleRelease, assistantCompletionLastActivityAt, turnAssistantCompletionIdleTimeoutMs, assistantCompletionIdleWatchArmed && params.getActiveFinalizationHookCount() === 0);
8411
- }
8412
- function shouldPauseAttemptIdleWatch() {
8413
- if (params.getActiveAppServerTurnRequestsWithoutTimeout() > 0) return false;
8414
- return params.getActiveCompletionBlockerItemCount() > 0 || params.getActiveAppServerTurnRequests() > 0;
8415
- }
8416
- function scheduleAttemptIdleWatch() {
8417
- scheduleWatch("attempt", fireAttemptIdleTimeout, attemptLastProgressAt, attemptIdleTimeoutOverrideMs ?? turnAttemptIdleTimeoutMs, attemptIdleWatchArmed && !shouldPauseAttemptIdleWatch());
8418
- }
8419
- const terminalWorkIsActive = () => params.getActiveAppServerTurnRequests() > 0 || params.isTerminalTurnNotificationQueued() && params.getActiveFinalizationHookCount() > 0;
8420
- const terminalIdleTimeoutMs = () => params.isTerminalTurnNotificationQueued() && !terminalWorkIsActive() ? Math.min(TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS, turnTerminalIdleTimeoutMs) : turnTerminalIdleTimeoutMs;
8421
- function scheduleTerminalIdleWatch() {
8422
- scheduleWatch("terminal", fireTerminalIdleTimeout, completionLastActivityAt, terminalIdleTimeoutMs(), terminalIdleWatchArmed && (params.isTerminalTurnNotificationQueued() || params.getActiveAppServerTurnRequests() === 0));
8423
- }
8424
- function scheduleProgressWatches() {
8425
- scheduleAttemptIdleWatch();
8426
- scheduleCompletionIdleWatch();
8427
- scheduleTerminalIdleWatch();
8428
- }
8429
- function isCompletionIdleTimeoutDueBeforeAttempt(timeoutMs) {
8430
- if (params.isCompleted() || params.isTerminalTurnNotificationQueued() || params.signal.aborted || !completionIdleWatchArmed || params.getActiveAppServerTurnRequests() > 0 || params.getActiveCompletionBlockerItemCount() > 0) return false;
8431
- const completionTimeoutMs = completionIdleTimeoutOverrideMs ?? turnCompletionIdleTimeoutMs;
8432
- if (completionTimeoutMs > timeoutMs) return false;
8433
- return Math.max(0, Date.now() - completionLastActivityAt) >= completionTimeoutMs;
8434
- }
8435
- function recordAttemptProgress(reason, options) {
8436
- attemptIdleTimeoutOverrideMs = options?.attemptTimeoutMs !== void 0 ? resolveWatchTimeoutMs(options.attemptTimeoutMs) : void 0;
8437
- attemptLastProgressAt = completionLastActivityAt;
8438
- attemptLastProgressReason = reason;
8439
- attemptLastProgressDetails = options?.details;
8440
- params.onAttemptProgress(reason, options?.details);
8441
- scheduleAttemptIdleWatch();
8442
- }
8443
- function fireAssistantCompletionIdleRelease() {
8444
- if (params.isCompleted() || params.signal.aborted || !assistantCompletionIdleWatchArmed) return;
8445
- if (params.getActiveAppServerTurnRequests() > 0 || params.getActiveTurnItemCount() > 0 || params.getActiveFinalizationHookCount() > 0) {
8446
- scheduleAssistantCompletionIdleWatch();
8447
- return;
8448
- }
8449
- if (!params.canReleaseAssistantCompletionIdle()) {
8450
- assistantCompletionIdleWatchArmed = false;
8451
- assistantCompletionLastActivityDetails = void 0;
8452
- clearTimer("assistant");
8453
- return;
8454
- }
8455
- const idleMs = Math.max(0, Date.now() - assistantCompletionLastActivityAt);
8456
- if (idleMs < turnAssistantCompletionIdleTimeoutMs) {
8457
- scheduleAssistantCompletionIdleWatch();
8458
- return;
8459
- }
8460
- assistantCompletionIdleWatchArmed = false;
8461
- clearCompletionIdleTimer();
8462
- clearTimer("terminal");
8463
- const turnId = params.getTurnId();
8464
- const fields = {
8465
- threadId: params.threadId,
8466
- turnId,
8467
- idleMs,
8468
- timeoutMs: turnAssistantCompletionIdleTimeoutMs,
8469
- ...assistantCompletionLastActivityDetails
8470
- };
8471
- params.onRecordEvent("turn.assistant_completion_idle_release", fields);
8472
- embeddedAgentLog.warn("codex app-server turn released after completed assistant item without terminal event", fields);
8473
- if (turnId) {
8474
- params.onInterruptTurn({
8475
- threadId: params.threadId,
8476
- turnId,
8477
- timeoutMs: interruptTimeoutMs
8478
- }).finally(params.onCompleted);
8479
- return;
8480
- }
8481
- params.onCompleted();
8482
- }
8483
- function reportTimeout(timeout) {
8484
- params.onTimeout(timeout);
8485
- const fields = {
8486
- threadId: params.threadId,
8487
- turnId: params.getTurnId(),
8488
- idleMs: timeout.idleMs,
8489
- timeoutMs: timeout.timeoutMs,
8490
- lastActivityReason: timeout.lastActivityReason,
8491
- ...timeout.details
8492
- };
8493
- params.onRecordEvent(`turn.${timeout.kind}_idle_timeout`, fields);
8494
- embeddedAgentLog.warn(`codex app-server turn idle timed out waiting for ${timeout.kind === "terminal" ? "terminal event" : timeout.kind}`, fields);
8495
- params.onAbort(`turn_${timeout.kind}_idle_timeout`);
8496
- }
8497
- function fireAttemptIdleTimeout() {
8498
- if (params.isCompleted() || params.signal.aborted || !attemptIdleWatchArmed || shouldPauseAttemptIdleWatch()) return;
8499
- const idleMs = Math.max(0, Date.now() - attemptLastProgressAt);
8500
- const timeoutMs = attemptIdleTimeoutOverrideMs ?? turnAttemptIdleTimeoutMs;
8501
- if (idleMs < timeoutMs) {
8502
- scheduleAttemptIdleWatch();
8503
- return;
8504
- }
8505
- if (isCompletionIdleTimeoutDueBeforeAttempt(timeoutMs)) {
8506
- fireCompletionIdleTimeout();
8507
- return;
8508
- }
8509
- reportTimeout({
8510
- kind: "progress",
8511
- idleMs,
8512
- timeoutMs,
8513
- lastActivityReason: attemptLastProgressReason,
8514
- details: attemptLastProgressDetails
8515
- });
8516
- }
8517
- function fireCompletionIdleTimeout() {
8518
- if (params.isCompleted() || params.isTerminalTurnNotificationQueued() || params.signal.aborted || !completionIdleWatchArmed || params.getActiveAppServerTurnRequests() > 0 || params.getActiveCompletionBlockerItemCount() > 0) return;
8519
- const timeoutMs = completionIdleTimeoutOverrideMs ?? turnCompletionIdleTimeoutMs;
8520
- const idleMs = Math.max(0, Date.now() - completionLastActivityAt);
8521
- if (idleMs < timeoutMs) {
8522
- scheduleCompletionIdleWatch();
8523
- return;
8524
- }
8525
- const details = {
8526
- ...completionLastActivityDetails,
8527
- activeAppServerTurnRequests: params.getActiveAppServerTurnRequests(),
8528
- activeTurnItemCount: params.getActiveTurnItemCount(),
8529
- terminalTurnNotificationQueued: params.isTerminalTurnNotificationQueued(),
8530
- completionIdleWatchArmed,
8531
- assistantCompletionIdleWatchArmed,
8532
- terminalIdleWatchArmed
8533
- };
8534
- reportTimeout({
8535
- kind: "completion",
8536
- idleMs,
8537
- timeoutMs,
8538
- lastActivityReason: completionLastActivityReason,
8539
- details
8540
- });
8541
- }
8542
- function fireTerminalIdleTimeout() {
8543
- if (params.isCompleted() || params.signal.aborted || !terminalIdleWatchArmed) return;
8544
- if (!params.isTerminalTurnNotificationQueued() && params.getActiveAppServerTurnRequests() > 0) {
8545
- scheduleTerminalIdleWatch();
8546
- return;
8547
- }
8548
- const timeoutMs = terminalIdleTimeoutMs();
8549
- const idleMs = Math.max(0, Date.now() - completionLastActivityAt);
8550
- if (idleMs < timeoutMs) {
8551
- scheduleTerminalIdleWatch();
8552
- return;
8553
- }
8554
- reportTimeout({
8555
- kind: "terminal",
8556
- idleMs,
8557
- timeoutMs,
8558
- lastActivityReason: completionLastActivityReason,
8559
- details: {
8560
- ...completionLastActivityDetails,
8561
- terminalTurnNotificationQueued: params.isTerminalTurnNotificationQueued()
8562
- }
7261
+ if (!params.signal.aborted) {
7262
+ params.signal.addEventListener("abort", dispose, { once: true });
7263
+ if (params.timeoutMs >= MAX_TIMER_TIMEOUT_MS) {
7264
+ deadline = { kind: "unlimited" };
7265
+ params.onDeadlineChanged?.({ kind: "unlimited" });
7266
+ } else armDeadline({
7267
+ kind: "execution",
7268
+ startedAtMs: params.startedAtMs,
7269
+ timeoutMs: params.timeoutMs
8563
7270
  });
8564
7271
  }
8565
7272
  return {
8566
- isCompletionIdleWatchArmed: () => completionIdleWatchArmed,
8567
- isCompletionIdleWatchPinnedByTerminalError: () => completionIdleWatchPinnedByTerminalError,
8568
- isAssistantCompletionIdleWatchArmed: () => assistantCompletionIdleWatchArmed,
8569
- armAttemptIdleWatch: () => {
8570
- attemptIdleWatchArmed = true;
8571
- scheduleAttemptIdleWatch();
8572
- },
8573
- armTerminalIdleWatch: () => {
8574
- terminalIdleWatchArmed = true;
8575
- scheduleTerminalIdleWatch();
8576
- },
8577
- armCompletionIdleWatch: (options) => {
8578
- completionIdleWatchArmed = true;
8579
- completionIdleWatchPinnedByTerminalError = options?.pinnedByTerminalError === true;
8580
- completionIdleTimeoutOverrideMs = options?.timeoutMs !== void 0 ? resolveWatchTimeoutMs(options.timeoutMs) : void 0;
8581
- scheduleCompletionIdleWatch();
8582
- },
8583
- disarmCompletionIdleWatch: () => {
8584
- completionIdleWatchArmed = false;
8585
- completionIdleWatchPinnedByTerminalError = false;
8586
- completionIdleTimeoutOverrideMs = void 0;
8587
- clearCompletionIdleTimer();
8588
- },
8589
- armAssistantCompletionIdleWatch: (details) => {
8590
- assistantCompletionIdleWatchArmed = true;
8591
- assistantCompletionLastActivityAt = Date.now();
8592
- assistantCompletionLastActivityDetails = details;
8593
- scheduleAssistantCompletionIdleWatch();
8594
- },
8595
- disarmAssistantCompletionIdleWatch: () => {
8596
- assistantCompletionIdleWatchArmed = false;
8597
- assistantCompletionLastActivityDetails = void 0;
8598
- clearTimer("assistant");
8599
- },
8600
- touchActivity: (reason, options) => {
8601
- completionLastActivityAt = Date.now();
8602
- completionLastActivityReason = reason;
8603
- completionLastActivityDetails = options?.details;
8604
- completionIdleTimeoutOverrideMs = void 0;
8605
- if (options?.attemptProgress) recordAttemptProgress(reason, options);
8606
- params.onProgressDiagnostic(reason);
8607
- if (options?.arm) {
8608
- completionIdleWatchArmed = true;
8609
- completionIdleWatchPinnedByTerminalError = false;
8610
- }
8611
- scheduleProgressWatches();
8612
- },
8613
- noteNotificationReceived: (method, options) => {
8614
- const now = Date.now();
8615
- completionLastActivityAt = Math.max(completionLastActivityAt, Math.min(now, options?.receivedAtMs ?? now));
8616
- completionLastActivityReason = `notification:${method}`;
8617
- if (options?.details !== void 0) completionLastActivityDetails = options.details;
8618
- if (options?.attemptProgress) recordAttemptProgress(completionLastActivityReason, options);
8619
- if (params.isTerminalTurnNotificationQueued()) scheduleTerminalIdleWatch();
8620
- },
8621
- extendAttemptIdleWatch: (timeoutMs) => {
8622
- attemptIdleTimeoutOverrideMs = resolveWatchTimeoutMs(timeoutMs);
8623
- scheduleAttemptIdleWatch();
7273
+ ownsExecutionWait: () => deadline.kind === "unlimited" || deadline.kind === "execution" && Date.now() < deadline.startedAtMs + deadline.timeoutMs,
7274
+ beginSettlement: (startedAtMs) => {
7275
+ if (deadline.kind === "closed" || deadline.kind === "settlement") return;
7276
+ armDeadline({
7277
+ kind: "settlement",
7278
+ startedAtMs,
7279
+ timeoutMs: TURN_TERMINAL_SETTLEMENT_TIMEOUT_MS
7280
+ });
8624
7281
  },
8625
- scheduleProgressWatches,
8626
- clearCompletionIdleTimer,
8627
- clearAllTimers
7282
+ dispose
8628
7283
  };
8629
7284
  }
8630
7285
  //#endregion
@@ -8632,9 +7287,8 @@ function createCodexAttemptTurnWatchController(params) {
8632
7287
  const CODEX_NATIVE_HOOK_RELAY_RENEW_INTERVAL_MS = 6e4;
8633
7288
  function createCodexAttemptTurnState(resources) {
8634
7289
  const { prompt, state: resourceState, projectorRef, trajectoryRecorder, startupTimeoutMs } = resources;
8635
- const { context } = prompt;
8636
- const { connection } = context.runtime;
8637
- const { params, options, appServer, runAbortController } = connection;
7290
+ const { connection } = prompt.context.runtime;
7291
+ const { params, options, runAbortController } = connection;
8638
7292
  const state = {
8639
7293
  latestStartupErrorNotification: void 0,
8640
7294
  rateLimitsRevisionBeforeLastTurnStart: void 0,
@@ -8644,14 +7298,7 @@ function createCodexAttemptTurnState(resources) {
8644
7298
  localCompletionRequested: false,
8645
7299
  terminalTurnNotificationQueued: false,
8646
7300
  sawCodexInterruptMarker: false,
8647
- timedOut: false,
8648
- turnCompletionIdleTimedOut: false,
8649
- turnWatchTimeoutKind: void 0,
8650
- turnWatchTimeoutIdleMs: void 0,
8651
- turnWatchTimeoutMs: void 0,
8652
- turnWatchTimeoutLastActivityReason: void 0,
8653
- turnWatchTimeoutDetails: void 0,
8654
- turnCompletionIdleTimeoutMessage: void 0,
7301
+ timeout: void 0,
8655
7302
  clientClosedPromptError: void 0,
8656
7303
  clientClosedDiagnostic: void 0,
8657
7304
  clientClosedAbort: false,
@@ -8660,26 +7307,16 @@ function createCodexAttemptTurnState(resources) {
8660
7307
  lifecycleTerminalEmitted: false,
8661
7308
  nativeHookRelayLastRenewedAt: 0,
8662
7309
  activeAppServerTurnRequests: 0,
8663
- activeAppServerTurnRequestsWithoutTimeout: 0,
8664
- unsettledFinalizationHookCount: 0,
8665
- rejectedFinalizationHookAssistant: void 0,
8666
- turnCrossedToolHandoff: false,
7310
+ activeLocalProjections: 0,
7311
+ projectionClosed: false,
8667
7312
  pendingTerminalDynamicToolRelease: void 0,
8668
7313
  terminalDynamicToolReleaseCheckScheduled: false,
8669
7314
  currentTurnHadNonTerminalDynamicToolResult: false
8670
7315
  };
8671
7316
  const { promise: completion, resolve: resolveCompletion } = createDeferred();
8672
- const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs(options.turnCompletionIdleTimeoutMs ?? appServer.turnCompletionIdleTimeoutMs);
8673
- const turnAssistantCompletionIdleTimeoutMs = resolveCodexTurnAssistantCompletionIdleTimeoutMs(options.turnAssistantCompletionIdleTimeoutMs ?? appServer.turnAssistantCompletionIdleTimeoutMs);
8674
- const postToolRawAssistantCompletionIdleTimeoutMs = resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs(options.postToolRawAssistantCompletionIdleTimeoutMs ?? appServer.postToolRawAssistantCompletionIdleTimeoutMs, turnAssistantCompletionIdleTimeoutMs);
8675
- const turnTerminalIdleTimeoutMs = resolveCodexTurnTerminalIdleTimeoutMs(options.turnTerminalIdleTimeoutMs, params.runTimeoutOverrideMs);
8676
- const turnAttemptIdleTimeoutMs = Math.max(100, Math.floor(params.timeoutMs));
8677
7317
  const pendingOpenClawDynamicToolCompletionIds = /* @__PURE__ */ new Set();
8678
7318
  const openClawDynamicToolExecutions = createCodexDynamicToolExecutionRegistry();
8679
7319
  const activeTurnItemIds = /* @__PURE__ */ new Set();
8680
- const activeCompletionBlockerItemIds = /* @__PURE__ */ new Set();
8681
- const activeFinalizationHookRunIds = /* @__PURE__ */ new Set();
8682
- const finalizationHookBatchStatuses = /* @__PURE__ */ new Map();
8683
7320
  const turnIdRef = {};
8684
7321
  const userInputBridgeRef = {};
8685
7322
  const steeringQueueRef = {};
@@ -8687,7 +7324,7 @@ function createCodexAttemptTurnState(resources) {
8687
7324
  if (state.completed) return;
8688
7325
  state.completed = true;
8689
7326
  steeringQueueRef.current?.cancel();
8690
- turnWatches.clearAllTimers();
7327
+ deadlines.beginSettlement(Date.now());
8691
7328
  resolveCompletion();
8692
7329
  };
8693
7330
  const interruptTurn = async (turnId, completionOptions) => {
@@ -8709,86 +7346,62 @@ function createCodexAttemptTurnState(resources) {
8709
7346
  state.nativeHookRelayLastRenewedAt = now;
8710
7347
  resourceState.nativeHookRelay.renew(resolveCodexNativeHookRelayTtlMs({
8711
7348
  explicitTtlMs: void 0,
8712
- attemptTimeoutMs: turnAttemptIdleTimeoutMs,
7349
+ attemptTimeoutMs: params.timeoutMs,
8713
7350
  startupTimeoutMs,
8714
7351
  turnStartTimeoutMs: params.timeoutMs
8715
7352
  }));
8716
7353
  };
8717
- const turnWatches = createCodexAttemptTurnWatchController({
8718
- threadId: resourceState.thread.threadId,
7354
+ const noteProgress = (reason) => {
7355
+ if (state.completed || state.projectionClosed || runAbortController.signal.aborted) return;
7356
+ renewNativeHookRelayForTurnProgress();
7357
+ params.onRunProgress?.({
7358
+ reason,
7359
+ provider: params.provider,
7360
+ model: params.modelId,
7361
+ backend: "codex-app-server"
7362
+ });
7363
+ emitTrustedDiagnosticEvent({
7364
+ type: "run.progress",
7365
+ runId: params.runId,
7366
+ sessionId: params.sessionId,
7367
+ sessionKey: params.sessionKey,
7368
+ reason: `codex_app_server:${reason}`
7369
+ });
7370
+ };
7371
+ const deadlines = createCodexAttemptDeadlineController({
7372
+ startedAtMs: connection.attemptStartedAt,
7373
+ timeoutMs: params.timeoutMs,
8719
7374
  signal: runAbortController.signal,
8720
- getTurnId: () => turnIdRef.current,
8721
- isCompleted: () => state.completed,
8722
- isTerminalTurnNotificationQueued: () => state.terminalTurnNotificationQueued,
8723
- getActiveAppServerTurnRequests: () => state.activeAppServerTurnRequests,
8724
- getActiveAppServerTurnRequestsWithoutTimeout: () => state.activeAppServerTurnRequestsWithoutTimeout,
8725
- getActiveTurnItemCount: () => activeTurnItemIds.size,
8726
- getActiveCompletionBlockerItemCount: () => activeCompletionBlockerItemIds.size,
8727
- getActiveFinalizationHookCount: () => state.unsettledFinalizationHookCount,
8728
- canReleaseAssistantCompletionIdle: () => projectorRef.current?.hasLatestTerminalAssistantCandidateText() === true,
8729
- turnCompletionIdleTimeoutMs,
8730
- turnAssistantCompletionIdleTimeoutMs,
8731
- turnAttemptIdleTimeoutMs,
8732
- turnTerminalIdleTimeoutMs,
8733
- interruptTimeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS,
8734
- onInterruptTurn: ({ turnId, timeoutMs }) => interruptTurn(turnId, {
8735
- locallyCompleted: true,
8736
- timeoutMs
8737
- }),
7375
+ onDeadlineChanged: params.onAttemptDeadlineChanged,
8738
7376
  onTimeout: (timeout) => {
8739
- state.timedOut = true;
8740
- state.turnCompletionIdleTimedOut = true;
8741
- state.turnWatchTimeoutKind = timeout.kind;
8742
- state.turnWatchTimeoutIdleMs = timeout.idleMs;
8743
- state.turnWatchTimeoutMs = timeout.timeoutMs;
8744
- state.turnWatchTimeoutLastActivityReason = timeout.lastActivityReason;
8745
- state.turnWatchTimeoutDetails = timeout.details;
8746
- state.turnCompletionIdleTimeoutMessage = "codex app-server turn idle timed out waiting for turn/completed";
7377
+ state.timeout = timeout;
8747
7378
  projectorRef.current?.markTimedOut();
8748
- },
8749
- onAbort: (reason) => runAbortController.abort(reason),
8750
- onCompleted: completeTurn,
8751
- onRecordEvent: (name, fields) => trajectoryRecorder?.recordEvent(name, fields),
8752
- onAttemptProgress: (reason) => {
8753
- renewNativeHookRelayForTurnProgress();
8754
- params.onRunProgress?.({
8755
- reason,
8756
- provider: params.provider,
8757
- model: params.modelId,
8758
- backend: "codex-app-server"
8759
- });
8760
- },
8761
- onProgressDiagnostic: (reason) => {
8762
- emitTrustedDiagnosticEvent({
8763
- type: "run.progress",
8764
- runId: params.runId,
8765
- sessionId: params.sessionId,
8766
- sessionKey: params.sessionKey,
8767
- reason: `codex_app_server:${reason}`
8768
- });
7379
+ const error = /* @__PURE__ */ new Error(timeout.kind === "execution" ? "codex app-server execution budget timed out" : "codex app-server terminal settlement timed out");
7380
+ const fields = {
7381
+ threadId: resourceState.thread.threadId,
7382
+ turnId: turnIdRef.current,
7383
+ ...timeout
7384
+ };
7385
+ trajectoryRecorder?.recordEvent(`turn.${timeout.kind}_timeout`, fields);
7386
+ embeddedAgentLog.warn(error.message, fields);
7387
+ params.onAttemptTimeout?.(error);
7388
+ runAbortController.abort(error);
8769
7389
  }
8770
7390
  });
8771
7391
  return {
8772
7392
  state,
8773
7393
  completion,
8774
- turnCompletionIdleTimeoutMs,
8775
- turnAssistantCompletionIdleTimeoutMs,
8776
- postToolRawAssistantCompletionIdleTimeoutMs,
8777
- turnTerminalIdleTimeoutMs,
8778
- turnAttemptIdleTimeoutMs,
8779
7394
  pendingOpenClawDynamicToolCompletionIds,
8780
7395
  openClawDynamicToolExecutions,
8781
7396
  activeTurnItemIds,
8782
- activeCompletionBlockerItemIds,
8783
- activeFinalizationHookRunIds,
8784
- finalizationHookBatchStatuses,
8785
7397
  turnIdRef,
8786
7398
  userInputBridgeRef,
8787
7399
  steeringQueueRef,
8788
7400
  completeTurn,
8789
7401
  interruptTurn,
8790
7402
  renewNativeHookRelayForTurnProgress,
8791
- turnWatches
7403
+ noteProgress,
7404
+ deadlines
8792
7405
  };
8793
7406
  }
8794
7407
  //#endregion
@@ -8804,21 +7417,26 @@ async function runCodexAppServerAttempt(params, options) {
8804
7417
  attemptTools.runtimeYieldCompletionClaim.current = () => resources.state.nativeHookRelay?.hasClaimedDirectChild() ?? false;
8805
7418
  await startCodexAttemptRuntime(resources);
8806
7419
  const turnRuntime = createCodexAttemptTurnState(resources);
8807
- const lifecycle = createCodexAttemptLifecycleController(resources, turnRuntime);
8808
- const notifications = createCodexAttemptNotificationController(resources, turnRuntime, lifecycle);
8809
- const { ensureCurrentThreadRoute } = await prepareCodexAttemptRoute(resources, turnRuntime, notifications, createCodexAttemptServerRequestController(resources, turnRuntime, lifecycle).handleServerRequest);
8810
- const turnRequest = await prepareCodexAttemptTurnRequest(resources, turnRuntime, ensureCurrentThreadRoute, notifications.waitForActiveNativeTurnCompletion);
8811
- const turnStart = await startCodexAttemptTurn(resources, turnRuntime, notifications, turnRequest);
8812
- if ("result" in turnStart) return turnStart.result;
8813
- const activeTurn = await activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notifications, turnStart.turn);
8814
- let finalizedResult;
8815
7420
  try {
8816
- finalizedResult = await finalizeCodexAttempt(resources, turnRuntime, lifecycle, notifications, turnRequest, activeTurn);
7421
+ const lifecycle = createCodexAttemptLifecycleController(resources, turnRuntime);
7422
+ const notifications = createCodexAttemptNotificationController(resources, turnRuntime, lifecycle);
7423
+ const { ensureCurrentThreadRoute } = await prepareCodexAttemptRoute(resources, turnRuntime, notifications, createCodexAttemptServerRequestController(resources, turnRuntime, lifecycle).handleServerRequest);
7424
+ const turnRequest = await prepareCodexAttemptTurnRequest(resources, turnRuntime, ensureCurrentThreadRoute, notifications.waitForActiveNativeTurnCompletion);
7425
+ const turnStart = await startCodexAttemptTurn(resources, turnRuntime, notifications, turnRequest);
7426
+ if ("result" in turnStart) return turnStart.result;
7427
+ const activeTurn = activateCodexAttemptTurn(resources, turnRuntime, lifecycle, notifications, turnStart.turn);
7428
+ let finalizedResult;
7429
+ try {
7430
+ await activeTurn.ready;
7431
+ finalizedResult = await finalizeCodexAttempt(resources, turnRuntime, lifecycle, notifications, turnRequest, activeTurn);
7432
+ } finally {
7433
+ await cleanupCodexAttempt(resources, turnRuntime, lifecycle, turnRequest, activeTurn);
7434
+ }
7435
+ if (resources.state.executionDisconnectError && !connection.terminalState.explicitCancellationObserved) throw resources.state.executionDisconnectError;
7436
+ return finalizedResult;
8817
7437
  } finally {
8818
- await cleanupCodexAttempt(resources, turnRuntime, lifecycle, turnRequest, activeTurn);
7438
+ turnRuntime.deadlines.dispose();
8819
7439
  }
8820
- if (resources.state.executionDisconnectError && !connection.terminalState.explicitCancellationObserved) throw resources.state.executionDisconnectError;
8821
- return finalizedResult;
8822
7440
  }
8823
7441
  //#endregion
8824
7442
  export { runCodexAppServerAttempt };