@openclaw/codex 2026.9.1 → 2026.9.3

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 (109) hide show
  1. package/README.md +2 -0
  2. package/dist/api.js +18 -23
  3. package/dist/apply-B6el6hdO.js +357 -0
  4. package/dist/{attempt-client-cleanup-DYHcRQAa.js → attempt-client-cleanup-D6cR54TV.js} +113 -49
  5. package/dist/{attempt-context-DmQUNWwT.js → attempt-context-AO4DSjE0.js} +11 -9
  6. package/dist/{event-projector-usage-C5mcrGjp.js → attempt-notifications--H8zXR5-.js} +3 -89
  7. package/dist/auth-cache-key-Kq6oerr6.js +498 -0
  8. package/dist/auth-profile-CwzO4_RG.js +43 -0
  9. package/dist/auth-profile-selection-DFu9e167.js +45 -0
  10. package/dist/{binding-connection-E6uDsPlx.js → binding-connection-QqgcPCgk.js} +18 -3
  11. package/dist/{bounded-turn-wzJrwKKe.js → bounded-turn-lRpmZHFv.js} +54 -150
  12. package/dist/build-state-6RL6qohz.js +19 -0
  13. package/dist/{capabilities-dIjNVCYH.js → capabilities-BeqLPbtM.js} +2 -1
  14. package/dist/cli-metadata.js +1 -1
  15. package/dist/client-BEKu4yDx.js +3983 -0
  16. package/dist/command-dispatch-CisT9Jqq.js +21 -0
  17. package/dist/{command-formatters-CLHhEB1Q.js → command-formatters-DeTfrapr.js} +2 -2
  18. package/dist/{command-handlers-CTGb2csu.js → command-handlers-BfIsJEuA.js} +158 -76
  19. package/dist/command-rpc-BMqRQOFX.js +226 -0
  20. package/dist/{compact-_t0HQrUS.js → compact-CGJZ2rlb.js} +89 -49
  21. package/dist/config-JDfTI4VI.js +106 -0
  22. package/dist/{config-BhmfC1s6.js → config-options-jnIFvwxD.js} +208 -654
  23. package/dist/config-runtime-BWf6JLWG.js +11 -0
  24. package/dist/config-utils-C57JiJrU.js +69 -0
  25. package/dist/conversation-binding-CxOlHiix.js +889 -0
  26. package/dist/{plugin-marketplace-discovery-CfFlKqDl.js → conversation-control-CUCrSIQI.js} +36 -155
  27. package/dist/doctor-contract-api.js +8 -9
  28. package/dist/{dynamic-tools-BU9tmlYm.js → dynamic-tools-D-JBoaLt.js} +734 -698
  29. package/dist/{effective-mcp-catalog-DzYBW4h1.js → effective-mcp-catalog-J_5Ep5Um.js} +4 -3
  30. package/dist/{elicitation-input-DMkeki8d.js → elicitation-input-DPoK8F7w.js} +8 -22
  31. package/dist/ephemeral-turn-DSwzP8YY.js +101 -0
  32. package/dist/{event-projector-assistant-message-DHRFXKbS.js → event-projector-assistant-message-BmBM4qBV.js} +23 -27
  33. package/dist/event-projector-usage-CIyrYYid.js +102 -0
  34. package/dist/{harness-DXVZ5II5.js → harness-FR6eQgyO.js} +47 -17
  35. package/dist/harness.js +1 -1
  36. package/dist/history-rejection-B4nOXNXF.js +13 -0
  37. package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
  38. package/dist/incognito-session-uhrBF6wJ.js +15 -0
  39. package/dist/index.js +413 -2893
  40. package/dist/{isolated-completion-kg9kv4iH.js → isolated-completion-DHBefrzg.js} +12 -21
  41. package/dist/{managed-binary-4-Vjz5GH.js → managed-binary-DrjjRRUD.js} +17 -84
  42. package/dist/managed-thread-store-D1t5jh3G.js +66 -0
  43. package/dist/media-understanding-provider.js +4 -1
  44. package/dist/{model-catalog-CUM68Hkl.js → model-catalog-B_6M04Wo.js} +6 -5
  45. package/dist/{models-sYnEc0gd.js → models-P9k2qAof.js} +4 -4
  46. package/dist/native-execution-policy-umd0tgh4.js +128 -0
  47. package/dist/{native-subagent-monitor-CnH147jH.js → native-subagent-monitor-BbCUKRwC.js} +76 -48
  48. package/dist/node-cli-sessions-rj0ysJwM.js +527 -0
  49. package/dist/{node-exec-server.runtime-CstNHCmC.js → node-exec-server.runtime-BY_ctYnT.js} +54 -23
  50. package/dist/plan-BMJdMOEF.js +711 -0
  51. package/dist/plugin-activation-BQK-f5sV.js +133 -0
  52. package/dist/{thread-resume-C7pyVKeW.js → plugin-app-cache-key-CdSQzWyX.js} +5 -53
  53. package/dist/{plugin-approval-roundtrip-CNe-Xrjx.js → plugin-approval-roundtrip-DeW95wB5.js} +1 -1
  54. package/dist/plugin-inventory-CoRCzWW4.js +325 -0
  55. package/dist/plugin-marketplace-discovery-3akBizzF.js +134 -0
  56. package/dist/{plugin-metadata-cache-BFrPhb2c.js → plugin-metadata-cache-DgNfRAGA.js} +13 -9
  57. package/dist/protocol-CAAcVIKF.js +275 -0
  58. package/dist/{provider-capabilities-DW5p4k9w.js → provider-capabilities-W4tfK5Y5.js} +2 -2
  59. package/dist/{rate-limits-CawQJfUS.js → rate-limits-Bqnx1N4z.js} +1 -1
  60. package/dist/request-CGvGOpeR.js +198 -0
  61. package/dist/{run-attempt-D1hJO4fX.js → run-attempt-DwKds3DN.js} +1498 -1028
  62. package/dist/{runtime-artifact-C3jVimFn.js → runtime-artifact-T-NwqeSH.js} +28 -28
  63. package/dist/sandbox-guard-BcT66fWy.js +165 -0
  64. package/dist/{session-binding-BHuTCRZQ.js → session-binding-CKSepwIC.js} +118 -324
  65. package/dist/session-binding-record-CaZjYwNS.js +256 -0
  66. package/dist/session-catalog-HaDcp7Sv.js +1533 -0
  67. package/dist/session-catalog-adoption-BKLtSrwi.js +251 -0
  68. package/dist/session-catalog-archive-D0i2pGj2.js +56 -0
  69. package/dist/session-catalog-home-id-D7-aLiNt.js +190 -0
  70. package/dist/session-catalog-node-adoption-BUuDYY8R.js +182 -0
  71. package/dist/session-catalog-node-continue-CBtc4wkI.js +238 -0
  72. package/dist/session-catalog-parsing-CdD3JhdM.js +280 -0
  73. package/dist/session-catalog-provenance-DkJHo735.js +124 -0
  74. package/dist/{session-cli-CmFEXMAM.js → session-cli-CbgaHh0f.js} +3 -2
  75. package/dist/session-history-Cz-tOOYj.js +96 -0
  76. package/dist/{session-upstream-marker-C351-5Qw.js → session-history-import-DFN7sWRU.js} +5 -49
  77. package/dist/session-history-read-cxPUxyVV.js +50 -0
  78. package/dist/session-history-worker-runtime.js +60 -0
  79. package/dist/{settled-turn-context-D3SNWtUn.js → session-history.worker-CNEvGaU5.js} +75 -69
  80. package/dist/session-history.worker.js +2 -0
  81. package/dist/{session-initialization-BeWzjGiR.js → session-initialization-Jo86C60I.js} +2 -1
  82. package/dist/{session-permission-policy-DeWVTCMj.js → session-permission-policy-B22InTp_.js} +4 -7
  83. package/dist/{session-retirement-BujDjXVF.js → session-retirement-BSNG_mhf.js} +8 -7
  84. package/dist/session-upstream-marker-D15C9NHp.js +46 -0
  85. package/dist/settled-turn-context-Du6gdUDd.js +45 -0
  86. package/dist/{settled-turn-finalizer-BVJx4yoc.js → settled-turn-finalizer-BuLUrZl2.js} +54 -42
  87. package/dist/shared-client-CVhez2Uo.js +2911 -0
  88. package/dist/{side-question-BTZAII4l.js → side-question-DLbr2vUU.js} +135 -191
  89. package/dist/source-Da-h6f7e.js +518 -0
  90. package/dist/thread-archive-guard-D1bP04Tf.js +53 -0
  91. package/dist/{thread-lifecycle-DAcEJ_Om.js → thread-lifecycle-D1pzRwHB.js} +467 -977
  92. package/dist/{thread-ownership-zGokd8i1.js → thread-ownership-D6oASVRu.js} +15 -17
  93. package/dist/{thread-policy-g4KbJPzp.js → thread-policy-D8kI7yAO.js} +2 -2
  94. package/dist/thread-resume-T5cveyxA.js +52 -0
  95. package/dist/timeout-BVw8aaM1.js +33 -0
  96. package/dist/transcript-mirror-attestation-DibE0krH.js +89 -0
  97. package/dist/{transcript-mirror-DTUxFWa_.js → transcript-mirror-t5TOccct.js} +111 -379
  98. package/dist/{transport-stdio-CyPldczm.js → transport-stdio-BGjHoYLr.js} +80 -48
  99. package/dist/{upstream-session-fork-CYlBIGVt.js → upstream-session-fork-De-sLJ2r.js} +94 -166
  100. package/dist/{usage-Bj-rrzkR.js → usage-CPPTmBvZ.js} +4 -3
  101. package/dist/version-omm6IPI3.js +12 -0
  102. package/dist/{web-search-provider.runtime-D2FWSK4w.js → web-search-provider.runtime-CSVgQL0w.js} +3 -3
  103. package/openclaw.plugin.json +22 -1
  104. package/package.json +6 -6
  105. package/dist/request-DYAI5FQE.js +0 -475
  106. package/dist/session-catalog-yyvTWFOw.js +0 -3120
  107. package/dist/session-history-C5J1AKzG.js +0 -87
  108. package/dist/shared-client-DxcfF9KX.js +0 -7494
  109. package/dist/{auth-binding-Ds_QVm-X.js → auth-binding-Co2Vfcf_.js} +1 -1
@@ -1,482 +1,73 @@
1
- import { $t as isJsonObject, E as CodexThreadStartRequestError, Et as isCodexAppServerLiveThreadClaimed, H as isCodexAppServerOverloadError, I as getCodexAppServerClientInstanceId, Jt as readCodexSessionMeta, K as resolveCodexAppServerClientInstanceId, Mt as withAbortableTimeout, Ot as readCodexClientSessionMeta, Q as assertCodexThreadForkResponse, Qt as flattenCodexDynamicToolFunctions, St as consumeCodexAppServerLiveThread, T as CodexThreadBindingConflictError, Tt as isCodexAppServerClientRuntimeLive, W as isCodexAppServerRequestTimeoutError, X as assertCodexThreadAcceptsDirectInput, _ as retainSharedCodexAppServerClientByInstanceId, _t as resolveCodexToolAbortTerminalReason, et as assertCodexThreadStartResponse, jt as unsubscribeCodexAppServerLiveThread, kt as releaseCodexAppServerLiveThread, n as captureCodexAppServerClientLifetime, q as CodexAppServerRpcError, w as CodexAdoptedThreadActiveError } from "./shared-client-DxcfF9KX.js";
2
- import { D as resolveCodexPluginsPolicy, F as resolveCodexAppServerHomeDir, I as resolveCodexAppServerLocalHomeDir, M as CODEX_PLUGINS_MARKETPLACE_NAME, N as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, n as codexSandboxPolicyForTurn, p as assertCodexModelBackedReviewerEffectiveConfig } from "./config-BhmfC1s6.js";
3
- import { a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-BHuTCRZQ.js";
4
- import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, d as unsubscribeCodexThreadBestEffort, l as retireUnsafeCodexTurnClientBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-DYHcRQAa.js";
5
- import { c as serializeCodexAppInventoryError, n as buildCodexAppServerConnectionFingerprint, s as defaultCodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-C7pyVKeW.js";
6
- import { c as withExclusiveCodexAppServerThread, l as isIncognitoSessionKey, o as withCodexAppServerThreadMutation, r as retainCodexAppServerBindingSubscription } from "./thread-ownership-zGokd8i1.js";
7
- import { f as projectBoundedCodexThreadHistory, g as codexCatalogHomeId } from "./transcript-mirror-DTUxFWa_.js";
8
- import { i as refreshCodexThreadPolicy, n as CodexThreadPolicyHandoffError, r as assertCodexSupervisionThreadLineage, t as CodexIncognitoPolicyChangeError } from "./thread-policy-g4KbJPzp.js";
9
- import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
1
+ import { l as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced } from "./session-binding-record-CaZjYwNS.js";
2
+ import { f as assertCodexModelBackedReviewerEffectiveConfig, g as readCodexEffectiveConfig, h as CODEX_SESSION_OVERRIDABLE_LAYER_TYPES, n as codexSandboxPolicyForTurn } from "./config-options-jnIFvwxD.js";
3
+ import { S as resolveCodexPluginsPolicy, a as flattenCodexDynamicToolFunctions, c as resolveCodexAppServerLocalHomeDir, o as isJsonObject, s as resolveCodexAppServerHomeDir } from "./protocol-CAAcVIKF.js";
4
+ import { L as resolveCodexToolAbortTerminalReason, _ as assertCodexThreadAcceptsDirectInput, b as assertCodexThreadStartResponse, d as isCodexAppServerRequestTimeoutError, l as isCodexAppServerOverloadError, p as resolveCodexAppServerClientInstanceId, r as getCodexAppServerClientInstanceId, v as assertCodexThreadForkResponse } from "./client-BEKu4yDx.js";
5
+ import { i as markStartedCodexManagedThread } from "./managed-thread-store-D1t5jh3G.js";
6
+ import { n as withCodexAppServerThreadMutation, t as isIncognitoSessionKey } from "./incognito-session-uhrBF6wJ.js";
7
+ import { i as isCodexThreadReadMissingError, n as readCodexSessionMeta, r as CodexAppServerRpcError } from "./session-catalog-provenance-DkJHo735.js";
8
+ import { n as codexCatalogHomeId, r as projectBoundedCodexThreadHistory } from "./session-catalog-home-id-D7-aLiNt.js";
9
+ import { c as serializeCodexAppInventoryError, o as CodexAppInventoryCache, s as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-CdSQzWyX.js";
10
+ import { t as withAbortableTimeout } from "./timeout-BVw8aaM1.js";
11
+ import "./config-JDfTI4VI.js";
12
+ import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-tupAr-2o.js";
13
+ import { t as assertCodexSessionRuntimeOwnership } from "./binding-connection-QqgcPCgk.js";
14
+ import { n as normalizeCodexAppServerBindingModelProvider, t as isCodexAppServerNativeAuthProfile } from "./auth-profile-CwzO4_RG.js";
15
+ import { C as CodexThreadBindingConflictError, G as unsubscribeCodexAppServerLiveThread, H as readCodexClientSessionMeta, P as consumeCodexAppServerLiveThread, R as isCodexAppServerClientRuntimeLive, S as CodexAdoptedThreadActiveError, U as releaseCodexAppServerLiveThread, _ as retainSharedCodexAppServerClientByInstanceId, n as captureCodexAppServerClientLifetime, w as CodexThreadStartRequestError, z as isCodexAppServerLiveThreadClaimed } from "./shared-client-CVhez2Uo.js";
16
+ import { a as closeCodexStartupClientBestEffort, d as unsubscribeCodexThreadBestEffort, l as retireUnsafeCodexTurnClientBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError, s as isCodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-D6cR54TV.js";
17
+ import { a as readCodexPluginInventory, o as resolveOwnedAppApprovalOverrideKeys } from "./plugin-inventory-CoRCzWW4.js";
18
+ import { t as ensureCodexPluginActivation } from "./plugin-activation-BQK-f5sV.js";
19
+ import { o as resolveCodexSessionBinding, r as hashCodexAppServerBindingFingerprint } from "./session-binding-CKSepwIC.js";
20
+ import { i as refreshCodexThreadPolicy, n as CodexThreadPolicyHandoffError, r as assertCodexSupervisionThreadLineage, t as CodexIncognitoPolicyChangeError } from "./thread-policy-D8kI7yAO.js";
21
+ import { t as resumeCodexAppServerThread } from "./thread-resume-T5cveyxA.js";
22
+ import { r as retainCodexAppServerBindingSubscription, s as withExclusiveCodexAppServerThread } from "./thread-ownership-D6oASVRu.js";
23
+ import "./transcript-mirror-t5TOccct.js";
10
24
  import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
11
- import fs from "node:fs/promises";
12
- import path from "node:path";
13
- import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
14
- import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
15
- import { AgentHarnessPreflightError, SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, buildTemporalContextText, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
16
- import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
17
25
  import * as crypto$1 from "node:crypto";
18
26
  import crypto, { createHash } from "node:crypto";
27
+ import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
28
+ import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
29
+ import path from "node:path";
19
30
  import os from "node:os";
31
+ import fs from "node:fs/promises";
32
+ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
33
+ import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
34
+ import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
35
+ import { createStageTimingTracker, formatStageTimings } from "openclaw/plugin-sdk/time-runtime";
36
+ import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
20
37
  import { isDeepStrictEqual } from "node:util";
21
38
  import { resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
22
- import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
23
- import { z } from "zod";
24
- import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
25
- import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
39
+ import { AgentHarnessPreflightError, SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, buildTemporalContextText, buildUiPresentationPrompt, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, isOpenClawRuntimeContextCustomMessage, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
26
40
  import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
27
- import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
28
41
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
42
+ import { IMAGE_BLOCK_TOKENS } from "openclaw/plugin-sdk/agent-core";
29
43
  import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
30
44
  import { buildCodexUserMcpServersThreadConfigPatchForRun } from "openclaw/plugin-sdk/codex-mcp-projection";
31
45
  import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
32
46
  import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-flags";
33
47
  import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
34
48
  import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
35
- //#region extensions/codex/src/app-server/plugin-inventory.ts
36
- /**
37
- * Reads Codex plugin marketplace state and app inventory to decide which
38
- * plugin-owned apps can be exposed to a native Codex thread.
39
- */
40
- const CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME = `${CODEX_PLUGINS_MARKETPLACE_NAME}-remote`;
41
- const CODEX_PLUGINS_API_MARKETPLACE_NAME = "openai-api-curated";
42
- /** Reads configured Codex plugin state and maps owned apps to readiness diagnostics. */
43
- async function readCodexPluginInventory(params) {
44
- const policy = params.policy ?? resolveCodexPluginsPolicy(params.pluginConfig);
45
- if (!policy.enabled) return {
46
- policy,
47
- records: [],
48
- diagnostics: [{
49
- code: "disabled",
50
- message: "Native Codex plugin support is disabled."
51
- }]
52
- };
53
- const appInventory = readCachedAppInventory(params);
54
- const installedPlugins = await readInstalledCodexPluginMetadata({
55
- ...params,
56
- policy
57
- });
58
- const pluginCatalogs = /* @__PURE__ */ new Map();
59
- const diagnostics = [];
60
- const records = [];
61
- if (appInventory?.state === "missing") diagnostics.push({
62
- code: "app_inventory_missing",
63
- message: "Cached Codex app inventory is missing; plugin apps are excluded for this setup."
64
- });
65
- else if (appInventory?.state === "stale") diagnostics.push({
66
- code: "app_inventory_stale",
67
- message: "Cached Codex app inventory is stale; using stale app readiness and refreshing."
68
- });
69
- for (const pluginPolicy of policy.pluginPolicies) {
70
- if (!pluginPolicy.enabled && !policy.allowAllPlugins) continue;
71
- let listed = installedPlugins;
72
- let resolvedPlugin = findConfiguredMarketplacePlugin(listed, pluginPolicy);
73
- if (!resolvedPlugin && pluginPolicy.enabled && pluginPolicy.marketplaceName !== "workspace-directory") {
74
- const requestParams = buildPluginCatalogRequestParams(params, pluginPolicy.marketplaceName);
75
- const catalogKey = JSON.stringify([requestParams, pluginMetadataCatalogScope(pluginPolicy.marketplaceName)]);
76
- let catalog = pluginCatalogs.get(catalogKey);
77
- if (!catalog) {
78
- catalog = listCodexPluginMetadata(params, pluginPolicy.marketplaceName);
79
- pluginCatalogs.set(catalogKey, catalog);
80
- }
81
- listed = await catalog;
82
- resolvedPlugin = findConfiguredMarketplacePlugin(listed, pluginPolicy);
83
- }
84
- if (!listed.marketplaces.some((marketplace) => marketplaceMatchesConfiguredName(marketplace, pluginPolicy.marketplaceName))) {
85
- diagnostics.push({
86
- code: "marketplace_missing",
87
- plugin: pluginPolicy,
88
- message: `Codex marketplace ${pluginPolicy.marketplaceName} was not found.`
89
- });
90
- continue;
91
- }
92
- if (!resolvedPlugin) {
93
- diagnostics.push({
94
- code: "plugin_missing",
95
- plugin: pluginPolicy,
96
- message: `${pluginPolicy.pluginName} was not found in ${pluginPolicy.marketplaceName}.`
97
- });
98
- continue;
99
- }
100
- const { summary } = resolvedPlugin;
101
- const unavailableByMarketplacePolicy = summary.availability === "DISABLED_BY_ADMIN" || summary.installPolicy === "NOT_AVAILABLE";
102
- if (unavailableByMarketplacePolicy) {
103
- diagnostics.push({
104
- code: "plugin_disabled",
105
- plugin: pluginPolicy,
106
- message: `${pluginPolicy.pluginName} is unavailable in ${pluginPolicy.marketplaceName}.`
107
- });
108
- if (!summary.installed) continue;
109
- }
110
- const detail = await readPluginDetail(params, marketplaceRef(resolvedPlugin.marketplace, pluginPolicy.marketplaceName), pluginPolicy, summary, diagnostics);
111
- const ownedAppIds = detail?.apps.map((app) => app.id).filter(Boolean).toSorted() ?? [];
112
- const appOwnership = resolveAppOwnership({
113
- detail,
114
- appInventory,
115
- summary
116
- });
117
- if (appOwnership === "ambiguous") diagnostics.push({
118
- code: "app_ownership_ambiguous",
119
- plugin: pluginPolicy,
120
- message: `${pluginPolicy.pluginName} has only display-name app matches; apps are not exposed until ownership is stable.`
121
- });
122
- if (summary.installed && !summary.enabled) diagnostics.push({
123
- code: "plugin_disabled",
124
- plugin: pluginPolicy,
125
- message: `${pluginPolicy.pluginName} is installed in Codex but disabled.`
126
- });
127
- const apps = resolveOwnedApps({
128
- pluginPolicy,
129
- detail,
130
- appInventory
131
- });
132
- records.push({
133
- policy: pluginPolicy,
134
- summary,
135
- ...detail ? { detail } : {},
136
- activationRequired: pluginPolicy.enabled && (unavailableByMarketplacePolicy || !summary.installed || !summary.enabled),
137
- authRequired: apps.some((app) => app.needsAuth || !app.accessible),
138
- appOwnership,
139
- ownedAppIds,
140
- apps
141
- });
142
- }
143
- return {
144
- policy,
145
- records,
146
- diagnostics,
147
- ...appInventory ? { appInventory } : {}
148
- };
149
- }
150
- /** Finds a configured plugin only in its authorized marketplace identity. */
151
- function findCodexMarketplacePluginSummary(listed, marketplaceName, pluginName) {
152
- const resolved = findConfiguredMarketplacePlugin(listed, {
153
- marketplaceName,
154
- pluginName
155
- });
156
- return resolved ? {
157
- marketplace: marketplaceRef(resolved.marketplace, marketplaceName),
158
- summary: resolved.summary
159
- } : void 0;
160
- }
161
- /** Builds plugin/read or plugin/install params from a marketplace reference. */
162
- function pluginReadParams(marketplace, pluginName) {
163
- return {
164
- ...marketplace.path ? { marketplacePath: marketplace.path } : {},
165
- ...marketplace.remoteMarketplaceName ? { remoteMarketplaceName: marketplace.remoteMarketplaceName } : {},
166
- pluginName
167
- };
168
- }
169
- /** Returns configured plugin keys whose current metadata may still recover. */
170
- function resolveRecoverableCodexPluginConfigKeys(params) {
171
- return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
172
- pluginPolicy,
173
- metadataCache: params.metadataCache,
174
- appCacheKey: params.appCacheKey,
175
- configCwd: params.configCwd
176
- })).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
177
- }
178
- async function listCodexPluginMetadata(params, marketplaceName) {
179
- const requestParams = buildPluginCatalogRequestParams(params, marketplaceName);
180
- if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
181
- return (await params.metadataCache.load({
182
- appCacheKey: params.appCacheKey,
183
- queryKind: "curated-global",
184
- requestParams,
185
- catalogScope: pluginMetadataCatalogScope(marketplaceName),
186
- request: async (method, listedParams) => await params.request(method, listedParams),
187
- cacheable: (response) => response.marketplaces.some((marketplace) => marketplaceMatchesConfiguredName(marketplace, marketplaceName))
188
- })).response;
189
- }
190
- async function readInstalledCodexPluginMetadata(params) {
191
- const requestParams = params.configCwd ? { cwds: [params.configCwd] } : {};
192
- if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/installed", requestParams);
193
- return (await params.metadataCache.load({
194
- appCacheKey: params.appCacheKey,
195
- queryKind: "installed",
196
- requestParams,
197
- request: async (method, installedParams) => await params.request(method, installedParams),
198
- cacheable: (response) => params.policy.pluginPolicies.every((pluginPolicy) => {
199
- if (!pluginPolicy.enabled && !params.policy.allowAllPlugins) return true;
200
- return Boolean(findConfiguredMarketplacePlugin(response, pluginPolicy));
201
- })
202
- })).response;
203
- }
204
- function isSettledMissingPluginPolicy(params) {
205
- const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "installed" : "curated-global";
206
- const requestParams = queryKind === "installed" ? params.configCwd ? { cwds: [params.configCwd] } : {} : buildPluginCatalogRequestParams(params, params.pluginPolicy.marketplaceName);
207
- const listed = params.metadataCache.read(params.appCacheKey, queryKind, requestParams, queryKind === "curated-global" ? pluginMetadataCatalogScope(params.pluginPolicy.marketplaceName) : void 0)?.response;
208
- if (!listed) return false;
209
- return !findConfiguredMarketplacePlugin(listed, params.pluginPolicy);
210
- }
211
- function pluginMetadataCatalogScope(marketplaceName) {
212
- return isOpenAiCuratedMarketplaceName(marketplaceName) ? void 0 : marketplaceName;
213
- }
214
- function buildPluginCatalogRequestParams(params, marketplaceName) {
215
- const marketplaceKinds = marketplaceName === "created-by-me-remote" ? ["created-by-me-remote"] : marketplaceName.startsWith("workspace-shared-with-me") ? ["shared-with-me"] : void 0;
216
- return {
217
- ...params.configCwd ? { cwds: [params.configCwd] } : {},
218
- ...marketplaceKinds ? { marketplaceKinds: [...marketplaceKinds] } : {}
219
- };
220
- }
221
- function readCachedAppInventory(params) {
222
- if (!params.appCache || !params.appCacheKey) return;
223
- const request = async (method, requestParams) => await params.request(method, requestParams);
224
- return params.appCache.read({
225
- key: params.appCacheKey,
226
- request,
227
- nowMs: params.nowMs,
228
- suppressRefresh: params.suppressAppInventoryRefresh
229
- });
230
- }
231
- async function readPluginDetail(params, marketplace, pluginPolicy, summary, diagnostics) {
232
- if (params.readPluginDetails === false) return;
233
- if (marketplace.remoteMarketplaceName && !summary.remotePluginId) {
234
- diagnostics.push({
235
- code: "plugin_detail_unavailable",
236
- plugin: pluginPolicy,
237
- message: `${pluginPolicy.pluginName} detail unavailable: Codex did not return a remote plugin id.`
238
- });
239
- return;
240
- }
241
- try {
242
- return (await params.request("plugin/read", pluginReadParams(marketplace, marketplace.remoteMarketplaceName && summary.remotePluginId ? summary.remotePluginId : pluginPolicy.pluginName))).plugin;
243
- } catch (error) {
244
- diagnostics.push({
245
- code: "plugin_detail_unavailable",
246
- plugin: pluginPolicy,
247
- message: `${pluginPolicy.pluginName} detail unavailable: ${error instanceof Error ? error.message : String(error)}`
248
- });
249
- return;
250
- }
251
- }
252
- function resolveAppOwnership(params) {
253
- if (params.detail && params.detail.apps.length > 0) return "proven";
254
- return (params.appInventory?.snapshot?.apps ?? []).filter((app) => app.pluginDisplayNames.some((displayName) => displayName === params.summary.name)).length > 0 ? "ambiguous" : "none";
255
- }
256
- function resolveOwnedApps(params) {
257
- const detailApps = params.detail?.apps ?? [];
258
- if (detailApps.length === 0) return [];
259
- if (params.appInventory?.state === "missing") {
260
- embeddedAgentLog.warn("codex plugin inventory missing app inventory for detail apps", {
261
- configKey: params.pluginPolicy.configKey,
262
- pluginName: params.pluginPolicy.pluginName,
263
- appIds: detailApps.map((app) => app.id).toSorted()
264
- });
265
- return [];
266
- }
267
- const appInfoById = new Map((params.appInventory?.snapshot?.apps ?? []).map((app) => [app.id, app]));
268
- return detailApps.map((app) => {
269
- const info = appInfoById.get(app.id);
270
- if (!info) return {
271
- id: app.id,
272
- name: app.name,
273
- accessible: false,
274
- enabled: false,
275
- needsAuth: true
276
- };
277
- return Object.assign({
278
- id: app.id,
279
- name: app.name,
280
- accessible: info.isAccessible,
281
- enabled: info.isEnabled,
282
- needsAuth: !info.isAccessible
283
- }, resolveOwnedAppApprovalOverrideKeys(info));
284
- }).toSorted((left, right) => left.id.localeCompare(right.id));
285
- }
286
- /** Returns current tool keys whose overrides could bypass the requested reviewer. */
287
- function resolveOwnedAppApprovalOverrideKeys(app) {
288
- if (!app.toolSummaries) return {};
289
- const appName = app.name.trim();
290
- const appNameLower = appName.toLowerCase();
291
- const keys = app.toolSummaries.filter((tool) => !tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
292
- appName,
293
- appNameLower,
294
- tool
295
- }));
296
- return { approvalOverrideToolConfigKeys: Array.from(new Set(keys)).toSorted() };
297
- }
298
- function resolveAppToolConfigKeys(params) {
299
- const keys = [params.tool.name];
300
- if (params.tool.title) keys.push(params.tool.title);
301
- if (params.appName) keys.push(`${params.appName}_${params.tool.name}`);
302
- if (params.appNameLower && params.appNameLower !== params.appName) keys.push(`${params.appNameLower}_${params.tool.name}`);
303
- return keys;
304
- }
305
- function findPluginSummary(marketplace, pluginName) {
306
- const exact = marketplace.plugins.find((plugin) => plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}`);
307
- if (exact) return exact;
308
- const matches = marketplace.plugins.filter((plugin) => plugin.name === pluginName || pluginNameFromPluginId(plugin.id, marketplace.name) === pluginName);
309
- return matches.length === 1 ? matches[0] : void 0;
310
- }
311
- function findConfiguredMarketplacePlugin(listed, plugin) {
312
- if (plugin.marketplaceName === "workspace-directory") return findWorkspaceMarketplacePlugin(listed, plugin.pluginName);
313
- for (const marketplace of listed.marketplaces) {
314
- if (!marketplaceMatchesConfiguredName(marketplace, plugin.marketplaceName)) continue;
315
- const summary = findPluginSummary(marketplace, plugin.pluginName);
316
- if (summary) return {
317
- marketplace,
318
- summary
319
- };
320
- }
321
- }
322
- function marketplaceMatchesConfiguredName(marketplace, configuredMarketplaceName) {
323
- return isOpenAiCuratedMarketplaceName(configuredMarketplaceName) ? isOpenAiCuratedMarketplace(marketplace) : marketplace.name === configuredMarketplaceName;
324
- }
325
- function findWorkspaceMarketplacePlugin(listed, pluginName) {
326
- const marketplace = listed.marketplaces.find((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME);
327
- const summary = marketplace?.plugins.find((plugin) => plugin.id === pluginName);
328
- return marketplace && summary ? {
329
- marketplace,
330
- summary
331
- } : void 0;
332
- }
333
- function pluginNameFromPluginId(pluginId, marketplaceName) {
334
- const trimmed = pluginId.trim();
335
- if (!trimmed) return;
336
- const marketplaceSuffix = `@${marketplaceName}`;
337
- return (trimmed.endsWith(marketplaceSuffix) ? trimmed.slice(0, -marketplaceSuffix.length) : trimmed).split("/").at(-1)?.trim() || void 0;
338
- }
339
- function marketplaceRef(marketplace, name) {
340
- return {
341
- name,
342
- ...marketplace.path ? { path: marketplace.path } : {},
343
- ...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
344
- };
345
- }
346
- /** True for any supported OpenAI curated marketplace wire name, matching Codex's own curated predicate. */
347
- function isOpenAiCuratedMarketplace(marketplace) {
348
- return isOpenAiCuratedMarketplaceName(marketplace.name);
349
- }
350
- /** True for all Codex wire aliases of the same OpenAI-curated catalog. */
351
- function isOpenAiCuratedMarketplaceName(marketplaceName) {
352
- return marketplaceName === "openai-curated" || marketplaceName === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME || marketplaceName === CODEX_PLUGINS_API_MARKETPLACE_NAME;
353
- }
354
- //#endregion
355
- //#region extensions/codex/src/app-server/plugin-activation.ts
356
- /**
357
- * Activates legacy curated Codex plugins while requiring owner-managed
358
- * installation for every other marketplace.
359
- */
360
- /** Activates legacy curated plugins without granting install authority to other marketplaces. */
361
- async function ensureCodexPluginActivation(params) {
362
- if (params.identity.marketplaceName === "workspace-directory") return activationFailure(params.identity, "disabled", { message: "workspace-directory plugins must be installed and enabled outside OpenClaw before use." });
363
- if (!isOpenAiCuratedMarketplaceName(params.identity.marketplaceName)) {
364
- const target = params.identity.pluginName.endsWith(`@${params.identity.marketplaceName}`) ? params.identity.pluginName : `${params.identity.pluginName}@${params.identity.marketplaceName}`;
365
- return activationFailure(params.identity, "disabled", { message: `${params.identity.marketplaceName} plugins must be installed and enabled by an owner before use. Run /codex plugins install ${target}.` });
366
- }
367
- const listed = await listCuratedCodexPluginMetadata(params);
368
- const resolved = findCodexMarketplacePluginSummary(listed, params.identity.marketplaceName, params.identity.pluginName);
369
- if (!resolved) {
370
- if (!listed.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
371
- return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
372
- }
373
- if (resolved.marketplace.remoteMarketplaceName && !resolved.summary.remotePluginId) return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} detail unavailable: Codex did not return a remote plugin id.` });
374
- if (resolved.summary.availability === "DISABLED_BY_ADMIN" || resolved.summary.installPolicy === "NOT_AVAILABLE") return activationFailure(params.identity, "disabled", { message: `${params.identity.pluginName} was disabled or made unavailable by its marketplace administrator.` });
375
- if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
376
- identity: params.identity,
377
- ok: true,
378
- reason: "already_active",
379
- installAttempted: false,
380
- marketplace: resolved.marketplace,
381
- diagnostics: []
382
- };
383
- const remotePluginId = resolved.marketplace.remoteMarketplaceName ? resolved.summary.remotePluginId : void 0;
384
- let installResponse;
385
- try {
386
- installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, remotePluginId ?? params.identity.pluginName));
387
- } catch (error) {
388
- if (!(error instanceof CodexAppServerRpcError) || error.code !== -32600 || !remotePluginId || error.message !== `remote plugin ${remotePluginId} is disabled by admin` && error.message !== `remote plugin ${remotePluginId} is not available for install`) throw error;
389
- return {
390
- identity: params.identity,
391
- ok: false,
392
- reason: "install_failed",
393
- installAttempted: true,
394
- marketplace: resolved.marketplace,
395
- diagnostics: [{ message: `Codex plugin install failed: ${coerceErrorMessage(error)}` }]
396
- };
397
- }
398
- if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
399
- const refreshDiagnostics = [];
400
- let refreshFailed = false;
401
- try {
402
- const refreshResult = await refreshCodexPluginRuntimeState({
403
- request: params.request,
404
- appCache: params.appCache,
405
- appCacheKey: params.appCacheKey,
406
- configCwd: params.configCwd,
407
- metadataCache: params.metadataCache,
408
- deferAppInventoryRefresh: params.deferAppInventoryRefresh,
409
- targetAppIds: params.targetAppIds
410
- });
411
- refreshDiagnostics.push(...refreshResult.diagnostics);
412
- } catch (error) {
413
- refreshFailed = true;
414
- refreshDiagnostics.push({ message: `Codex plugin runtime refresh failed after install: ${coerceErrorMessage(error)}` });
415
- }
416
- const authRequired = installResponse.appsNeedingAuth.length > 0;
417
- return {
418
- identity: params.identity,
419
- ok: !authRequired && !refreshFailed,
420
- reason: refreshFailed ? "refresh_failed" : authRequired ? "auth_required" : resolved.summary.installed && resolved.summary.enabled ? "already_active" : "installed",
421
- installAttempted: true,
422
- marketplace: resolved.marketplace,
423
- installResponse,
424
- diagnostics: [...refreshDiagnostics, ...installResponse.appsNeedingAuth.map((app) => ({ message: `${app.name} requires app authentication before plugin tools are exposed.` }))]
425
- };
426
- }
427
- /** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
428
- async function refreshCodexPluginRuntimeState(params) {
429
- const diagnostics = [];
430
- await listCuratedCodexPluginMetadata(params, { forceRefetch: true });
431
- await params.request("skills/list", {
432
- cwds: params.configCwd ? [params.configCwd] : [],
433
- forceReload: true
434
- });
435
- try {
436
- await params.request("hooks/list", { cwds: params.configCwd ? [params.configCwd] : [] });
437
- } catch (error) {
438
- diagnostics.push({ message: `Codex hooks refresh skipped: ${coerceErrorMessage(error)}` });
439
- }
440
- await params.request("config/mcpServer/reload", void 0);
441
- if (params.appCache && params.appCacheKey) {
442
- params.appCache.invalidate(params.appCacheKey, "Codex plugin activation changed app inventory", void 0, params.targetAppIds);
443
- if (params.deferAppInventoryRefresh) return { diagnostics };
444
- const request = async (method, requestParams) => await params.request(method, requestParams);
445
- try {
446
- await params.appCache.refreshNow({
447
- key: params.appCacheKey,
448
- request,
449
- forceRefetch: true,
450
- targetAppIds: params.targetAppIds
451
- });
452
- } catch (error) {
453
- diagnostics.push({ message: `Codex app inventory refresh skipped: ${coerceErrorMessage(error)}` });
49
+ //#region extensions/codex/src/app-server/plugin-app-approval-overrides.ts
50
+ /** Projects ask approvals into the native session layer without changing saved settings. */
51
+ function buildCodexAppApprovalOverrides(config, app) {
52
+ const appsRoot = config.apps;
53
+ const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
54
+ if (!isJsonObject(appConfig)) return {};
55
+ const overrides = {};
56
+ const keys = app.approvalOverrideToolConfigKeys;
57
+ for (const [section, fields] of [["tools", { approval_mode: "auto" }], ["links", {
58
+ approvals_reviewer: "user",
59
+ default_tools_approval_mode: "auto"
60
+ }]]) {
61
+ const entries = appConfig[section];
62
+ if (!isJsonObject(entries)) continue;
63
+ const projected = [];
64
+ for (const [name, value] of Object.entries(entries).toSorted(([left], [right]) => left.localeCompare(right))) {
65
+ if (!isJsonObject(value) || section === "tools" && keys && !keys.includes(name)) continue;
66
+ if (Object.keys(fields).some((field) => value[field] !== void 0 && value[field] !== null)) projected.push([name, fields]);
454
67
  }
68
+ if (projected.length > 0) overrides[section] = Object.fromEntries(projected);
455
69
  }
456
- return { diagnostics };
457
- }
458
- async function listCuratedCodexPluginMetadata(params, options = {}) {
459
- const requestParams = {
460
- ...params.configCwd ? { cwds: [params.configCwd] } : {},
461
- ...options.forceRefetch ? { forceRefetch: true } : {}
462
- };
463
- if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
464
- return (await params.metadataCache.load({
465
- appCacheKey: params.appCacheKey,
466
- queryKind: "curated-global",
467
- requestParams,
468
- request: async (method, listedParams) => await params.request(method, listedParams),
469
- cacheable: (response) => response.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
470
- })).response;
471
- }
472
- function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
473
- return {
474
- identity,
475
- ok: false,
476
- reason,
477
- installAttempted: false,
478
- diagnostics: [diagnostic, ...extraDiagnostics]
479
- };
70
+ return overrides;
480
71
  }
481
72
  //#endregion
482
73
  //#region extensions/codex/src/app-server/plugin-thread-app-admission.ts
@@ -592,12 +183,15 @@ async function readCodexConfigForAppAdmission(params) {
592
183
  return [];
593
184
  }
594
185
  if (!isJsonObject(layer.config)) throw new Error("Codex config/read returned an invalid layer config");
186
+ if (!isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned an invalid config layer source");
187
+ if (layer.config.apps !== void 0 && !CODEX_SESSION_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex app policy cannot override ${layer.name.type}; move app settings to a supported user or project config layer before exposing native apps`);
595
188
  return [layer.config];
596
189
  })
597
190
  };
598
191
  } catch (error) {
599
- embeddedAgentLog.warn("codex plugin app admission config read failed", { error: serializeCodexAppInventoryError(error) });
600
- return;
192
+ const details = serializeCodexAppInventoryError(error);
193
+ embeddedAgentLog.warn("codex plugin app admission config read failed", { error: details });
194
+ throw new Error(`Could not verify the Codex app allowlist: ${String(details.message)}. No native thread was started; resolve the native configuration error and retry.`, { cause: error });
601
195
  }
602
196
  }
603
197
  function resolveCodexExplicitAppEnablement(layersHighestPrecedenceFirst, appId) {
@@ -617,7 +211,7 @@ function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
617
211
  * Builds Codex thread config patches that expose only policy-approved apps
618
212
  * for native Codex turns.
619
213
  */
620
- const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 4;
214
+ const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 6;
621
215
  const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
622
216
  /** Returns true when plugin config exists and thread config may need app patches. */
623
217
  function shouldBuildCodexPluginThreadConfig(pluginConfig) {
@@ -763,20 +357,17 @@ async function buildCodexPluginThreadConfig(params) {
763
357
  });
764
358
  }
765
359
  const accountAppsResult = policy.allowAllPlugins ? await readCodexThreadAdmissibleAccountApps(params, appCache) : { apps: [] };
360
+ let appAdmissionConfig;
361
+ const getAdmissionConfig = () => appAdmissionConfig ??= readCodexConfigForAppAdmission(params);
766
362
  const diagnostics = [
767
363
  ...inventory.diagnostics,
768
364
  ...activationDiagnostics,
769
365
  ...accountAppsResult.diagnostic ? [accountAppsResult.diagnostic] : []
770
366
  ];
771
367
  const provisionalAppIds = /* @__PURE__ */ new Set();
772
- const apps = { _default: {
773
- enabled: false,
774
- destructive_enabled: false,
775
- open_world_enabled: false
776
- } };
368
+ const { apps } = buildDisabledAppsConfigPatch();
777
369
  const policyApps = {};
778
370
  const pluginAppIds = {};
779
- let configForAppAdmission;
780
371
  const pluginOwnedAppIds = collectCodexReservedPluginAppIds({
781
372
  policy,
782
373
  inventory,
@@ -803,7 +394,7 @@ async function buildCodexPluginThreadConfig(params) {
803
394
  record,
804
395
  inventory
805
396
  })) {
806
- const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await (configForAppAdmission ??= readCodexConfigForAppAdmission(params));
397
+ const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await getAdmissionConfig();
807
398
  if (!admissionConfig || resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) {
808
399
  diagnostics.push({
809
400
  code: "app_not_ready",
@@ -812,16 +403,8 @@ async function buildCodexPluginThreadConfig(params) {
812
403
  });
813
404
  continue;
814
405
  }
815
- if (record.policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
816
- request: params.request,
817
- configCwd: params.configCwd,
818
- config: admissionConfig.config,
819
- plugin: record.policy,
820
- app,
821
- diagnostics
822
- })) continue;
823
406
  provisionalAppIds.add(app.id);
824
- apps[app.id] = buildEnabledAppConfig(record.policy);
407
+ apps[app.id] = buildEnabledAppConfig(record.policy, record.policy.destructiveApprovalMode === "ask" ? buildCodexAppApprovalOverrides(admissionConfig.config, app) : void 0);
825
408
  policyApps[app.id] = {
826
409
  configKey: record.policy.configKey,
827
410
  marketplaceName: record.policy.marketplaceName,
@@ -835,26 +418,11 @@ async function buildCodexPluginThreadConfig(params) {
835
418
  }
836
419
  for (const app of unresolvedDisabledPluginOwnership ? [] : accountAppsResult.apps) {
837
420
  if (pluginOwnedAppIds.has(app.id)) continue;
838
- configForAppAdmission ??= readCodexConfigForAppAdmission(params);
839
- const admissionConfig = await configForAppAdmission;
840
- if (!admissionConfig) {
841
- diagnostics.push({
842
- code: "account_app_config_unavailable",
843
- message: "Codex account app configuration was unavailable; account apps were not exposed."
844
- });
845
- break;
846
- }
421
+ const admissionConfig = await getAdmissionConfig();
847
422
  if (resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) continue;
848
423
  const accountApp = toCodexPluginOwnedAccountApp(app);
849
- if (policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
850
- request: params.request,
851
- configCwd: params.configCwd,
852
- config: admissionConfig.config,
853
- app: accountApp,
854
- diagnostics
855
- })) continue;
856
424
  provisionalAppIds.add(app.id);
857
- apps[app.id] = buildEnabledAppConfig(policy);
425
+ apps[app.id] = buildEnabledAppConfig(policy, policy.destructiveApprovalMode === "ask" ? buildCodexAppApprovalOverrides(admissionConfig.config, accountApp) : void 0);
858
426
  policyApps[app.id] = {
859
427
  source: "account",
860
428
  appName: app.name,
@@ -864,7 +432,7 @@ async function buildCodexPluginThreadConfig(params) {
864
432
  mcpServerNames: []
865
433
  };
866
434
  }
867
- const configPatch = { apps };
435
+ const configPatch = Object.keys(policyApps).length === 0 ? buildDisabledAppsConfigPatch() : disableUnlistedCodexApps({ apps }, (await getAdmissionConfig()).config);
868
436
  const policyContext = buildPluginAppPolicyContext(policyApps, pluginAppIds);
869
437
  return {
870
438
  enabled: true,
@@ -914,36 +482,76 @@ function emptyPluginThreadConfig(params) {
914
482
  };
915
483
  }
916
484
  function buildDisabledAppsConfigPatch() {
917
- return { apps: { _default: {
918
- enabled: false,
919
- destructive_enabled: false,
920
- open_world_enabled: false
921
- } } };
485
+ return {
486
+ "features.apps": false,
487
+ apps: { _default: {
488
+ enabled: false,
489
+ destructive_enabled: false,
490
+ open_world_enabled: false
491
+ } }
492
+ };
493
+ }
494
+ function disableUnlistedCodexApps(configPatch, nativeConfig) {
495
+ const apps = { ...configPatch.apps };
496
+ for (const id of Object.keys(isJsonObject(nativeConfig.apps) ? nativeConfig.apps : {})) if (id !== "_default" && !Object.hasOwn(apps, id)) apps[id] = { enabled: false };
497
+ return {
498
+ ...configPatch,
499
+ apps
500
+ };
922
501
  }
923
- function buildEnabledAppConfig(policy) {
502
+ function buildEnabledAppConfig(policy, approvalOverrides = {}) {
924
503
  return {
504
+ ...approvalOverrides,
925
505
  enabled: true,
926
506
  destructive_enabled: policy.allowDestructiveActions,
927
- open_world_enabled: true,
507
+ open_world_enabled: policy.allowOpenWorld !== false,
928
508
  default_tools_approval_mode: "auto",
929
509
  ...policy.destructiveApprovalMode === "ask" ? { approvals_reviewer: "user" } : {}
930
510
  };
931
511
  }
932
512
  /** Rebuilds the safe per-thread apps patch persisted with a Codex thread binding. */
933
513
  function buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext) {
934
- const apps = { _default: {
935
- enabled: false,
936
- destructive_enabled: false,
937
- open_world_enabled: false
938
- } };
939
- for (const [appId, policy] of Object.entries(policyContext.apps).toSorted(([left], [right]) => left.localeCompare(right))) apps[appId] = {
940
- enabled: true,
941
- destructive_enabled: policy.allowDestructiveActions,
942
- open_world_enabled: policy.allowOpenWorld !== false,
943
- default_tools_approval_mode: "auto",
944
- ...policy.destructiveApprovalMode === "ask" ? { approvals_reviewer: "user" } : {}
514
+ const disabledConfigPatch = buildDisabledAppsConfigPatch();
515
+ const { apps } = disabledConfigPatch;
516
+ for (const [appId, policy] of Object.entries(policyContext.apps).toSorted(([left], [right]) => left.localeCompare(right))) apps[appId] = buildEnabledAppConfig(policy);
517
+ return Object.keys(policyContext.apps).length > 0 ? { apps } : disabledConfigPatch;
518
+ }
519
+ /** Projects current ask overrides before a side thread replays its bound app policy. */
520
+ async function refreshCodexPluginAppApprovalPolicy(params) {
521
+ if (Object.keys(params.policyContext.apps).length === 0) return {
522
+ policyContext: params.policyContext,
523
+ configPatch: buildDisabledAppsConfigPatch(),
524
+ diagnostics: []
525
+ };
526
+ const targetApps = Object.entries(params.policyContext.apps).filter(([, app]) => app.destructiveApprovalMode === "ask").toSorted(([left], [right]) => left.localeCompare(right));
527
+ const targetAppIds = targetApps.map(([id]) => id);
528
+ const diagnostics = [];
529
+ const readParams = {
530
+ ...params,
531
+ appCacheKey: "approval-policy-replay"
532
+ };
533
+ const [inventory, admissionConfig] = await Promise.all([targetAppIds.length > 0 ? refreshCodexPluginAppInventory(readParams, new CodexAppInventoryCache(), { targetAppIds }) : void 0, readCodexConfigForAppAdmission(readParams)]);
534
+ const configPatch = disableUnlistedCodexApps(buildCodexPluginAppsConfigPatchFromPolicyContext(params.policyContext), admissionConfig.config);
535
+ const currentApps = new Map(inventory?.apps.map((app) => [app.id, toCodexPluginOwnedAccountApp(app)]));
536
+ const apps = { ...params.policyContext.apps };
537
+ for (const [id, policy] of targetApps) {
538
+ const app = currentApps.get(id);
539
+ if (!app) diagnostics.push({
540
+ code: "app_not_ready",
541
+ message: `Could not verify current Codex app approval policy for ${id}; the app was not exposed.`
542
+ });
543
+ else {
544
+ configPatch.apps[id] = buildEnabledAppConfig(policy, buildCodexAppApprovalOverrides(admissionConfig.config, app));
545
+ continue;
546
+ }
547
+ delete apps[id];
548
+ configPatch.apps[id] = { enabled: false };
549
+ }
550
+ return {
551
+ policyContext: buildPluginAppPolicyContext(apps, Object.fromEntries(Object.entries(params.policyContext.pluginAppIds).map(([key, ids]) => [key, ids.filter((id) => Object.hasOwn(apps, id))]))),
552
+ configPatch,
553
+ diagnostics
945
554
  };
946
- return { apps };
947
555
  }
948
556
  function buildPluginAppPolicyContext(apps, pluginAppIds) {
949
557
  return {
@@ -956,49 +564,6 @@ function buildPluginAppPolicyContext(apps, pluginAppIds) {
956
564
  pluginAppIds
957
565
  };
958
566
  }
959
- async function clearPersistedAppToolApprovalOverrides(params) {
960
- try {
961
- const overrideNames = readPersistedAppToolApprovalOverrideNames(params.config, params.app);
962
- if (overrideNames.length === 0) return true;
963
- const edits = overrideNames.map((toolName) => ({
964
- keyPath: `apps.${quoteConfigKeyPathSegment(params.app.id)}.tools.${quoteConfigKeyPathSegment(toolName)}.approval_mode`,
965
- value: null,
966
- mergeStrategy: "replace"
967
- }));
968
- const response = await params.request("config/batchWrite", { edits });
969
- if (!isJsonObject(response) || response.status !== "ok" && response.status !== "okOverridden") throw new Error("Codex did not confirm the approval override batch");
970
- if (response.status === "okOverridden") throw new Error(`approval override for ${overrideNames.join(", ")} is controlled by another config layer`);
971
- const confirmed = await params.request("config/read", {
972
- includeLayers: false,
973
- ...params.configCwd ? { cwd: params.configCwd } : {}
974
- });
975
- if (!isJsonObject(confirmed) || !isJsonObject(confirmed.config)) throw new Error("Codex did not confirm effective app approval configuration");
976
- const remainingOverrideNames = readPersistedAppToolApprovalOverrideNames(confirmed.config, params.app);
977
- if (remainingOverrideNames.length > 0) throw new Error(`effective approval overrides remain for ${remainingOverrideNames.join(", ")}`);
978
- return true;
979
- } catch (error) {
980
- params.diagnostics.push({
981
- code: "approval_overrides_clear_failed",
982
- ...params.plugin ? { plugin: params.plugin } : {},
983
- message: `Could not clear durable Codex app approval overrides for ${params.app.id}: ${error instanceof Error ? error.message : String(error)}`
984
- });
985
- return false;
986
- }
987
- }
988
- function readPersistedAppToolApprovalOverrideNames(config, app) {
989
- const appsRoot = config.apps;
990
- const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
991
- const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
992
- if (!isJsonObject(tools)) return [];
993
- const keys = app.approvalOverrideToolConfigKeys;
994
- return Object.entries(tools).flatMap(([name, value]) => (!keys || keys.includes(name)) && hasPersistedToolApprovalOverride(value) ? [name] : []).toSorted();
995
- }
996
- function hasPersistedToolApprovalOverride(value) {
997
- return isJsonObject(value) && value.approval_mode !== void 0 && value.approval_mode !== null;
998
- }
999
- function quoteConfigKeyPathSegment(segment) {
1000
- return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
1001
- }
1002
567
  function shouldWaitForInitialAppInventory(params, policy, inventory) {
1003
568
  if (inventory.records.some((record) => record.activationRequired)) return false;
1004
569
  return shouldRefreshMissingAppInventory(params, policy, inventory);
@@ -1030,23 +595,32 @@ function policyFingerprint(policy) {
1030
595
  };
1031
596
  }
1032
597
  function mergeJsonObjects(left, right) {
1033
- const merged = { ...left };
598
+ const merged = {
599
+ ...left,
600
+ ...right
601
+ };
1034
602
  for (const [key, value] of Object.entries(right)) {
1035
- const existing = merged[key];
1036
- merged[key] = isJsonObject(existing) && isJsonObject(value) ? mergeJsonObjects(existing, value) : value;
603
+ const existing = left[key];
604
+ if (Object.hasOwn(left, key) && isJsonObject(existing) && isJsonObject(value)) merged[key] = mergeJsonObjects(existing, value);
1037
605
  }
1038
606
  return merged;
1039
607
  }
1040
608
  function fingerprintJson(value) {
1041
- return crypto.createHash("sha256").update(stableStringify$1(value)).digest("hex");
609
+ return crypto.createHash("sha256").update(stringifyCodexPluginPolicy(value)).digest("hex");
1042
610
  }
1043
- function stableStringify$1(value) {
1044
- if (Array.isArray(value)) return `[${value.map((item) => stableStringify$1(item)).join(",")}]`;
1045
- if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify$1(item)}`).join(",")}}`;
611
+ function stringifyCodexPluginPolicy(value) {
612
+ if (Array.isArray(value)) return `[${value.map((item) => stringifyCodexPluginPolicy(item)).join(",")}]`;
613
+ if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stringifyCodexPluginPolicy(item)}`).join(",")}}`;
1046
614
  return JSON.stringify(value);
1047
615
  }
1048
616
  //#endregion
1049
617
  //#region extensions/codex/src/app-server/context-engine-projection.ts
618
+ /**
619
+ * Projects OpenClaw context-engine assemblies into Codex prompt text while
620
+ * preserving safety boundaries and redacting tool payloads.
621
+ */
622
+ /** Attachment preparation must not degrade to a prompt that silently loses the saved input. */
623
+ var CodexContextAttachmentError = class extends Error {};
1050
624
  const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
1051
625
  const CONTEXT_OPEN = "<conversation_context>";
1052
626
  const CONTEXT_CLOSE = "</conversation_context>";
@@ -1065,16 +639,22 @@ const MIN_PROMPT_BUDGET_TOKENS = 8e3;
1065
639
  function neutralizeCodexExplicitMentionSigils(text) {
1066
640
  return text.replace(/\$(?=[A-Za-z0-9_:-])/gu, "$").replace(/\[@(?=[A-Za-z0-9_:-]+\]\()/gu, "[@");
1067
641
  }
642
+ /** Hidden durable notes are context; transient runtime carriers are current-turn only. */
643
+ function isCodexDurableCustomMessage(message) {
644
+ return message.role === "custom" && message.excludeFromContext !== true && !isOpenClawRuntimeContextCustomMessage(message);
645
+ }
1068
646
  /** Projects assembled OpenClaw context-engine messages into Codex prompt inputs. */
1069
- function projectContextEngineAssemblyForCodex(params) {
647
+ async function projectContextEngineAssemblyForCodex(params) {
1070
648
  const prompt = params.prompt.trim();
1071
- const contextMessages = dropDuplicateTrailingPrompt(params.assembledMessages, prompt);
1072
649
  const maxRenderedContextChars = normalizeRenderedContextMaxChars(params.maxRenderedContextChars);
1073
- const renderedContext = neutralizeCodexExplicitMentionSigils(renderMessagesForCodexContext(contextMessages, {
650
+ const context = await renderMessagesForCodexContext(params.assembledMessages.filter((message) => message.role !== "custom" || isCodexDurableCustomMessage(message)), {
1074
651
  maxTextPartChars: resolveTextPartMaxChars(maxRenderedContextChars),
1075
- toolPayloadMode: params.toolPayloadMode ?? "elide"
1076
- }));
1077
- const boundedContext = renderedContext ? truncateOlderContext(renderedContext, maxRenderedContextChars) : void 0;
652
+ toolPayloadMode: params.toolPayloadMode ?? "elide",
653
+ maxRenderedContextChars,
654
+ prepareFileContext: params.prepareFileContext,
655
+ currentUserTurnIdempotencyKey: params.currentUserTurnIdempotencyKey
656
+ });
657
+ const boundedContext = context.text;
1078
658
  const promptPrefix = boundedContext ? [
1079
659
  CONTEXT_HEADER,
1080
660
  CONTEXT_SAFETY_NOTE,
@@ -1092,7 +672,8 @@ function projectContextEngineAssemblyForCodex(params) {
1092
672
  promptText,
1093
673
  ...promptContextRange ? { promptContextRange } : {},
1094
674
  assembledMessages: params.assembledMessages,
1095
- prePromptMessageCount: params.originalHistoryMessages.length
675
+ prePromptMessageCount: params.originalHistoryMessages.length,
676
+ ...context.images.length ? { images: context.images } : {}
1096
677
  };
1097
678
  }
1098
679
  /** Resolves rendered context size from a token budget and reserve. */
@@ -1180,17 +761,43 @@ function resolveProjectionPromptBudgetTokens(params) {
1180
761
  const effectiveReserveTokens = Math.min(requestedReserveTokens, Math.max(0, params.contextTokenBudget - minPromptBudget));
1181
762
  return Math.max(1, params.contextTokenBudget - effectiveReserveTokens);
1182
763
  }
1183
- function dropDuplicateTrailingPrompt(messages, prompt) {
1184
- if (!prompt) return messages;
1185
- const trailing = messages.at(-1);
1186
- if (!trailing || trailing.role !== "user") return messages;
1187
- return extractMessageText(trailing).trim() === prompt ? messages.slice(0, -1) : messages;
1188
- }
1189
- function renderMessagesForCodexContext(messages, options) {
1190
- return messages.map((message) => {
1191
- const text = renderMessageBody(message, options);
1192
- return text ? `[${message.role}]\n${text}` : void 0;
1193
- }).filter((value) => Boolean(value)).join("\n\n");
764
+ async function renderMessagesForCodexContext(messages, options) {
765
+ const tail = [];
766
+ const images = [];
767
+ let retainedImageChars = 0;
768
+ let totalChars = 0;
769
+ let retainedChars = 0;
770
+ for (let index = messages.length - 1; index >= 0; index--) {
771
+ const message = messages[index];
772
+ if (message.role === "user" && options.currentUserTurnIdempotencyKey && Reflect.get(message, "idempotencyKey") === options.currentUserTurnIdempotencyKey) continue;
773
+ const remaining = options.maxRenderedContextChars - retainedChars;
774
+ const files = remaining > 0 && message.role === "user" ? await options.prepareFileContext?.(message, Math.min(remaining, options.maxTextPartChars)) : void 0;
775
+ const imageChars = (files?.images.length ?? 0) * IMAGE_BLOCK_TOKENS * APPROX_RENDERED_CHARS_PER_TOKEN;
776
+ const imagesFit = imageChars < remaining;
777
+ const acceptedImageChars = imagesFit ? imageChars : 0;
778
+ const text = [
779
+ renderMessageBody(message, {
780
+ ...options,
781
+ mediaPrepared: files !== void 0
782
+ }),
783
+ files?.text ? truncateText(files.text, options.maxTextPartChars) : void 0,
784
+ imageChars > 0 && !imagesFit ? "[Attachment images omitted: context budget exceeded]" : void 0
785
+ ].filter(Boolean).join("\n\n");
786
+ if (!text && acceptedImageChars === 0) continue;
787
+ const chunk = `[${message.role}]\n${text}${totalChars > 0 ? "\n\n" : ""}`;
788
+ totalChars += chunk.length;
789
+ if (remaining > 0) {
790
+ const retained = neutralizeCodexExplicitMentionSigils(chunk).slice(-(remaining - acceptedImageChars));
791
+ tail.push(retained);
792
+ retainedChars += retained.length + acceptedImageChars;
793
+ retainedImageChars += acceptedImageChars;
794
+ if (imagesFit && files?.images.length) images.unshift(...files.images);
795
+ }
796
+ }
797
+ return {
798
+ text: truncateOlderContext(tail.toReversed().join(""), options.maxRenderedContextChars - retainedImageChars, totalChars),
799
+ images
800
+ };
1194
801
  }
1195
802
  function renderMessageBody(message, options) {
1196
803
  if (message.role === "compactionSummary" || message.role === "branchSummary") return truncateText(message.summary.trim(), options.maxTextPartChars);
@@ -1204,7 +811,7 @@ function renderMessagePart(part, options) {
1204
811
  const record = part;
1205
812
  const type = typeof record.type === "string" ? record.type : void 0;
1206
813
  if (type === "text") return typeof record.text === "string" ? truncateText(record.text.trim(), options.maxTextPartChars) : "";
1207
- if (type === "image") return "[image omitted]";
814
+ if (type === "image") return options.mediaPrepared ? "" : "[image omitted]";
1208
815
  if (type === "toolCall" || type === "tool_use") {
1209
816
  const label = `tool call${typeof record.name === "string" ? `: ${record.name}` : ""}`;
1210
817
  if (options.toolPayloadMode === "preserve") return truncateText(`${label}\n${stableJson(renderToolCallPayload(record))}`, options.maxTextPartChars);
@@ -1287,16 +894,6 @@ function stableJson(value) {
1287
894
  return "[unserializable payload omitted]";
1288
895
  }
1289
896
  }
1290
- function extractMessageText(message) {
1291
- if (!hasMessageContent(message)) return "";
1292
- if (typeof message.content === "string") return message.content;
1293
- if (!Array.isArray(message.content)) return "";
1294
- return message.content.flatMap((part) => {
1295
- if (!part || typeof part !== "object" || !("type" in part)) return [];
1296
- const record = part;
1297
- return record.type === "text" ? [typeof record.text === "string" ? record.text : ""] : [];
1298
- }).join("\n");
1299
- }
1300
897
  function hasMessageContent(message) {
1301
898
  return "content" in message;
1302
899
  }
@@ -1312,13 +909,13 @@ function truncateText(text, maxChars) {
1312
909
  const truncated = truncateUtf16Safe(text, maxChars);
1313
910
  return `${truncated}\n[truncated ${text.length - truncated.length} chars]`;
1314
911
  }
1315
- function truncateOlderContext(text, maxChars) {
1316
- if (text.length <= maxChars) return text;
912
+ function truncateOlderContext(text, maxChars, totalChars = text.length) {
913
+ if (totalChars <= maxChars) return text;
1317
914
  if (maxChars <= 0) return "";
1318
915
  const buildMarker = (omittedChars) => `[truncated ${omittedChars} chars from older context]\n`;
1319
- let marker = buildMarker(text.length - maxChars);
916
+ let marker = buildMarker(totalChars - maxChars);
1320
917
  let tailChars = Math.max(0, maxChars - marker.length);
1321
- marker = buildMarker(text.length - tailChars);
918
+ marker = buildMarker(totalChars - tailChars);
1322
919
  if (marker.length >= maxChars) return marker.slice(0, maxChars);
1323
920
  tailChars = maxChars - marker.length;
1324
921
  return `${marker}${sliceUtf16Safe(text, -tailChars).trimStart()}`;
@@ -1533,7 +1130,7 @@ function shouldRecheckRecoverablePluginBinding(params) {
1533
1130
  //#endregion
1534
1131
  //#region extensions/codex/src/app-server/thread-fingerprints.ts
1535
1132
  function codexDynamicToolsFingerprint(dynamicTools) {
1536
- return fingerprintDynamicTools(dynamicTools);
1133
+ return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
1537
1134
  }
1538
1135
  function codexLegacyDynamicToolsFingerprint(dynamicTools) {
1539
1136
  return legacyFingerprintDynamicTools(dynamicTools);
@@ -1541,11 +1138,8 @@ function codexLegacyDynamicToolsFingerprint(dynamicTools) {
1541
1138
  function areCodexDynamicToolFingerprintsCompatible(params) {
1542
1139
  return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
1543
1140
  }
1544
- function fingerprintDynamicTools(dynamicTools) {
1545
- return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
1546
- }
1547
1141
  function legacyFingerprintDynamicTools(dynamicTools) {
1548
- return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
1142
+ return JSON.stringify(dynamicTools.map(stabilizeJsonValue).toSorted(compareJsonFingerprint));
1549
1143
  }
1550
1144
  function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
1551
1145
  return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
@@ -1556,15 +1150,10 @@ function fingerprintUserMcpServersConfigPatch(configPatch) {
1556
1150
  function redactUserMcpServersFingerprintSecrets(value) {
1557
1151
  if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
1558
1152
  if (!value || typeof value !== "object") return value;
1559
- const next = {};
1560
- for (const [key, entry] of Object.entries(value)) {
1561
- if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
1562
- next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
1563
- continue;
1564
- }
1565
- next[key] = redactUserMcpServersFingerprintSecrets(entry);
1566
- }
1567
- return next;
1153
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
1154
+ if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) return [key, Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]))];
1155
+ return [key, redactUserMcpServersFingerprintSecrets(entry)];
1156
+ }));
1568
1157
  }
1569
1158
  function fingerprintUserMcpServersAuthorizationHeader(value) {
1570
1159
  return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
@@ -1589,15 +1178,10 @@ function fingerprintCodexThreadConfig(request, authProfileId, dynamicToolsFinger
1589
1178
  function fingerprintEnvironmentSelection(environments) {
1590
1179
  return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
1591
1180
  }
1592
- function fingerprintDynamicToolSpec(tool) {
1593
- return stabilizeJsonValue(tool);
1594
- }
1595
1181
  function stabilizeJsonValue(value) {
1596
1182
  if (Array.isArray(value)) return value.map(stabilizeJsonValue);
1597
1183
  if (!isJsonObject(value)) return value;
1598
- const stable = {};
1599
- for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
1600
- return stable;
1184
+ return Object.fromEntries(Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, stabilizeJsonValue(child)]));
1601
1185
  }
1602
1186
  function readActiveCodexTurnIds(thread) {
1603
1187
  return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
@@ -1827,10 +1411,12 @@ function applyCodexNativeSkillIsolation(config, isolation) {
1827
1411
  function hasCodexNativeToolCatalog(binding) {
1828
1412
  return binding?.connectionScope === "supervision" && !binding.pendingSupervisionBranch;
1829
1413
  }
1830
- /** Pinned serde omits false deferLoading; every other declaration byte remains native-owned. */
1414
+ /** Pinned serde omits empty catalogs and false deferLoading; declarations remain native-owned. */
1831
1415
  function parseCodexNativeToolCatalog(metadata, threadId, fingerprint) {
1832
1416
  const fail = () => /* @__PURE__ */ new Error("The canonical Codex native tool catalog is missing, corrupt, or changed; the thread is preserved. Reconnect and inspect its native metadata before retrying.");
1833
- if (!isJsonObject(metadata) || metadata.id !== threadId || !Array.isArray(metadata.dynamic_tools) || Buffer.byteLength(JSON.stringify(metadata.dynamic_tools)) > 1048576) throw fail();
1417
+ if (!isJsonObject(metadata) || metadata.id !== threadId) throw fail();
1418
+ const catalog = metadata.dynamic_tools ?? [];
1419
+ if (!Array.isArray(catalog) || Buffer.byteLength(JSON.stringify(catalog)) > 1048576) throw fail();
1834
1420
  const names = /* @__PURE__ */ new Set();
1835
1421
  const namespaces = /* @__PURE__ */ new Set();
1836
1422
  const validName = (name) => typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/u.test(name);
@@ -1851,7 +1437,7 @@ function parseCodexNativeToolCatalog(metadata, threadId, fingerprint) {
1851
1437
  ...value.deferLoading === true ? { deferLoading: true } : {}
1852
1438
  };
1853
1439
  };
1854
- const tools = metadata.dynamic_tools.map((value) => {
1440
+ const tools = catalog.map((value) => {
1855
1441
  if (!isJsonObject(value) || value.type !== "namespace") return readFunction(value);
1856
1442
  if (!validName(value.name) || namespaces.has(value.name) || typeof value.description !== "string" || !Array.isArray(value.tools) || !value.tools.length || Object.keys(value).some((key) => ![
1857
1443
  "type",
@@ -1882,68 +1468,6 @@ async function loadCodexNativeToolCatalog(params) {
1882
1468
  return parseCodexNativeToolCatalog(metadata, binding.threadId, binding.dynamicToolsFingerprint);
1883
1469
  }
1884
1470
  //#endregion
1885
- //#region extensions/codex/src/app-server/managed-thread-store.ts
1886
- const CODEX_MANAGED_THREAD_NAMESPACE = "app-server-managed-threads";
1887
- const CODEX_MANAGED_THREAD_MAX_ENTRIES = 2e4;
1888
- const managedThreadSchema = z.object({
1889
- version: z.literal(1),
1890
- kind: z.literal("managed-thread"),
1891
- sourceHomeId: z.string().min(1),
1892
- threadId: z.string().min(1),
1893
- rolloutPath: z.string().min(1).optional()
1894
- });
1895
- async function markStartedCodexManagedThread(store, params) {
1896
- if (!store) return;
1897
- try {
1898
- await store.mark({
1899
- sourceHomeId: params.sourceHomeId,
1900
- threadId: params.threadId,
1901
- ...params.rolloutPath ? { rolloutPath: params.rolloutPath } : {}
1902
- });
1903
- } catch (error) {
1904
- embeddedAgentLog.warn("failed to record Codex managed thread ownership", { error });
1905
- }
1906
- }
1907
- function managedThreadStoreKey(sourceHomeId, threadId) {
1908
- return `sha256:${createHash("sha256").update("openclaw:codex-managed-thread:v1\0").update(sourceHomeId).update("\0").update(threadId).digest("hex")}`;
1909
- }
1910
- /** Durable ownership index for Codex threads created by OpenClaw. */
1911
- function createCodexManagedThreadStore(state) {
1912
- return {
1913
- async has(sourceHomeId, threadId) {
1914
- const parsed = managedThreadSchema.safeParse(state.lookup(managedThreadStoreKey(sourceHomeId, threadId)));
1915
- return parsed.success && parsed.data.sourceHomeId === sourceHomeId && parsed.data.threadId === threadId;
1916
- },
1917
- async mark(params) {
1918
- try {
1919
- const value = managedThreadSchema.parse({
1920
- version: 1,
1921
- kind: "managed-thread",
1922
- sourceHomeId: params.sourceHomeId.trim(),
1923
- threadId: params.threadId.trim(),
1924
- ...params.rolloutPath?.trim() ? { rolloutPath: params.rolloutPath.trim() } : {}
1925
- });
1926
- state.registerIfAbsent(managedThreadStoreKey(value.sourceHomeId, value.threadId), value);
1927
- return true;
1928
- } catch (error) {
1929
- embeddedAgentLog.warn("failed to record Codex managed thread ownership", { error });
1930
- return false;
1931
- }
1932
- },
1933
- async snapshot() {
1934
- const byHome = /* @__PURE__ */ new Map();
1935
- for (const entry of state.entries()) {
1936
- const parsed = managedThreadSchema.safeParse(entry.value);
1937
- if (!parsed.success) continue;
1938
- const ids = byHome.get(parsed.data.sourceHomeId) ?? /* @__PURE__ */ new Set();
1939
- ids.add(parsed.data.threadId);
1940
- byHome.set(parsed.data.sourceHomeId, ids);
1941
- }
1942
- return byHome;
1943
- }
1944
- };
1945
- }
1946
- //#endregion
1947
1471
  //#region extensions/codex/src/app-server/thread-mcp-attestation.ts
1948
1472
  async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal, expectedActiveServerNames = []) {
1949
1473
  const configuredServers = threadConfig?.mcp_servers;
@@ -1982,14 +1506,13 @@ async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, thread
1982
1506
  //#endregion
1983
1507
  //#region extensions/codex/src/app-server/plugin-thread-attestation.ts
1984
1508
  /**
1985
- * Confirms admitted plugin and account apps against their actual Codex thread before
1986
- * OpenClaw commits a binding or starts a turn.
1509
+ * Checks app availability and enforces restricted MCP surfaces before a turn.
1987
1510
  */
1988
1511
  /** Every admission path checks the same surface; its lifecycle owner keeps the claim fenced. */
1989
1512
  async function attestCodexThreadToolSurface(params) {
1990
1513
  params.assertCurrent();
1991
1514
  if (params.appIds.length > 0) {
1992
- await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps(params));
1515
+ await params.lifecycleTiming.measure("plugin-app-attestation", () => checkCodexThreadAppAvailability(params));
1993
1516
  params.assertCurrent();
1994
1517
  }
1995
1518
  if (params.restrictedToolSurface) {
@@ -2004,7 +1527,7 @@ var CodexPluginThreadAppAttestationError = class extends Error {
2004
1527
  }
2005
1528
  };
2006
1529
  /** Reads the existing runtime snapshot with the started thread's effective app policy. */
2007
- async function attestCodexPluginThreadApps(params) {
1530
+ async function checkCodexThreadAppAvailability(params) {
2008
1531
  const appIds = Array.from(new Set(params.appIds.filter(Boolean))).toSorted();
2009
1532
  if (appIds.length === 0) return;
2010
1533
  let response;
@@ -2014,8 +1537,10 @@ async function attestCodexPluginThreadApps(params) {
2014
1537
  forceRefresh: false
2015
1538
  }, { signal: params.signal });
2016
1539
  } catch (error) {
1540
+ params.signal?.throwIfAborted();
2017
1541
  throw new CodexPluginThreadAppAttestationError(`Codex could not confirm admitted apps for thread ${params.threadId}`, { cause: error });
2018
1542
  }
1543
+ params.signal?.throwIfAborted();
2019
1544
  const installedById = new Map(response.apps.map((app) => [app.id, app]));
2020
1545
  const failures = appIds.flatMap((appId) => {
2021
1546
  const app = installedById.get(appId);
@@ -2023,7 +1548,10 @@ async function attestCodexPluginThreadApps(params) {
2023
1548
  if (!app.enabled) return [`${appId}:disabled`];
2024
1549
  return app.callable ? [] : [`${appId}:not-callable`];
2025
1550
  });
2026
- if (failures.length > 0) throw new CodexPluginThreadAppAttestationError(`Codex thread ${params.threadId} did not expose admitted apps: ${failures.join(", ")}`);
1551
+ if (failures.length > 0) embeddedAgentLog.warn("codex apps unavailable; continuing with remaining tools", {
1552
+ threadId: params.threadId,
1553
+ failures
1554
+ });
2027
1555
  }
2028
1556
  /** Deletes a persistent pre-turn thread; ephemeral threads can only be unsubscribed. */
2029
1557
  async function discardUnattestedCodexPluginThread(params) {
@@ -2207,7 +1735,10 @@ function createCodexNativeHookRelay(params) {
2207
1735
  }),
2208
1736
  signal: params.signal,
2209
1737
  runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
2210
- assertActive: params.hostCapabilities.assertActive,
1738
+ assertActive: () => {
1739
+ params.hostCapabilities.assertActive();
1740
+ params.assertCurrent?.();
1741
+ },
2211
1742
  retention: {
2212
1743
  readClaim: readCodexNativeChildThreadId,
2213
1744
  shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
@@ -2523,7 +2054,6 @@ function buildDeveloperInstructions(params, options = {}) {
2523
2054
  let hasSubagentsList = false;
2524
2055
  let hasSessionsSend = false;
2525
2056
  let hasSeenDirectNamespace = false;
2526
- let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
2527
2057
  for (const spec of options.dynamicTools ?? []) {
2528
2058
  const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
2529
2059
  if (isDirectNamespace) hasSeenDirectNamespace = true;
@@ -2540,7 +2070,6 @@ function buildDeveloperInstructions(params, options = {}) {
2540
2070
  hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
2541
2071
  hasSubagentsList ||= name === "subagents";
2542
2072
  hasSessionsSend ||= name === "sessions_send";
2543
- messageToolAvailable ||= name === "message";
2544
2073
  }
2545
2074
  }
2546
2075
  const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
@@ -2570,15 +2099,11 @@ function buildDeveloperInstructions(params, options = {}) {
2570
2099
  hasSubagentsList,
2571
2100
  hasSessionsSend
2572
2101
  }).join("\n") : void 0,
2573
- buildHarnessVisibleReplyGuidance({
2574
- sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
2575
- messageToolAvailable,
2576
- uiPresentation: params.disableTools !== true && params.promptMode !== "minimal" && params.promptMode !== "none" ? {
2577
- showWidgetToolName,
2578
- dashboardToolName,
2579
- portalToolName
2580
- } : void 0
2581
- }),
2102
+ params.disableTools !== true && params.promptMode !== "minimal" && params.promptMode !== "none" ? buildUiPresentationPrompt({
2103
+ showWidgetToolName,
2104
+ dashboardToolName,
2105
+ portalToolName
2106
+ }) : void 0,
2582
2107
  buildCredentialSafetyPrompt(secretsToolName),
2583
2108
  nativeCommandGuidance,
2584
2109
  params.extraSystemPrompt
@@ -2686,6 +2211,7 @@ function resolveCodexWebSearchPlan(params) {
2686
2211
  const CODEX_CODE_MODE_THREAD_CONFIG = {
2687
2212
  "features.code_mode": true,
2688
2213
  "features.code_mode_only": false,
2214
+ "features.shell_tool": true,
2689
2215
  "features.apply_patch_streaming_events": true,
2690
2216
  suppress_unstable_features_warning: true
2691
2217
  };
@@ -2712,6 +2238,7 @@ const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
2712
2238
  "code_mode",
2713
2239
  "code_mode_only",
2714
2240
  "computer_use",
2241
+ "context_management",
2715
2242
  "current_time_reminder",
2716
2243
  "default_mode_request_user_input",
2717
2244
  "deferred_executor",
@@ -2764,15 +2291,6 @@ const CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES = /* @__PURE__ */ new Map([
2764
2291
  ["telepathy", "chronicle"],
2765
2292
  ["codex_hooks", "hooks"]
2766
2293
  ]);
2767
- const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
2768
- "packagedDefaults",
2769
- "mdm",
2770
- "system",
2771
- "enterpriseManaged",
2772
- "user",
2773
- "project",
2774
- "sessionFlags"
2775
- ]);
2776
2294
  /** Common deterministic start/resume/fork fields; no run resources or unsupported setters. */
2777
2295
  function buildCodexThreadConfiguration(params, options) {
2778
2296
  return {
@@ -2926,17 +2444,16 @@ function buildRestrictedToolConfigPatch(inheritedMcpServerNames, scheduledAppAut
2926
2444
  ...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
2927
2445
  };
2928
2446
  }
2929
- async function readCodexInheritedMcpServerNames(client, cwd, signal) {
2930
- const response = await client.request("config/read", {
2931
- cwd,
2932
- includeLayers: true
2933
- }, { signal });
2934
- if (!isJsonObject(response) || !isJsonObject(response.config)) throw new Error("Codex config/read returned an invalid effective config");
2447
+ async function readCodexInheritedMcpServerNames(client, cwd, signal, effectiveConfig) {
2448
+ const response = effectiveConfig ?? await readCodexEffectiveConfig(client, cwd, signal);
2935
2449
  if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
2936
2450
  for (const layer of response.layers) {
2937
2451
  if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
2938
- if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex restricted tool surface cannot override config layer ${layer.name.type}`);
2939
- if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex restricted tool surface does not recognize config layer ${layer.name.type}`);
2452
+ if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") {
2453
+ const migrationGuidance = layer.name.type === "legacyManagedConfigTomlFromFile" ? "migrate /etc/codex/managed_config.toml to /etc/codex/requirements.toml before running restricted or isolated turns. For ChatGPT-only authentication, use allowed_login_methods = [\"chatgpt\"] in /etc/codex/requirements.toml" : "replace the legacy MDM payload with base64-encoded TOML requirements in the com.openai.codex managed preference requirements_toml_base64 before running restricted or isolated turns. For ChatGPT-only authentication, include allowed_login_methods = [\"chatgpt\"] in that TOML payload";
2454
+ throw new Error(`Codex restricted tool surface cannot override config layer ${layer.name.type}; ${migrationGuidance}.`);
2455
+ }
2456
+ if (!CODEX_SESSION_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex restricted tool surface does not recognize config layer ${layer.name.type}`);
2940
2457
  }
2941
2458
  const configuredServers = response.config.mcp_servers;
2942
2459
  if (configuredServers === void 0) return [];
@@ -2968,6 +2485,7 @@ async function assertCodexManagedRequirementsDoNotOverrideToolPolicy(client, opt
2968
2485
  for (const [feature, enabled] of Object.entries(featureRequirements)) {
2969
2486
  if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
2970
2487
  const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
2488
+ if (options.requiredNativeShell && canonicalFeature === "shell_tool" && !enabled) throw new Error("Codex native code mode requires shell_tool, but managed requirements disable it. Ask your administrator to allow the shell, or select a tool policy that disables native code mode; no automation authority was captured.");
2971
2489
  const deniedByToolPolicy = options.restrictedToolSurface && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature) || additionalDeniedFeatures.has(canonicalFeature);
2972
2490
  if (canonicalFeature === "hooks" && managedHooksAllowed) continue;
2973
2491
  if (enabled && deniedByToolPolicy) throw new Error(`Codex tool policy cannot override required feature ${feature}`);
@@ -3262,11 +2780,6 @@ function appApprovalCeiling(mode) {
3262
2780
  if (mode === "allow") return "approve";
3263
2781
  return mode === "ask" ? "prompt" : "auto";
3264
2782
  }
3265
- function stableStringify(value) {
3266
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
3267
- if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
3268
- return JSON.stringify(value);
3269
- }
3270
2783
  /** Intersects a stored app-ID cap with current policy without admitting new apps. */
3271
2784
  function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authority, currentPolicy = {
3272
2785
  config: {},
@@ -3296,13 +2809,17 @@ function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authorit
3296
2809
  };
3297
2810
  }
3298
2811
  const policyContext = buildPluginAppPolicyContext(apps, Object.fromEntries(Object.entries(config.policyContext.pluginAppIds).map(([key, ids]) => [key, ids.filter((id) => Object.hasOwn(apps, id))]).filter(([, ids]) => ids.length > 0)));
3299
- const configPatch = buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext);
2812
+ const configPatch = disableUnlistedCodexApps(buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext), currentPolicy.config);
3300
2813
  const appsPatch = asOptionalRecord(configPatch.apps);
3301
2814
  for (const [appId, captured] of capturedById) {
3302
2815
  const appPatch = asOptionalRecord(appsPatch?.[appId]);
3303
2816
  if (!appPatch || !Object.hasOwn(apps, appId)) continue;
3304
2817
  const currentApp = apps[appId];
3305
2818
  if (!currentApp) continue;
2819
+ if (currentApp.destructiveApprovalMode === "ask") Object.assign(appPatch, buildCodexAppApprovalOverrides(currentPolicy.config, {
2820
+ id: appId,
2821
+ approvalOverrideToolConfigKeys: []
2822
+ }));
3306
2823
  const storedAppCeiling = appApprovalCeiling(captured.destructiveApprovalMode);
3307
2824
  const currentAppCeiling = appApprovalCeiling(defaultApprovalMode(currentApp));
3308
2825
  const tools = currentPolicy.toolsByApp.get(appId) ?? /* @__PURE__ */ new Map();
@@ -3315,7 +2832,7 @@ function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authorit
3315
2832
  }];
3316
2833
  }));
3317
2834
  }
3318
- const fingerprint = crypto.createHash("sha256").update(stableStringify({
2835
+ const fingerprint = crypto.createHash("sha256").update(stringifyCodexPluginPolicy({
3319
2836
  version: 1,
3320
2837
  namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
3321
2838
  authority: scheduled,
@@ -3355,7 +2872,7 @@ function buildLegacyScheduledCodexAppRecoveryPrompt(params) {
3355
2872
  function buildScheduledCodexAppAuthorityInputFingerprint(baseFingerprint, authority) {
3356
2873
  const scheduled = parseScheduledCodexAppAuthority(authority);
3357
2874
  if (!scheduled) return baseFingerprint;
3358
- return crypto.createHash("sha256").update(stableStringify({
2875
+ return crypto.createHash("sha256").update(stringifyCodexPluginPolicy({
3359
2876
  version: 1,
3360
2877
  namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
3361
2878
  baseFingerprint,
@@ -3367,67 +2884,36 @@ function buildScheduledCodexAppAuthorityInputFingerprint(baseFingerprint, author
3367
2884
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
3368
2885
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
3369
2886
  function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
3370
- const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
3371
- const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
2887
+ const detailed = options.enabled || options.log?.isEnabled?.("trace");
2888
+ const totalThresholdMs = options.totalThresholdMs ?? (detailed ? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS : 1e4);
2889
+ const stageThresholdMs = options.stageThresholdMs ?? (detailed ? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS : 5e3);
3372
2890
  return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
3373
2891
  }
3374
2892
  function formatCodexThreadLifecycleTimingSummary(params) {
3375
- const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
2893
+ const spans = formatStageTimings(params.summary.spans);
3376
2894
  return `[trace:codex-app-server] thread lifecycle: runId=${params.runId} sessionId=${params.sessionId} sessionKey=${params.sessionKey ?? "unknown"} action=${params.action} totalMs=${params.summary.totalMs} stages=${spans}`;
3377
2895
  }
3378
2896
  function createCodexThreadLifecycleTimingTracker(options = {}) {
3379
2897
  const log = options.log ?? embeddedAgentLog;
3380
- if (!options.enabled && log.isEnabled?.("trace") !== true) return {
3381
- async measure(_name, run) {
3382
- return await run();
3383
- },
3384
- measureSync(_name, run) {
3385
- return run();
3386
- },
3387
- mark() {},
3388
- logSummary() {}
3389
- };
3390
- const now = options.now ?? Date.now;
3391
- const startedAt = now();
2898
+ const timing = createStageTimingTracker(options.now ?? Date.now);
3392
2899
  let didLog = false;
3393
- const spans = [];
3394
- const toMs = (value) => Math.max(0, Math.round(value));
3395
- const record = (name, spanStartedAt) => {
3396
- const currentAt = now();
3397
- spans.push({
3398
- name,
3399
- durationMs: toMs(currentAt - spanStartedAt),
3400
- elapsedMs: toMs(currentAt - startedAt)
3401
- });
3402
- };
3403
- const snapshot = () => ({
3404
- totalMs: toMs(now() - startedAt),
3405
- spans: spans.slice()
3406
- });
3407
2900
  return {
3408
- async measure(name, run) {
3409
- const spanStartedAt = now();
3410
- try {
3411
- return await run();
3412
- } finally {
3413
- record(name, spanStartedAt);
3414
- }
3415
- },
3416
- measureSync(name, run) {
3417
- const spanStartedAt = now();
3418
- try {
3419
- return run();
3420
- } finally {
3421
- record(name, spanStartedAt);
3422
- }
3423
- },
2901
+ measure: timing.measure,
2902
+ measureSync: timing.measureSync,
3424
2903
  mark(name) {
3425
- record(name, now());
2904
+ timing.measureSync(name, () => void 0);
3426
2905
  },
3427
2906
  logSummary(params) {
3428
2907
  if (didLog) return;
3429
- const summary = snapshot();
3430
- const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
2908
+ const { totalMs, stages: spans } = timing.snapshot();
2909
+ const summary = {
2910
+ totalMs,
2911
+ spans
2912
+ };
2913
+ const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, {
2914
+ ...options,
2915
+ log
2916
+ });
3431
2917
  if (!shouldWarn && !log.isEnabled?.("trace")) return;
3432
2918
  didLog = true;
3433
2919
  const message = formatCodexThreadLifecycleTimingSummary({
@@ -3462,7 +2948,7 @@ function resolveCodexThreadAgentDir(params) {
3462
2948
  return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
3463
2949
  }
3464
2950
  async function prepareCodexThreadLifecyclePreflight(params) {
3465
- await assertCodexModelBackedReviewerEffectiveConfig({
2951
+ let effectiveConfig = await assertCodexModelBackedReviewerEffectiveConfig({
3466
2952
  client: params.client,
3467
2953
  approvalsReviewer: params.appServer.approvalsReviewer,
3468
2954
  cwd: params.cwd,
@@ -3518,13 +3004,17 @@ async function prepareCodexThreadLifecyclePreflight(params) {
3518
3004
  const allowConfiguredManagedHooks = params.params.pluginHarnessToolPolicyRestricted === true && !ringZeroActive && !messageOnlySourceReply && params.params.scheduledRuntimeAuthority === void 0;
3519
3005
  const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
3520
3006
  if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
3521
- const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
3522
- if (restrictedToolSurface || imageGenerationDenied) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
3007
+ if ((restrictedToolSurface || params.nativeCodeModeEnabled !== false) && !effectiveConfig) effectiveConfig = await lifecycleTiming.measure("tool-policy-config-read", () => readCodexEffectiveConfig(params.client, params.cwd, params.signal));
3008
+ const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-policy", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal, effectiveConfig)) : [];
3009
+ if (restrictedToolSurface || imageGenerationDenied || params.nativeCodeModeEnabled !== false) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
3523
3010
  restrictedToolSurface,
3011
+ requiredNativeShell: params.nativeCodeModeEnabled !== false,
3524
3012
  additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0,
3525
3013
  allowedManagedRequirementsFingerprint: readScheduledCodexAppManagedRequirementsFingerprint(params.params.scheduledRuntimeAuthority),
3526
3014
  allowConfiguredManagedHooks
3527
3015
  }, params.signal));
3016
+ const features = effectiveConfig?.config.features;
3017
+ if (params.nativeCodeModeEnabled !== false && isJsonObject(features) && features.shell_tool === false && !CODEX_SESSION_OVERRIDABLE_LAYER_TYPES.has(effectiveConfig?.origins?.["features.shell_tool"]?.name.type ?? "")) throw new Error("Codex native code mode requires shell_tool, but the effective shell setting cannot be overridden. Ask your administrator to allow the shell, or select a tool policy that disables native code mode; no automation authority was captured.");
3528
3018
  const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
3529
3019
  version: 1,
3530
3020
  baseInstructions: "",
@@ -3553,71 +3043,26 @@ async function prepareCodexThreadLifecyclePreflight(params) {
3553
3043
  };
3554
3044
  }
3555
3045
  //#endregion
3556
- //#region extensions/codex/src/app-server/thread-lifecycle-result.ts
3046
+ //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
3557
3047
  function resolveCodexThreadRolloutPath(thread) {
3558
3048
  const rolloutPath = thread.path?.trim();
3559
3049
  if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
3560
3050
  return rolloutPath;
3561
3051
  }
3562
- /** Materializes the public lifecycle result after a fresh thread is durably committed. */
3563
- function buildStartedCodexThreadBinding(input) {
3564
- const { context, params, response, startParams } = input;
3565
- return {
3566
- threadId: response.thread.id,
3567
- ...input.clientId ? { clientId: input.clientId } : {},
3568
- cwd: params.cwd,
3569
- ...input.rolloutPath ? { rolloutPath: input.rolloutPath } : {},
3570
- authProfileId: params.params.authProfileId,
3571
- agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
3572
- model: response.model ?? startParams.model ?? params.params.modelId,
3573
- modelProvider: response.modelProvider ?? input.startModelProvider ?? input.modelProvider,
3574
- dynamicToolsFingerprint: context.dynamicToolsFingerprint,
3575
- dynamicToolsContainDeferred: context.dynamicToolsContainDeferred,
3576
- nativeSkillIsolationFingerprint: context.nativeSkillIsolationFingerprint,
3577
- userMcpServersFingerprint: context.userMcpServersFingerprint,
3578
- mcpServersFingerprint: input.nextMcpServersFingerprint,
3579
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
3580
- ringZeroConfigFingerprint: context.ringZeroConfigFingerprint,
3581
- ringZeroClientInstanceId: context.ringZeroClientInstanceId,
3582
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
3583
- networkProxyConfigFingerprint: context.networkProxyConfigFingerprint,
3584
- nativeHookRelayGeneration: input.finalConfigPatch.nativeHookRelayGeneration,
3585
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
3586
- pluginAppsFingerprint: input.pluginThreadConfig?.fingerprint,
3587
- pluginAppsInputFingerprint: input.pluginThreadConfig?.inputFingerprint,
3588
- pluginAppPolicyContext: input.pluginThreadConfig?.policyContext,
3589
- contextEngine: context.contextEngineBinding,
3590
- environmentSelectionFingerprint: context.environmentSelectionFingerprint,
3591
- ...startParams.ephemeral ? { liveThreadEphemeralPolicy: startParams.developerInstructions } : {},
3592
- ...!context.preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
3593
- ...startParams,
3594
- model: response.model ?? startParams.model ?? null,
3595
- requestedModel: startParams.model ?? null,
3596
- modelProvider: input.bindingModelProvider ?? null,
3597
- requestedModelProvider: startParams.modelProvider ?? input.bindingModelProvider ?? null
3598
- }, params.params.authProfileId, context.dynamicToolsFingerprint) } : {},
3599
- lifecycle: {
3600
- action: "started",
3601
- ...context.rotatedContextEngineBinding ? { rotatedContextEngineBinding: true } : {}
3602
- }
3603
- };
3604
- }
3605
- //#endregion
3606
- //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
3607
3052
  async function resumeExistingCodexThread(params, context) {
3608
3053
  const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
3054
+ let acceptedConfiguration;
3055
+ let disposeConfiguration;
3609
3056
  let resumeReservation;
3610
- let resumeResponseAccepted = false;
3611
3057
  let ordinaryAppConfigChanged = false;
3612
3058
  let policyOutcome = "not-written";
3613
- const assertHandoffCurrent = () => {
3614
- params.params.hostCapabilities.assertActive();
3615
- throwIfAborted();
3616
- context.assertResumeOwnership();
3617
- context.assertResumeConfiguration();
3618
- };
3619
3059
  const abandonClient = params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client));
3620
3060
  try {
3061
+ const configuration = await context.prepareResume();
3062
+ const assertHandoffCurrent = configuration.assertConfigured;
3063
+ disposeConfiguration = configuration.dispose;
3064
+ await context.releaseRetainedThread(configuration.assertCurrent);
3065
+ configuration.assertCurrent();
3621
3066
  const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
3622
3067
  const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
3623
3068
  action: "resume",
@@ -3626,9 +3071,8 @@ async function resumeExistingCodexThread(params, context) {
3626
3071
  configPatch: params.finalConfigPatch,
3627
3072
  nativeHookRelayGeneration: params.nativeHookRelayGeneration
3628
3073
  };
3629
- const pluginThreadConfig = context.prebuiltPluginThreadConfig ?? (params.pluginThreadConfig?.requiresCurrentPolicyCheck ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0);
3630
- const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
3631
- const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
3074
+ const pluginThreadConfig = context.prebuiltPluginThreadConfig ?? (params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0);
3075
+ const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
3632
3076
  const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
3633
3077
  threadId: resumeBinding.threadId,
3634
3078
  cwd: params.cwd,
@@ -3657,11 +3101,12 @@ async function resumeExistingCodexThread(params, context) {
3657
3101
  abandonClient,
3658
3102
  request: resumeParams,
3659
3103
  signal: params.signal,
3660
- assertCurrent: context.assertResumeOwnership
3104
+ assertCurrent: configuration.assertCurrent
3661
3105
  }));
3662
- resumeResponseAccepted = true;
3106
+ acceptedConfiguration = configuration;
3663
3107
  assertCodexThreadAcceptsDirectInput(response.thread);
3664
- context.assertResumeConfiguration();
3108
+ configuration.assertConfigured();
3109
+ if (requestModelProvider && response.modelProvider !== requestModelProvider) throw new Error("Codex resumed a different model provider than the one selected for this turn");
3665
3110
  const loadedPluginThreadConfig = await context.buildLoadedPluginThreadConfig?.(resumeBinding);
3666
3111
  if (loadedPluginThreadConfig && loadedPluginThreadConfig.fingerprint !== (pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint)) {
3667
3112
  ordinaryAppConfigChanged = resumeBinding.connectionScope !== "supervision" && !resumeBinding.pendingResumeConfiguration;
@@ -3695,7 +3140,7 @@ async function resumeExistingCodexThread(params, context) {
3695
3140
  cwd: params.cwd,
3696
3141
  rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
3697
3142
  authProfileId,
3698
- model: response.model ?? resumeParams.model ?? params.params.modelId,
3143
+ model: resumeParams.model ?? response.model ?? params.params.modelId,
3699
3144
  preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
3700
3145
  modelProvider: normalizeBindingModelProvider(authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
3701
3146
  dynamicToolsFingerprint,
@@ -3760,13 +3205,13 @@ async function resumeExistingCodexThread(params, context) {
3760
3205
  };
3761
3206
  } catch (error) {
3762
3207
  resumeReservation?.release();
3763
- if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
3764
- if (resumeResponseAccepted) {
3208
+ if (!acceptedConfiguration && (!(error instanceof CodexAppServerRpcError) || error.method === "thread/read" && !isCodexThreadReadMissingError(error, resumeBinding.threadId) || isCodexAppServerOverloadError(error))) throw error;
3209
+ if (acceptedConfiguration) {
3765
3210
  const handoffError = error instanceof CodexThreadPolicyHandoffError || error instanceof CodexAppServerUnsafeSubscriptionError ? error : new CodexThreadPolicyHandoffError(policyOutcome, error);
3766
3211
  const subscriptionReleased = await unsubscribeCodexThreadBestEffort(params.client, {
3767
3212
  threadId: resumeBinding.threadId,
3768
3213
  timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,
3769
- assertCurrent: context.assertResumeOwnership
3214
+ assertCurrent: acceptedConfiguration.assertCurrent
3770
3215
  }).catch(() => false);
3771
3216
  if (!subscriptionReleased || handoffError instanceof CodexThreadPolicyHandoffError && handoffError.outcome === "unknown") {
3772
3217
  if (resumeBinding.connectionScope === "supervision") await retireUnsafeCodexTurnClientBestEffort(params.client, "session policy handoff");
@@ -3777,11 +3222,13 @@ async function resumeExistingCodexThread(params, context) {
3777
3222
  }
3778
3223
  }
3779
3224
  if (!ordinaryAppConfigChanged || !subscriptionReleased) throw handoffError;
3780
- assertHandoffCurrent();
3225
+ acceptedConfiguration.assertConfigured();
3781
3226
  }
3782
- if (resumeBinding.pendingResumeConfiguration || resumeBinding.connectionScope === "supervision" || params.signal?.aborted) throw error;
3227
+ if (resumeBinding.pendingResumeConfiguration || resumeBinding.preserveNativeModel || resumeBinding.connectionScope === "supervision" || params.signal?.aborted) throw error;
3783
3228
  embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
3784
3229
  await clearCurrentBinding("rotating a stale thread binding");
3230
+ } finally {
3231
+ disposeConfiguration?.();
3785
3232
  }
3786
3233
  }
3787
3234
  async function startFreshCodexThread(params, context) {
@@ -3812,9 +3259,18 @@ async function startFreshCodexThread(params, context) {
3812
3259
  disableLoginShell: params.disableLoginShell
3813
3260
  }));
3814
3261
  const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
3262
+ const assertCurrent = () => {
3263
+ throwIfAborted();
3264
+ params.params.hostCapabilities.assertActive();
3265
+ params.assertCurrent?.();
3266
+ };
3815
3267
  const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
3816
3268
  try {
3817
- return await params.client.request("thread/start", startParams, { signal: params.signal });
3269
+ assertCurrent();
3270
+ return await params.client.request("thread/start", startParams, {
3271
+ signal: params.signal,
3272
+ assertCurrent
3273
+ });
3818
3274
  } catch (error) {
3819
3275
  if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
3820
3276
  throw error;
@@ -3822,10 +3278,6 @@ async function startFreshCodexThread(params, context) {
3822
3278
  });
3823
3279
  const response = assertCodexThreadStartResponse(threadStartResponse);
3824
3280
  const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
3825
- const assertCurrent = () => {
3826
- throwIfAborted();
3827
- params.params.hostCapabilities.assertActive();
3828
- };
3829
3281
  const rejectUncommittedThread = async (cause) => {
3830
3282
  if (!await discardUnattestedCodexPluginThread({
3831
3283
  client: params.client,
@@ -3862,35 +3314,38 @@ async function startFreshCodexThread(params, context) {
3862
3314
  });
3863
3315
  const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
3864
3316
  const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
3317
+ const startedBinding = {
3318
+ threadId: response.thread.id,
3319
+ ...clientId ? { clientId } : {},
3320
+ cwd: params.cwd,
3321
+ ...rolloutPath ? { rolloutPath } : {},
3322
+ authProfileId: params.params.authProfileId,
3323
+ agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
3324
+ model: response.model ?? startParams.model ?? params.params.modelId,
3325
+ modelProvider: bindingModelProvider,
3326
+ dynamicToolsFingerprint,
3327
+ dynamicToolsContainDeferred,
3328
+ nativeSkillIsolationFingerprint,
3329
+ userMcpServersFingerprint,
3330
+ mcpServersFingerprint: nextMcpServersFingerprint,
3331
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
3332
+ ringZeroConfigFingerprint,
3333
+ ringZeroClientInstanceId,
3334
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
3335
+ networkProxyConfigFingerprint,
3336
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
3337
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
3338
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
3339
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
3340
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
3341
+ contextEngine: contextEngineBinding,
3342
+ environmentSelectionFingerprint
3343
+ };
3865
3344
  if (!preserveExistingBinding) {
3866
3345
  const nextBinding = {
3867
- threadId: response.thread.id,
3868
- ...clientId ? { clientId } : {},
3869
- cwd: params.cwd,
3870
- ...rolloutPath ? { rolloutPath } : {},
3871
- authProfileId: params.params.authProfileId,
3872
- agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
3873
- model: response.model ?? startParams.model ?? params.params.modelId,
3874
- modelProvider: bindingModelProvider,
3875
- dynamicToolsFingerprint,
3876
- dynamicToolsContainDeferred,
3346
+ ...startedBinding,
3877
3347
  webSearchThreadConfigFingerprint,
3878
- nativeSkillIsolationFingerprint,
3879
- userMcpServersFingerprint,
3880
- mcpServersFingerprint: nextMcpServersFingerprint,
3881
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
3882
- ringZeroConfigFingerprint,
3883
- ringZeroClientInstanceId,
3884
- nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
3885
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
3886
- networkProxyConfigFingerprint,
3887
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
3888
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
3889
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
3890
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
3891
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
3892
- contextEngine: contextEngineBinding,
3893
- environmentSelectionFingerprint
3348
+ nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0
3894
3349
  };
3895
3350
  const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
3896
3351
  let committed;
@@ -3931,20 +3386,22 @@ async function startFreshCodexThread(params, context) {
3931
3386
  threadId: response.thread.id,
3932
3387
  action: rotatedContextEngineBinding ? "rotated" : "started"
3933
3388
  });
3934
- return buildStartedCodexThreadBinding({
3935
- bindingModelProvider,
3936
- clientId,
3937
- context,
3938
- finalConfigPatch,
3939
- nextMcpServersFingerprint,
3940
- params,
3941
- pluginThreadConfig,
3942
- response,
3943
- rolloutPath,
3944
- startModelProvider: requestModelProvider ?? startModelProvider,
3945
- startParams,
3946
- modelProvider
3947
- });
3389
+ return {
3390
+ ...startedBinding,
3391
+ modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
3392
+ ...startParams.ephemeral ? { liveThreadEphemeralPolicy: startParams.developerInstructions } : {},
3393
+ ...!preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
3394
+ ...startParams,
3395
+ model: response.model ?? startParams.model ?? null,
3396
+ requestedModel: startParams.model ?? null,
3397
+ modelProvider: bindingModelProvider ?? null,
3398
+ requestedModelProvider: startParams.modelProvider ?? bindingModelProvider ?? null
3399
+ }, params.params.authProfileId, dynamicToolsFingerprint) } : {},
3400
+ lifecycle: {
3401
+ action: "started",
3402
+ ...rotatedContextEngineBinding ? { rotatedContextEngineBinding: true } : {}
3403
+ }
3404
+ };
3948
3405
  }
3949
3406
  //#endregion
3950
3407
  //#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
@@ -4032,6 +3489,7 @@ async function tryReuseCodexLiveThread(options) {
4032
3489
  try {
4033
3490
  retainedThread.assertCurrent();
4034
3491
  params.params.hostCapabilities.assertActive();
3492
+ params.assertCurrent?.();
4035
3493
  } catch (cause) {
4036
3494
  throw new AgentHarnessPreflightError("Codex warm thread ownership changed before this turn could run. No turn was sent; reconnect before continuing, or start a new conversation if the original thread was closed.", { cause });
4037
3495
  }
@@ -4163,11 +3621,14 @@ async function tryReuseCodexLiveThread(options) {
4163
3621
  //#endregion
4164
3622
  //#region extensions/codex/src/app-server/thread-lifecycle-adoption.ts
4165
3623
  /** Passive refusal must precede releasing or acquiring any native subscription. */
4166
- async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context) {
3624
+ async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context, assertCurrent) {
4167
3625
  const { thread } = await context.lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
4168
3626
  threadId,
4169
3627
  includeTurns: false
4170
- }, { signal: params.signal }));
3628
+ }, {
3629
+ signal: params.signal,
3630
+ assertCurrent
3631
+ }));
4171
3632
  context.throwIfAborted();
4172
3633
  assertCodexThreadAcceptsDirectInput(thread);
4173
3634
  if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
@@ -4182,13 +3643,25 @@ async function withCodexThreadLifecycleBinding(params, run) {
4182
3643
  agentId: params.agentId ?? params.params.agentId,
4183
3644
  config: params.params.config
4184
3645
  });
4185
- const snapshot = await params.bindingStore.read(identity);
3646
+ const { binding: snapshot, assertCurrent } = await resolveCodexSessionBinding({
3647
+ reclaimStale: true,
3648
+ bindingStore: params.bindingStore,
3649
+ identity,
3650
+ config: params.params.config,
3651
+ storePath: params.params.sessionTarget?.storePath,
3652
+ assertCurrent: () => {
3653
+ params.params.hostCapabilities.assertActive();
3654
+ params.assertCurrent?.();
3655
+ },
3656
+ signal: params.signal,
3657
+ assertBinding: params.params.expectedSessionRuntimeOwnership ? (binding) => assertCodexSessionRuntimeOwnership(binding, params.params.expectedSessionRuntimeOwnership) : void 0
3658
+ });
4186
3659
  const runWithLease = () => params.bindingStore.withLease(identity, async () => {
4187
- const binding = await params.bindingStore.read(identity);
3660
+ const binding = params.bindingStore.read(identity);
3661
+ assertCodexSessionRuntimeOwnership(binding, params.params.expectedSessionRuntimeOwnership);
4188
3662
  if (binding?.threadId !== snapshot?.threadId || binding?.clientId !== snapshot?.clientId) throw new CodexThreadBindingConflictError(binding?.threadId ?? snapshot?.threadId ?? params.params.sessionId, "acquiring thread lifecycle ownership");
4189
- params.params.hostCapabilities.assertActive();
4190
- params.signal?.throwIfAborted();
4191
- return await run(identity, binding);
3663
+ assertCurrent();
3664
+ return await run(identity, binding, assertCurrent);
4192
3665
  });
4193
3666
  return snapshot?.pendingResumeConfiguration ? await withExclusiveCodexAppServerThread({
4194
3667
  bindingStore: params.bindingStore,
@@ -4206,32 +3679,27 @@ async function resumePendingCodexThread(params, context) {
4206
3679
  })) throw new Error(`Cannot configure resumed Codex thread ${binding.threadId} under a transient or incompatible session policy. The thread is preserved; retry from its normal session or use /new for the current policy.`);
4207
3680
  const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
4208
3681
  const clientId = resolveCodexAppServerClientInstanceId(params.client);
4209
- const configuration = await preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint, async (assertCurrent) => {
4210
- const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
4211
- assertCurrent();
4212
- if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
4213
- client: params.client,
4214
- abandonClient: params.abandonClient,
4215
- lifecycleTiming,
4216
- threadId: binding.threadId,
4217
- assertCurrent
4218
- });
3682
+ const resumed = await resumeExistingCodexThread(params, {
3683
+ ...context,
3684
+ prebuiltPluginThreadConfig,
3685
+ prepareResume: () => preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint),
3686
+ releaseRetainedThread: async (assertCurrent) => {
3687
+ const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
3688
+ assertCurrent();
3689
+ if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
3690
+ client: params.client,
3691
+ abandonClient: params.abandonClient,
3692
+ lifecycleTiming,
3693
+ threadId: binding.threadId,
3694
+ assertCurrent
3695
+ });
3696
+ }
4219
3697
  });
4220
- try {
4221
- const resumed = await resumeExistingCodexThread(params, {
4222
- ...context,
4223
- prebuiltPluginThreadConfig,
4224
- assertResumeConfiguration: configuration.assertConfigured,
4225
- assertResumeOwnership: configuration.assertCurrent
4226
- });
4227
- if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
4228
- return resumed;
4229
- } finally {
4230
- configuration.dispose();
4231
- }
3698
+ if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
3699
+ return resumed;
4232
3700
  }
4233
3701
  /** Manual attachment is intent, never evidence that loaded native overrides took effect. */
4234
- async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
3702
+ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint) {
4235
3703
  const fail = (reason) => /* @__PURE__ */ new Error(`Cannot configure resumed Codex thread ${binding.threadId}: ${reason}. The thread is preserved; continue it in native Codex or use /new for the current OpenClaw tools.`);
4236
3704
  const agentDir = resolveCodexThreadAgentDir(params);
4237
3705
  const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
@@ -4240,6 +3708,7 @@ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFing
4240
3708
  const assertClient = captureCodexAppServerClientLifetime(params.client, "native-process");
4241
3709
  const assertCurrent = () => {
4242
3710
  params.params.hostCapabilities.assertActive();
3711
+ params.assertCurrent?.();
4243
3712
  params.signal?.throwIfAborted();
4244
3713
  assertClient();
4245
3714
  if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
@@ -4248,10 +3717,12 @@ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFing
4248
3717
  const { thread } = await params.client.request("thread/read", {
4249
3718
  threadId: binding.threadId,
4250
3719
  includeTurns: false
4251
- }, { signal: params.signal });
3720
+ }, {
3721
+ signal: params.signal,
3722
+ assertCurrent
3723
+ });
4252
3724
  assertCurrent();
4253
- const statusType = thread.status?.type;
4254
- if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
3725
+ if (thread.id !== binding.threadId || !isCodexThreadNonRunning(thread.status)) throw fail("the native thread is not idle; wait for its current run to finish");
4255
3726
  assertCodexThreadAcceptsDirectInput(thread);
4256
3727
  const observation = observeCodexThreadConfiguration(params, thread, assertCurrent);
4257
3728
  const dispose = observation.dispose;
@@ -4262,8 +3733,6 @@ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFing
4262
3733
  const recordedTools = metadata.dynamic_tools ?? [];
4263
3734
  if (!Array.isArray(recordedTools) || codexDynamicToolsFingerprint(recordedTools) !== dynamicToolsFingerprint) throw fail("its immutable native tool catalog does not match the current OpenClaw tools");
4264
3735
  assertCurrent();
4265
- await releaseSubscription(assertCurrent);
4266
- assertCurrent();
4267
3736
  return {
4268
3737
  assertConfigured: observation.assertConfigured,
4269
3738
  assertCurrent,
@@ -4279,21 +3748,29 @@ async function prepareCodexThreadResume(params, binding, context) {
4279
3748
  const assertClient = captureCodexAppServerClientLifetime(params.client, binding.connectionScope === "supervision" ? "connection" : "native-process");
4280
3749
  const assertCurrent = () => {
4281
3750
  params.params.hostCapabilities.assertActive();
3751
+ params.assertCurrent?.();
4282
3752
  params.signal?.throwIfAborted();
4283
3753
  assertClient();
4284
3754
  if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
4285
3755
  };
4286
3756
  assertCurrent();
4287
- const thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context);
4288
- assertCurrent();
3757
+ let thread;
3758
+ try {
3759
+ thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context, assertCurrent);
3760
+ } finally {
3761
+ assertCurrent();
3762
+ }
4289
3763
  assertCodexSupervisionThreadLineage(binding, thread);
4290
3764
  return {
4291
3765
  ...observeCodexThreadConfiguration(params, thread, assertCurrent),
4292
3766
  assertCurrent
4293
3767
  };
4294
3768
  }
3769
+ function isCodexThreadNonRunning(status) {
3770
+ return status?.type === "idle" || status?.type === "notLoaded" || status?.type === "systemError";
3771
+ }
4295
3772
  function observeCodexThreadConfiguration(params, thread, assertCurrent) {
4296
- if (thread.status?.type !== "idle" && thread.status?.type !== "notLoaded") throw new CodexAdoptedThreadActiveError();
3773
+ if (!isCodexThreadNonRunning(thread.status)) throw new CodexAdoptedThreadActiveError();
4297
3774
  let unloaded = thread.status.type === "notLoaded";
4298
3775
  return {
4299
3776
  dispose: params.client.addNotificationHandler((notification) => {
@@ -4309,6 +3786,10 @@ function observeCodexThreadConfiguration(params, thread, assertCurrent) {
4309
3786
  //#region extensions/codex/src/app-server/thread-supervision.ts
4310
3787
  async function materializePendingSupervisionBranch(params) {
4311
3788
  let pending = params.binding.pendingSupervisionBranch;
3789
+ const requestOptions = {
3790
+ signal: params.signal,
3791
+ assertCurrent: params.throwIfAborted
3792
+ };
4312
3793
  const connectionFingerprint = buildCodexAppServerConnectionFingerprint(params.appServer, params.attempt.agentDir);
4313
3794
  if (!pending.connectionFingerprint || pending.connectionFingerprint !== connectionFingerprint) throw new Error("Codex supervision source connection changed before branch materialization");
4314
3795
  pending = await recoverPendingSupervisionArtifacts(params, pending);
@@ -4316,7 +3797,7 @@ async function materializePendingSupervisionBranch(params) {
4316
3797
  const sourceResponse = await params.lifecycleTiming.measure("supervision-source-read", () => params.client.request("thread/read", {
4317
3798
  threadId: pending.sourceThreadId,
4318
3799
  includeTurns: true
4319
- }, { signal: params.signal }));
3800
+ }, requestOptions));
4320
3801
  params.throwIfAborted();
4321
3802
  const sourceThread = sourceResponse.thread;
4322
3803
  if (sourceThread.id !== pending.sourceThreadId) throw new Error(`Codex supervision source read returned ${sourceThread.id} for ${pending.sourceThreadId}`);
@@ -4342,7 +3823,7 @@ async function materializePendingSupervisionBranch(params) {
4342
3823
  });
4343
3824
  } catch (error) {
4344
3825
  try {
4345
- const current = await params.bindingStore.read(params.bindingIdentity);
3826
+ const current = params.bindingStore.read(params.bindingIdentity);
4346
3827
  if (matchesPendingSupervisionState(current, pending)) cleanupExpected = pending;
4347
3828
  else if (matchesPendingSupervisionState(current, expected)) cleanupExpected = expected;
4348
3829
  else throw new CodexThreadBindingConflictError(pending.sourceThreadId, "verifying supervised Codex cleanup tracking");
@@ -4359,7 +3840,7 @@ async function materializePendingSupervisionBranch(params) {
4359
3840
  const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
4360
3841
  const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
4361
3842
  try {
4362
- return await params.client.request("thread/fork", probeParams, { signal: params.signal });
3843
+ return await params.client.request("thread/fork", probeParams, requestOptions);
4363
3844
  } catch (error) {
4364
3845
  if (!(error instanceof CodexAppServerRpcError)) throw new CodexAppServerUnsafeSubscriptionError("Codex model probe fork may have materialized without a response", { cause: error });
4365
3846
  throw error;
@@ -4414,7 +3895,7 @@ async function materializePendingSupervisionBranch(params) {
4414
3895
  });
4415
3896
  const rawStartResponse = await params.lifecycleTiming.measure("supervision-thread-start", async () => {
4416
3897
  try {
4417
- return await params.client.request("thread/start", startParams, { signal: params.signal });
3898
+ return await params.client.request("thread/start", startParams, requestOptions);
4418
3899
  } catch (error) {
4419
3900
  if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
4420
3901
  throw new CodexAppServerUnsafeSubscriptionError("Canonical Codex branch may have started without a response", { cause: error });
@@ -4435,7 +3916,7 @@ async function materializePendingSupervisionBranch(params) {
4435
3916
  });
4436
3917
  if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, finalThreadId, startParams.config, params.signal));
4437
3918
  if (params.provisionalAppIds?.length) try {
4438
- await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
3919
+ await params.lifecycleTiming.measure("plugin-app-attestation", () => checkCodexThreadAppAvailability({
4439
3920
  client: params.client,
4440
3921
  threadId: finalThreadId,
4441
3922
  appIds: params.provisionalAppIds ?? [],
@@ -4457,7 +3938,7 @@ async function materializePendingSupervisionBranch(params) {
4457
3938
  await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
4458
3939
  threadId: finalThreadId,
4459
3940
  items: history.responseItems
4460
- }, { signal: params.signal }));
3941
+ }, requestOptions));
4461
3942
  params.throwIfAborted();
4462
3943
  }
4463
3944
  const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
@@ -4474,11 +3955,11 @@ async function materializePendingSupervisionBranch(params) {
4474
3955
  modelProvider: bindingModelProvider,
4475
3956
  historyCoveredThrough
4476
3957
  }
4477
- });
3958
+ }, params.throwIfAborted);
4478
3959
  } catch (error) {
4479
3960
  let current;
4480
3961
  try {
4481
- current = await params.bindingStore.read(params.bindingIdentity);
3962
+ current = params.bindingStore.read(params.bindingIdentity);
4482
3963
  } catch (readError) {
4483
3964
  provisionalCleanupSafe = false;
4484
3965
  throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding could not be verified: ${finalThreadId}`, { cause: new AggregateError([error, readError]) });
@@ -4669,11 +4150,16 @@ async function archiveSupervisionArtifact(client, threadId) {
4669
4150
  }
4670
4151
  //#endregion
4671
4152
  //#region extensions/codex/src/app-server/thread-lifecycle-run.ts
4672
- async function startOrResumeThread(params) {
4673
- const incognito = isIncognitoSessionKey(params.params.sessionKey);
4674
- const clientId = resolveCodexAppServerClientInstanceId(params.client);
4675
- return await withCodexThreadLifecycleBinding(params, async (bindingIdentity, currentBinding) => {
4676
- let binding = currentBinding;
4153
+ async function startOrResumeThread(input) {
4154
+ const incognito = isIncognitoSessionKey(input.params.sessionKey);
4155
+ const clientId = resolveCodexAppServerClientInstanceId(input.client);
4156
+ return await withCodexThreadLifecycleBinding(input, async (bindingIdentity, saved, assert) => {
4157
+ const params = {
4158
+ ...input,
4159
+ assertCurrent: assert
4160
+ };
4161
+ const expectedOwnership = params.params.expectedSessionRuntimeOwnership;
4162
+ let binding = saved;
4677
4163
  if (hasCodexNativeToolCatalog(binding)) {
4678
4164
  const nativeCatalog = await loadCodexNativeToolCatalog({
4679
4165
  client: params.client,
@@ -4682,7 +4168,7 @@ async function startOrResumeThread(params) {
4682
4168
  agentDir: resolveCodexThreadAgentDir(params),
4683
4169
  assertCurrent: () => {
4684
4170
  params.signal?.throwIfAborted();
4685
- params.params.hostCapabilities.assertActive();
4171
+ assert();
4686
4172
  }
4687
4173
  });
4688
4174
  if (!isDeepStrictEqual(params.dynamicTools, nativeCatalog)) throw new Error("Canonical Codex declarations changed after tool preparation; retry the turn on its preserved native thread.");
@@ -4709,13 +4195,6 @@ async function startOrResumeThread(params) {
4709
4195
  threadId,
4710
4196
  assertCurrent
4711
4197
  });
4712
- if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
4713
- if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
4714
- bindingStore: params.bindingStore,
4715
- identity: bindingIdentity,
4716
- config: params.params.config
4717
- }))) throw createCodexSessionGenerationSupersededError(bindingIdentity.sessionId);
4718
- }
4719
4198
  if (binding?.pendingSupervisionBranch) {
4720
4199
  await releaseRetainedThread(binding.threadId);
4721
4200
  const pendingBinding = binding;
@@ -4749,7 +4228,10 @@ async function startOrResumeThread(params) {
4749
4228
  environmentSelection: params.environmentSelection,
4750
4229
  provisionalAppIds: pluginThreadConfig?.provisionalAppIds,
4751
4230
  signal: params.signal,
4752
- throwIfAborted,
4231
+ throwIfAborted: () => {
4232
+ throwIfAborted();
4233
+ assert();
4234
+ },
4753
4235
  lifecycleTiming,
4754
4236
  normalizeBindingModelProvider,
4755
4237
  bindingPatch: {
@@ -4781,11 +4263,11 @@ async function startOrResumeThread(params) {
4781
4263
  const clearCurrentBinding = async (operation) => {
4782
4264
  const current = binding;
4783
4265
  if (!current?.threadId) return;
4784
- assertCodexBindingMayBeReplaced(current, operation);
4266
+ assertCodexBindingMayBeReplaced(current, operation, expectedOwnership);
4785
4267
  if (!await params.bindingStore.mutate(bindingIdentity, {
4786
4268
  kind: "clear",
4787
4269
  threadId: current.threadId
4788
- })) throw new CodexThreadBindingConflictError(current.threadId, operation);
4270
+ }, assert)) throw new CodexThreadBindingConflictError(current.threadId, operation);
4789
4271
  binding = void 0;
4790
4272
  };
4791
4273
  const resolveRequestContext = () => {
@@ -4879,14 +4361,14 @@ async function startOrResumeThread(params) {
4879
4361
  const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
4880
4362
  if (binding?.threadId && (params.configuredMcpOwnershipVersion === 1 && (binding.configuredMcpOwnershipVersion !== 1 || binding.dynamicToolsFingerprint === void 0 || binding.mcpServersFingerprint !== void 0 || binding.userMcpServersFingerprint !== void 0) || params.configuredMcpOwnershipVersion !== 1 && binding.configuredMcpOwnershipVersion === 1) && binding?.threadId) {
4881
4363
  const predecessorBinding = binding;
4882
- assertCodexBindingMayBeReplaced(predecessorBinding, "changing configured MCP ownership");
4364
+ assertCodexBindingMayBeReplaced(predecessorBinding, "changing configured MCP ownership", expectedOwnership);
4883
4365
  embeddedAgentLog.debug("codex app-server configured MCP ownership changed; starting a new thread", { threadId: predecessorBinding.threadId });
4884
4366
  replacementPredecessor = predecessorBinding;
4885
4367
  binding = void 0;
4886
4368
  preserveExistingBinding = false;
4887
4369
  }
4888
4370
  if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
4889
- assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
4371
+ assertCodexBindingMayBeReplaced(binding, "changing MCP configuration", expectedOwnership);
4890
4372
  if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
4891
4373
  embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
4892
4374
  preserveExistingBinding = true;
@@ -4898,7 +4380,7 @@ async function startOrResumeThread(params) {
4898
4380
  }
4899
4381
  const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
4900
4382
  if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
4901
- assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
4383
+ assertCodexBindingMayBeReplaced(binding, "changing web-search configuration", expectedOwnership);
4902
4384
  if (!ringZeroActive && transientWebSearchRestriction) {
4903
4385
  embeddedAgentLog.debug("codex app-server tool surface restricted for turn; starting transient thread", { threadId: binding.threadId });
4904
4386
  preserveExistingBinding = true;
@@ -4909,13 +4391,13 @@ async function startOrResumeThread(params) {
4909
4391
  binding = void 0;
4910
4392
  }
4911
4393
  if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
4912
- assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
4394
+ assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn", expectedOwnership);
4913
4395
  embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
4914
4396
  preserveExistingBinding = true;
4915
4397
  binding = void 0;
4916
4398
  }
4917
4399
  if (binding?.threadId && transientDelegationRestriction) {
4918
- assertCodexBindingMayBeReplaced(binding, "starting a delegation-restricted turn");
4400
+ assertCodexBindingMayBeReplaced(binding, "starting a delegation-restricted turn", expectedOwnership);
4919
4401
  embeddedAgentLog.debug("codex app-server delegation restricted for turn; starting transient thread", { threadId: binding.threadId });
4920
4402
  binding = void 0;
4921
4403
  }
@@ -4973,7 +4455,7 @@ async function startOrResumeThread(params) {
4973
4455
  }
4974
4456
  if (binding?.threadId) {
4975
4457
  if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
4976
- assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
4458
+ assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog", expectedOwnership);
4977
4459
  preserveExistingBinding = shouldStartTransientNoToolThread({
4978
4460
  previous: binding.dynamicToolsFingerprint,
4979
4461
  nextHasDynamicTools: params.dynamicTools.length > 0
@@ -4996,27 +4478,24 @@ async function startOrResumeThread(params) {
4996
4478
  throwIfAborted();
4997
4479
  await clearCurrentBinding(incognito ? "rotating an unavailable ephemeral thread binding" : "rotating a stale plugin app binding");
4998
4480
  } else {
4999
- const configuration = await prepareCodexThreadResume(params, binding, requestContext);
5000
- try {
5001
- await releaseRetainedThread(binding.threadId, binding.clientId, configuration.assertCurrent);
5002
- configuration.assertCurrent();
5003
- const resumed = await resumeExistingCodexThread(params, {
5004
- ...requestContext,
5005
- binding,
5006
- clearCurrentBinding,
5007
- prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
5008
- prebuiltPluginThreadConfig,
5009
- buildLoadedPluginThreadConfig,
5010
- assertResumeOwnership: configuration.assertCurrent,
5011
- assertResumeConfiguration: configuration.assertConfigured
5012
- });
5013
- if (resumed) return resumed;
5014
- } finally {
5015
- configuration.dispose();
5016
- }
4481
+ const resumeBinding = binding;
4482
+ const resumed = await resumeExistingCodexThread(params, {
4483
+ ...requestContext,
4484
+ binding: resumeBinding,
4485
+ clearCurrentBinding,
4486
+ prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
4487
+ prebuiltPluginThreadConfig,
4488
+ buildLoadedPluginThreadConfig,
4489
+ prepareResume: () => prepareCodexThreadResume(params, resumeBinding, requestContext),
4490
+ releaseRetainedThread: async (assertCurrent) => {
4491
+ await releaseRetainedThread(resumeBinding.threadId, resumeBinding.clientId, assertCurrent);
4492
+ }
4493
+ });
4494
+ if (resumed) return resumed;
5017
4495
  }
5018
4496
  }
5019
4497
  }
4498
+ assertCodexBindingMayBeReplaced(binding, "starting a fresh native thread", expectedOwnership);
5020
4499
  if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
5021
4500
  const started = await startFreshCodexThread(params, {
5022
4501
  ...requestContext,
@@ -5139,6 +4618,17 @@ function buildTurnStartParams(params, options) {
5139
4618
  const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
5140
4619
  const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
5141
4620
  let additionalContext = buildCodexTemporalAdditionalContext(params, { sessionStatusAvailable: options.sessionStatusAvailable === true });
4621
+ additionalContext = {
4622
+ ...additionalContext,
4623
+ openclaw_source_delivery: {
4624
+ kind: "application",
4625
+ value: ["Current source-delivery policy for this turn (replaces earlier source-delivery guidance):", buildHarnessVisibleReplyGuidance({
4626
+ sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
4627
+ messageToolAvailable: options.messageToolAvailable === true,
4628
+ requireExplicitMessageTarget: options.requireExplicitMessageTarget
4629
+ })].join("\n")
4630
+ }
4631
+ };
5142
4632
  if (currentSenderContext) additionalContext = {
5143
4633
  ...additionalContext,
5144
4634
  openclaw_current_sender: {
@@ -5231,4 +4721,4 @@ function joinPresentSections(...sections) {
5231
4721
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
5232
4722
  }
5233
4723
  //#endregion
5234
- export { resolveCodexNativeSkillIsolation as $, readCodexMcpToolConnectorId as A, refreshCodexPluginRuntimeState as At, emitCodexNativePreToolUseFailureDiagnostic as B, buildDeveloperInstructions as C, buildCodexPluginThreadConfig as Ct, resolveCodexAppServerThreadModelSelection as D, mergeCodexThreadConfigs as Dt, resolveCodexAppServerRequestModelSelection as E, buildDisabledAppsConfigPatch as Et, assertCodexNativeHookRelayAllowed as F, attestCodexRestrictedToolSurfaceMcpServersDisabled as G, resolveCodexNativeHookRelayTtlMs as H, buildCodexNativeHookRelayConfig as I, createCodexManagedThreadStore as J, CODEX_MANAGED_THREAD_MAX_ENTRIES as K, buildCodexNativeHookRelayDisabledConfig as L, isCodexAppServerProfilerEnabled as M, isOpenAiCuratedMarketplaceName as Mt, CODEX_NATIVE_HOOK_RELAY_EVENTS as N, pluginReadParams as Nt, resolveCodexBindingModelProviderFallback as O, shouldBuildCodexPluginThreadConfig as Ot, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as P, resolveRecoverableCodexPluginConfigKeys as Pt, applyCodexNativeSkillIsolation as Q, buildCodexNativeHookRelayId as R, resolveCodexWebSearchPlan as S, buildCodexPluginAppsConfigPatchFromPolicyContext as St, resolveCodexAppServerModelProvider as T, buildCodexPluginThreadConfigTimeoutFallback as Tt, scheduleCodexNativeHookRelayUnregister as U, resolveCodexNativeHookRelayEvents as V, attestCodexPluginThreadApps as W, loadCodexNativeToolCatalog as X, hasCodexNativeToolCatalog as Y, parseCodexNativeToolCatalog as Z, buildCodexRingZeroThreadConfigPatch as _, neutralizeCodexExplicitMentionSigils as _t, readCodexSupportedReasoningEfforts as a, filterCodexDynamicTools as at, readCodexInheritedMcpServerNames as b, resolveCodexContextEngineProjectionReserveTokens as bt, assertScheduledCodexAppAuthorityRuntime as c, isMessageOnlyCodexSourceReply as ct, buildScheduledCodexAppServerConnectionIdentity as d, resolveCodexDynamicToolsLoading as dt, areCodexDynamicToolFingerprintsCompatible as et, captureScheduledCodexAppAuthority as f, resolveCodexDynamicToolsLoadingForRuntime as ft, assertCodexManagedRequirementsDoNotOverrideToolPolicy as g, fitCodexProjectedContextForTurnStart as gt, resolveScheduledCodexAppCreatorCaptureDecision as h, buildCodexContinuityCalibration as ht, buildCodexUserInput as i, fingerprintUserMcpServersConfigPatch as it, readCodexMcpToolUiVisibility as j, isOpenAiCuratedMarketplace as jt, buildCodexProjectDocThreadConfig as k, ensureCodexPluginActivation as kt, buildLegacyScheduledCodexAppRecoveryPrompt as l, isSystemAgentOnlyCodexDynamicToolAllowlist as lt, readCurrentCodexScheduledAppPolicy as m, isContextEngineBindingCompatible as mt, buildTurnCollaborationMode as n, codexLegacyDynamicToolsFingerprint as nt, resolveCodexAppServerReasoningEffort as o, filterCodexDynamicToolsForDisabledNativeSurface as ot, intersectCodexPluginThreadConfigWithScheduledAuthority as p, buildContextEngineBinding as pt, CODEX_MANAGED_THREAD_NAMESPACE as q, buildTurnStartParams as r, fingerprintJsonObject as rt, startOrResumeThread as s, isForcedPrivateQaCodexRuntime as st, buildCodexTemporalAdditionalContext as t, codexDynamicToolsFingerprint as tt, buildScheduledCodexAppAuthorityInputFingerprint as u, normalizeCodexDynamicToolName as ut, buildCodexRuntimeThreadConfig as v, projectContextEngineAssemblyForCodex as vt, CODEX_NATIVE_PERSONALITY_NONE as w, buildCodexPluginThreadConfigInputFingerprint as wt, buildCodexNativeWebSearchThreadConfig as x, resolveCodexContinuityProjectionMaxChars as xt, buildCodexThreadConfiguration as y, resolveCodexContextEngineProjectionMaxChars as yt, createCodexNativeHookRelay as z };
4724
+ export { codexLegacyDynamicToolsFingerprint as $, readCodexMcpToolConnectorId as A, emitCodexNativePreToolUseFailureDiagnostic as B, buildDeveloperInstructions as C, buildCodexPluginThreadConfigTimeoutFallback as Ct, resolveCodexAppServerThreadModelSelection as D, shouldBuildCodexPluginThreadConfig as Dt, resolveCodexAppServerRequestModelSelection as E, refreshCodexPluginAppApprovalPolicy as Et, assertCodexNativeHookRelayAllowed as F, attestCodexRestrictedToolSurfaceMcpServersDisabled as G, resolveCodexNativeHookRelayTtlMs as H, buildCodexNativeHookRelayConfig as I, parseCodexNativeToolCatalog as J, hasCodexNativeToolCatalog as K, buildCodexNativeHookRelayDisabledConfig as L, isCodexAppServerProfilerEnabled as M, CODEX_NATIVE_HOOK_RELAY_EVENTS as N, resolveCodexBindingModelProviderFallback as O, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as P, codexDynamicToolsFingerprint as Q, buildCodexNativeHookRelayId as R, resolveCodexWebSearchPlan as S, buildCodexPluginThreadConfigInputFingerprint as St, resolveCodexAppServerModelProvider as T, mergeCodexThreadConfigs as Tt, scheduleCodexNativeHookRelayUnregister as U, resolveCodexNativeHookRelayEvents as V, checkCodexThreadAppAvailability as W, resolveCodexNativeSkillIsolation as X, applyCodexNativeSkillIsolation as Y, areCodexDynamicToolFingerprintsCompatible as Z, buildCodexRingZeroThreadConfigPatch as _, projectContextEngineAssemblyForCodex as _t, readCodexSupportedReasoningEfforts as a, isMessageOnlyCodexSourceReply as at, readCodexInheritedMcpServerNames as b, resolveCodexContinuityProjectionMaxChars as bt, assertScheduledCodexAppAuthorityRuntime as c, resolveCodexDynamicToolsLoading as ct, buildScheduledCodexAppServerConnectionIdentity as d, isContextEngineBindingCompatible as dt, fingerprintJsonObject as et, captureScheduledCodexAppAuthority as f, CodexContextAttachmentError as ft, assertCodexManagedRequirementsDoNotOverrideToolPolicy as g, neutralizeCodexExplicitMentionSigils as gt, resolveScheduledCodexAppCreatorCaptureDecision as h, isCodexDurableCustomMessage as ht, buildCodexUserInput as i, isForcedPrivateQaCodexRuntime as it, readCodexMcpToolUiVisibility as j, buildCodexProjectDocThreadConfig as k, buildLegacyScheduledCodexAppRecoveryPrompt as l, resolveCodexDynamicToolsLoadingForRuntime as lt, readCurrentCodexScheduledAppPolicy as m, fitCodexProjectedContextForTurnStart as mt, buildTurnCollaborationMode as n, filterCodexDynamicTools as nt, resolveCodexAppServerReasoningEffort as o, isSystemAgentOnlyCodexDynamicToolAllowlist as ot, intersectCodexPluginThreadConfigWithScheduledAuthority as p, buildCodexContinuityCalibration as pt, loadCodexNativeToolCatalog as q, buildTurnStartParams as r, filterCodexDynamicToolsForDisabledNativeSurface as rt, startOrResumeThread as s, normalizeCodexDynamicToolName as st, buildCodexTemporalAdditionalContext as t, fingerprintUserMcpServersConfigPatch as tt, buildScheduledCodexAppAuthorityInputFingerprint as u, buildContextEngineBinding as ut, buildCodexRuntimeThreadConfig as v, resolveCodexContextEngineProjectionMaxChars as vt, CODEX_NATIVE_PERSONALITY_NONE as w, buildDisabledAppsConfigPatch as wt, buildCodexNativeWebSearchThreadConfig as x, buildCodexPluginThreadConfig as xt, buildCodexThreadConfiguration as y, resolveCodexContextEngineProjectionReserveTokens as yt, createCodexNativeHookRelay as z };