@openclaw/codex 2026.9.1 → 2026.9.2

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 (67) hide show
  1. package/dist/api.js +6 -6
  2. package/dist/{attempt-client-cleanup-DYHcRQAa.js → attempt-client-cleanup-Bp1ZOnk2.js} +76 -32
  3. package/dist/{attempt-context-DmQUNWwT.js → attempt-context-CSazkWns.js} +6 -6
  4. package/dist/{bounded-turn-wzJrwKKe.js → bounded-turn-DxGJRYbY.js} +46 -45
  5. package/dist/{capabilities-dIjNVCYH.js → capabilities-B1mgvpP8.js} +2 -1
  6. package/dist/cli-metadata.js +1 -1
  7. package/dist/{command-formatters-CLHhEB1Q.js → command-formatters-DSbGMXA0.js} +2 -2
  8. package/dist/{command-handlers-CTGb2csu.js → command-handlers-DOJvGv6-.js} +146 -74
  9. package/dist/{compact-_t0HQrUS.js → compact-Y_xBqrHN.js} +83 -48
  10. package/dist/{config-BhmfC1s6.js → config-CyapYSji.js} +47 -105
  11. package/dist/config-utils-C57JiJrU.js +69 -0
  12. package/dist/doctor-contract-api.js +4 -5
  13. package/dist/{dynamic-tools-BU9tmlYm.js → dynamic-tools-BwpvTxaX.js} +678 -638
  14. package/dist/{effective-mcp-catalog-DzYBW4h1.js → effective-mcp-catalog-CY9Jn4bA.js} +4 -3
  15. package/dist/{event-projector-assistant-message-DHRFXKbS.js → event-projector-assistant-message-B9kp6iGC.js} +11 -2
  16. package/dist/{event-projector-usage-C5mcrGjp.js → event-projector-usage-ZqEVbAEY.js} +30 -13
  17. package/dist/{harness-DXVZ5II5.js → harness-Czh2Sdoc.js} +36 -13
  18. package/dist/harness.js +1 -1
  19. package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
  20. package/dist/index.js +68 -84
  21. package/dist/{isolated-completion-kg9kv4iH.js → isolated-completion-CRh-51AK.js} +9 -21
  22. package/dist/{managed-binary-4-Vjz5GH.js → managed-binary-DcpnJF9x.js} +105 -73
  23. package/dist/media-understanding-provider.js +1 -1
  24. package/dist/{model-catalog-CUM68Hkl.js → model-catalog-DT5vo6qo.js} +5 -5
  25. package/dist/{models-sYnEc0gd.js → models-CcrgSMQR.js} +4 -4
  26. package/dist/{native-subagent-monitor-CnH147jH.js → native-subagent-monitor-B3bvQGcg.js} +12 -11
  27. package/dist/{node-exec-server.runtime-CstNHCmC.js → node-exec-server.runtime-nfbUOtUB.js} +3 -3
  28. package/dist/{plugin-approval-roundtrip-CNe-Xrjx.js → plugin-approval-roundtrip-aQVdyL5g.js} +1 -1
  29. package/dist/{plugin-marketplace-discovery-CfFlKqDl.js → plugin-marketplace-discovery-B7gHXJ3d.js} +35 -22
  30. package/dist/{plugin-metadata-cache-BFrPhb2c.js → plugin-metadata-cache-BwCLTY_U.js} +5 -5
  31. package/dist/{provider-capabilities-DW5p4k9w.js → provider-capabilities-BpG9Ltc5.js} +2 -2
  32. package/dist/{rate-limits-CawQJfUS.js → rate-limits-Fx_l4eNa.js} +1 -1
  33. package/dist/{request-DYAI5FQE.js → request-C8H22rPi.js} +2 -2
  34. package/dist/{run-attempt-D1hJO4fX.js → run-attempt-BzthFQSM.js} +977 -723
  35. package/dist/{runtime-artifact-C3jVimFn.js → runtime-artifact-Bh_gf6-t.js} +3 -3
  36. package/dist/{session-binding-BHuTCRZQ.js → session-binding-4Ki04sW-.js} +94 -282
  37. package/dist/session-binding-record-CyaXZvTe.js +270 -0
  38. package/dist/{session-catalog-yyvTWFOw.js → session-catalog-B47uwekb.js} +266 -114
  39. package/dist/{session-cli-CmFEXMAM.js → session-cli-BjLhpmS1.js} +1 -1
  40. package/dist/session-history-DWmMqiqW.js +93 -0
  41. package/dist/session-history-read-LHldwvyh.js +36 -0
  42. package/dist/session-history-worker-runtime.js +48 -0
  43. package/dist/{settled-turn-context-D3SNWtUn.js → session-history.worker-zZVcx0ME.js} +30 -31
  44. package/dist/session-history.worker.js +2 -0
  45. package/dist/{session-initialization-BeWzjGiR.js → session-initialization-BCFVnLL4.js} +2 -1
  46. package/dist/{session-permission-policy-DeWVTCMj.js → session-permission-policy-BaueHmSj.js} +1 -1
  47. package/dist/{session-retirement-BujDjXVF.js → session-retirement-DNdmYdFc.js} +7 -7
  48. package/dist/{session-upstream-marker-C351-5Qw.js → session-upstream-marker-DReTpGz6.js} +4 -3
  49. package/dist/settled-turn-context-BOM3Y1pl.js +45 -0
  50. package/dist/{settled-turn-finalizer-BVJx4yoc.js → settled-turn-finalizer-BI-b1sry.js} +53 -42
  51. package/dist/{shared-client-DxcfF9KX.js → shared-client-CKhX2kFt.js} +279 -268
  52. package/dist/{side-question-BTZAII4l.js → side-question-B6DWAw1A.js} +85 -36
  53. package/dist/{thread-lifecycle-DAcEJ_Om.js → thread-lifecycle-DPK30jad.js} +382 -388
  54. package/dist/{thread-ownership-zGokd8i1.js → thread-ownership-CbucAmmp.js} +5 -5
  55. package/dist/thread-policy-CTalhWFy.js +105 -0
  56. package/dist/{thread-resume-C7pyVKeW.js → thread-resume-BLgqkUc1.js} +7 -7
  57. package/dist/{transcript-mirror-DTUxFWa_.js → transcript-mirror-_njEB84z.js} +67 -125
  58. package/dist/transcript-mirror-attestation-Byl34PC-.js +76 -0
  59. package/dist/{transport-stdio-CyPldczm.js → transport-stdio-CzTngV0j.js} +10 -8
  60. package/dist/{upstream-session-fork-CYlBIGVt.js → upstream-session-fork-1fwbNKzQ.js} +85 -165
  61. package/dist/{usage-Bj-rrzkR.js → usage-i9fv5-4m.js} +3 -3
  62. package/dist/{web-search-provider.runtime-D2FWSK4w.js → web-search-provider.runtime-DKQn67A7.js} +3 -3
  63. package/package.json +6 -6
  64. package/dist/binding-connection-E6uDsPlx.js +0 -45
  65. package/dist/launch-args-RXQwn8zV.js +0 -90
  66. package/dist/session-history-C5J1AKzG.js +0 -87
  67. package/dist/thread-policy-g4KbJPzp.js +0 -54
@@ -1,26 +1,28 @@
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 { h as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, m as CODEX_PLUGINS_MARKETPLACE_NAME } from "./config-utils-C57JiJrU.js";
2
+ import { l as sessionBindingIdentity } from "./session-binding-record-CyaXZvTe.js";
3
+ import { B as isJsonObject, C as CODEX_SESSION_OVERRIDABLE_LAYER_TYPES, M as resolveCodexAppServerHomeDir, N as resolveCodexAppServerLocalHomeDir, k as resolveCodexPluginsPolicy, n as codexSandboxPolicyForTurn, p as assertCodexModelBackedReviewerEffectiveConfig, w as readCodexEffectiveConfig, z as flattenCodexDynamicToolFunctions } from "./config-CyapYSji.js";
4
+ import { At as readCodexClientSessionMeta, Dt as isCodexAppServerClientRuntimeLive, E as CodexThreadStartRequestError, H as isCodexAppServerOverloadError, I as getCodexAppServerClientInstanceId, J as isCodexThreadReadMissingError, K as resolveCodexAppServerClientInstanceId, Nt as unsubscribeCodexAppServerLiveThread, Ot as isCodexAppServerLiveThreadClaimed, Pt as withAbortableTimeout, Q as assertCodexThreadAcceptsDirectInput, T as CodexThreadBindingConflictError, W as isCodexAppServerRequestTimeoutError, Xt as readCodexSessionMeta, _ as retainSharedCodexAppServerClientByInstanceId, et as assertCodexThreadForkResponse, jt as releaseCodexAppServerLiveThread, n as captureCodexAppServerClientLifetime, nt as assertCodexThreadStartResponse, q as CodexAppServerRpcError, w as CodexAdoptedThreadActiveError, wt as consumeCodexAppServerLiveThread, yt as resolveCodexToolAbortTerminalReason } from "./shared-client-CKhX2kFt.js";
5
+ import { a as isCodexAppServerNativeAuthProfile, i as hashCodexAppServerBindingFingerprint, l as resolveCodexSessionBinding, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider } from "./session-binding-4Ki04sW-.js";
6
+ 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-Bp1ZOnk2.js";
7
+ import { c as defaultCodexAppInventoryCache, l as serializeCodexAppInventoryError, n as buildCodexAppServerConnectionFingerprint, s as CodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-BLgqkUc1.js";
8
+ import { c as withExclusiveCodexAppServerThread, l as isIncognitoSessionKey, o as withCodexAppServerThreadMutation, r as retainCodexAppServerBindingSubscription } from "./thread-ownership-CbucAmmp.js";
9
+ import { a as assertCodexSessionRuntimeOwnership, i as refreshCodexThreadPolicy, n as CodexThreadPolicyHandoffError, r as assertCodexSupervisionThreadLineage, t as CodexIncognitoPolicyChangeError } from "./thread-policy-CTalhWFy.js";
10
+ import { d as codexCatalogHomeId, s as projectBoundedCodexThreadHistory } from "./transcript-mirror-_njEB84z.js";
11
+ import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-tupAr-2o.js";
10
12
  import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
13
+ import * as crypto$1 from "node:crypto";
14
+ import crypto, { createHash } from "node:crypto";
15
+ import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
16
+ import { z } from "zod";
17
+ import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
11
18
  import fs from "node:fs/promises";
12
19
  import path from "node:path";
13
20
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
14
- import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
15
21
  import { AgentHarnessPreflightError, SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, buildTemporalContextText, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
16
22
  import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
17
- import * as crypto$1 from "node:crypto";
18
- import crypto, { createHash } from "node:crypto";
19
23
  import os from "node:os";
20
24
  import { isDeepStrictEqual } from "node:util";
21
- 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";
25
+ import { resolveAgentDir as resolveAgentDir$2 } from "openclaw/plugin-sdk/agent-runtime";
24
26
  import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
25
27
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
28
  import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
@@ -479,6 +481,30 @@ function activationFailure(identity, reason, diagnostic, extraDiagnostics = [])
479
481
  };
480
482
  }
481
483
  //#endregion
484
+ //#region extensions/codex/src/app-server/plugin-app-approval-overrides.ts
485
+ /** Projects ask approvals into the native session layer without changing saved settings. */
486
+ function buildCodexAppApprovalOverrides(config, app) {
487
+ const appsRoot = config.apps;
488
+ const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
489
+ if (!isJsonObject(appConfig)) return {};
490
+ const overrides = {};
491
+ const keys = app.approvalOverrideToolConfigKeys;
492
+ for (const [section, fields] of [["tools", { approval_mode: "auto" }], ["links", {
493
+ approvals_reviewer: "user",
494
+ default_tools_approval_mode: "auto"
495
+ }]]) {
496
+ const entries = appConfig[section];
497
+ if (!isJsonObject(entries)) continue;
498
+ const projected = [];
499
+ for (const [name, value] of Object.entries(entries).toSorted(([left], [right]) => left.localeCompare(right))) {
500
+ if (!isJsonObject(value) || section === "tools" && keys && !keys.includes(name)) continue;
501
+ if (Object.keys(fields).some((field) => value[field] !== void 0 && value[field] !== null)) projected.push([name, fields]);
502
+ }
503
+ if (projected.length > 0) overrides[section] = Object.fromEntries(projected);
504
+ }
505
+ return overrides;
506
+ }
507
+ //#endregion
482
508
  //#region extensions/codex/src/app-server/plugin-thread-app-admission.ts
483
509
  function resolveCodexPluginThreadAppCacheKey(params) {
484
510
  return params.threadId ? `${params.appCacheKey}:thread:${encodeURIComponent(params.threadId)}` : params.appCacheKey;
@@ -592,12 +618,15 @@ async function readCodexConfigForAppAdmission(params) {
592
618
  return [];
593
619
  }
594
620
  if (!isJsonObject(layer.config)) throw new Error("Codex config/read returned an invalid layer config");
621
+ if (!isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned an invalid config layer source");
622
+ 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
623
  return [layer.config];
596
624
  })
597
625
  };
598
626
  } catch (error) {
599
- embeddedAgentLog.warn("codex plugin app admission config read failed", { error: serializeCodexAppInventoryError(error) });
600
- return;
627
+ const details = serializeCodexAppInventoryError(error);
628
+ embeddedAgentLog.warn("codex plugin app admission config read failed", { error: details });
629
+ 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
630
  }
602
631
  }
603
632
  function resolveCodexExplicitAppEnablement(layersHighestPrecedenceFirst, appId) {
@@ -617,7 +646,7 @@ function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
617
646
  * Builds Codex thread config patches that expose only policy-approved apps
618
647
  * for native Codex turns.
619
648
  */
620
- const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 4;
649
+ const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 6;
621
650
  const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
622
651
  /** Returns true when plugin config exists and thread config may need app patches. */
623
652
  function shouldBuildCodexPluginThreadConfig(pluginConfig) {
@@ -763,20 +792,17 @@ async function buildCodexPluginThreadConfig(params) {
763
792
  });
764
793
  }
765
794
  const accountAppsResult = policy.allowAllPlugins ? await readCodexThreadAdmissibleAccountApps(params, appCache) : { apps: [] };
795
+ let appAdmissionConfig;
796
+ const getAdmissionConfig = () => appAdmissionConfig ??= readCodexConfigForAppAdmission(params);
766
797
  const diagnostics = [
767
798
  ...inventory.diagnostics,
768
799
  ...activationDiagnostics,
769
800
  ...accountAppsResult.diagnostic ? [accountAppsResult.diagnostic] : []
770
801
  ];
771
802
  const provisionalAppIds = /* @__PURE__ */ new Set();
772
- const apps = { _default: {
773
- enabled: false,
774
- destructive_enabled: false,
775
- open_world_enabled: false
776
- } };
803
+ const { apps } = buildDisabledAppsConfigPatch();
777
804
  const policyApps = {};
778
805
  const pluginAppIds = {};
779
- let configForAppAdmission;
780
806
  const pluginOwnedAppIds = collectCodexReservedPluginAppIds({
781
807
  policy,
782
808
  inventory,
@@ -803,7 +829,7 @@ async function buildCodexPluginThreadConfig(params) {
803
829
  record,
804
830
  inventory
805
831
  })) {
806
- const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await (configForAppAdmission ??= readCodexConfigForAppAdmission(params));
832
+ const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await getAdmissionConfig();
807
833
  if (!admissionConfig || resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) {
808
834
  diagnostics.push({
809
835
  code: "app_not_ready",
@@ -812,16 +838,8 @@ async function buildCodexPluginThreadConfig(params) {
812
838
  });
813
839
  continue;
814
840
  }
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
841
  provisionalAppIds.add(app.id);
824
- apps[app.id] = buildEnabledAppConfig(record.policy);
842
+ apps[app.id] = buildEnabledAppConfig(record.policy, record.policy.destructiveApprovalMode === "ask" ? buildCodexAppApprovalOverrides(admissionConfig.config, app) : void 0);
825
843
  policyApps[app.id] = {
826
844
  configKey: record.policy.configKey,
827
845
  marketplaceName: record.policy.marketplaceName,
@@ -835,26 +853,11 @@ async function buildCodexPluginThreadConfig(params) {
835
853
  }
836
854
  for (const app of unresolvedDisabledPluginOwnership ? [] : accountAppsResult.apps) {
837
855
  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
- }
856
+ const admissionConfig = await getAdmissionConfig();
847
857
  if (resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) continue;
848
858
  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
859
  provisionalAppIds.add(app.id);
857
- apps[app.id] = buildEnabledAppConfig(policy);
860
+ apps[app.id] = buildEnabledAppConfig(policy, policy.destructiveApprovalMode === "ask" ? buildCodexAppApprovalOverrides(admissionConfig.config, accountApp) : void 0);
858
861
  policyApps[app.id] = {
859
862
  source: "account",
860
863
  appName: app.name,
@@ -864,7 +867,7 @@ async function buildCodexPluginThreadConfig(params) {
864
867
  mcpServerNames: []
865
868
  };
866
869
  }
867
- const configPatch = { apps };
870
+ const configPatch = Object.keys(policyApps).length === 0 ? buildDisabledAppsConfigPatch() : disableUnlistedCodexApps({ apps }, (await getAdmissionConfig()).config);
868
871
  const policyContext = buildPluginAppPolicyContext(policyApps, pluginAppIds);
869
872
  return {
870
873
  enabled: true,
@@ -914,36 +917,76 @@ function emptyPluginThreadConfig(params) {
914
917
  };
915
918
  }
916
919
  function buildDisabledAppsConfigPatch() {
917
- return { apps: { _default: {
918
- enabled: false,
919
- destructive_enabled: false,
920
- open_world_enabled: false
921
- } } };
920
+ return {
921
+ "features.apps": false,
922
+ apps: { _default: {
923
+ enabled: false,
924
+ destructive_enabled: false,
925
+ open_world_enabled: false
926
+ } }
927
+ };
928
+ }
929
+ function disableUnlistedCodexApps(configPatch, nativeConfig) {
930
+ const apps = { ...configPatch.apps };
931
+ for (const id of Object.keys(isJsonObject(nativeConfig.apps) ? nativeConfig.apps : {})) if (id !== "_default" && !Object.hasOwn(apps, id)) apps[id] = { enabled: false };
932
+ return {
933
+ ...configPatch,
934
+ apps
935
+ };
922
936
  }
923
- function buildEnabledAppConfig(policy) {
937
+ function buildEnabledAppConfig(policy, approvalOverrides = {}) {
924
938
  return {
939
+ ...approvalOverrides,
925
940
  enabled: true,
926
941
  destructive_enabled: policy.allowDestructiveActions,
927
- open_world_enabled: true,
942
+ open_world_enabled: policy.allowOpenWorld !== false,
928
943
  default_tools_approval_mode: "auto",
929
944
  ...policy.destructiveApprovalMode === "ask" ? { approvals_reviewer: "user" } : {}
930
945
  };
931
946
  }
932
947
  /** Rebuilds the safe per-thread apps patch persisted with a Codex thread binding. */
933
948
  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" } : {}
949
+ const disabledConfigPatch = buildDisabledAppsConfigPatch();
950
+ const { apps } = disabledConfigPatch;
951
+ for (const [appId, policy] of Object.entries(policyContext.apps).toSorted(([left], [right]) => left.localeCompare(right))) apps[appId] = buildEnabledAppConfig(policy);
952
+ return Object.keys(policyContext.apps).length > 0 ? { apps } : disabledConfigPatch;
953
+ }
954
+ /** Projects current ask overrides before a side thread replays its bound app policy. */
955
+ async function refreshCodexPluginAppApprovalPolicy(params) {
956
+ if (Object.keys(params.policyContext.apps).length === 0) return {
957
+ policyContext: params.policyContext,
958
+ configPatch: buildDisabledAppsConfigPatch(),
959
+ diagnostics: []
960
+ };
961
+ const targetApps = Object.entries(params.policyContext.apps).filter(([, app]) => app.destructiveApprovalMode === "ask").toSorted(([left], [right]) => left.localeCompare(right));
962
+ const targetAppIds = targetApps.map(([id]) => id);
963
+ const diagnostics = [];
964
+ const readParams = {
965
+ ...params,
966
+ appCacheKey: "approval-policy-replay"
967
+ };
968
+ const [inventory, admissionConfig] = await Promise.all([targetAppIds.length > 0 ? refreshCodexPluginAppInventory(readParams, new CodexAppInventoryCache(), { targetAppIds }) : void 0, readCodexConfigForAppAdmission(readParams)]);
969
+ const configPatch = disableUnlistedCodexApps(buildCodexPluginAppsConfigPatchFromPolicyContext(params.policyContext), admissionConfig.config);
970
+ const currentApps = new Map(inventory?.apps.map((app) => [app.id, toCodexPluginOwnedAccountApp(app)]));
971
+ const apps = { ...params.policyContext.apps };
972
+ for (const [id, policy] of targetApps) {
973
+ const app = currentApps.get(id);
974
+ if (!app) diagnostics.push({
975
+ code: "app_not_ready",
976
+ message: `Could not verify current Codex app approval policy for ${id}; the app was not exposed.`
977
+ });
978
+ else {
979
+ configPatch.apps[id] = buildEnabledAppConfig(policy, buildCodexAppApprovalOverrides(admissionConfig.config, app));
980
+ continue;
981
+ }
982
+ delete apps[id];
983
+ configPatch.apps[id] = { enabled: false };
984
+ }
985
+ return {
986
+ policyContext: buildPluginAppPolicyContext(apps, Object.fromEntries(Object.entries(params.policyContext.pluginAppIds).map(([key, ids]) => [key, ids.filter((id) => Object.hasOwn(apps, id))]))),
987
+ configPatch,
988
+ diagnostics
945
989
  };
946
- return { apps };
947
990
  }
948
991
  function buildPluginAppPolicyContext(apps, pluginAppIds) {
949
992
  return {
@@ -956,49 +999,6 @@ function buildPluginAppPolicyContext(apps, pluginAppIds) {
956
999
  pluginAppIds
957
1000
  };
958
1001
  }
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
1002
  function shouldWaitForInitialAppInventory(params, policy, inventory) {
1003
1003
  if (inventory.records.some((record) => record.activationRequired)) return false;
1004
1004
  return shouldRefreshMissingAppInventory(params, policy, inventory);
@@ -1030,19 +1030,22 @@ function policyFingerprint(policy) {
1030
1030
  };
1031
1031
  }
1032
1032
  function mergeJsonObjects(left, right) {
1033
- const merged = { ...left };
1033
+ const merged = {
1034
+ ...left,
1035
+ ...right
1036
+ };
1034
1037
  for (const [key, value] of Object.entries(right)) {
1035
- const existing = merged[key];
1036
- merged[key] = isJsonObject(existing) && isJsonObject(value) ? mergeJsonObjects(existing, value) : value;
1038
+ const existing = left[key];
1039
+ if (Object.hasOwn(left, key) && isJsonObject(existing) && isJsonObject(value)) merged[key] = mergeJsonObjects(existing, value);
1037
1040
  }
1038
1041
  return merged;
1039
1042
  }
1040
1043
  function fingerprintJson(value) {
1041
- return crypto.createHash("sha256").update(stableStringify$1(value)).digest("hex");
1044
+ return crypto.createHash("sha256").update(stringifyCodexPluginPolicy(value)).digest("hex");
1042
1045
  }
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(",")}}`;
1046
+ function stringifyCodexPluginPolicy(value) {
1047
+ if (Array.isArray(value)) return `[${value.map((item) => stringifyCodexPluginPolicy(item)).join(",")}]`;
1048
+ 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
1049
  return JSON.stringify(value);
1047
1050
  }
1048
1051
  //#endregion
@@ -1070,11 +1073,11 @@ function projectContextEngineAssemblyForCodex(params) {
1070
1073
  const prompt = params.prompt.trim();
1071
1074
  const contextMessages = dropDuplicateTrailingPrompt(params.assembledMessages, prompt);
1072
1075
  const maxRenderedContextChars = normalizeRenderedContextMaxChars(params.maxRenderedContextChars);
1073
- const renderedContext = neutralizeCodexExplicitMentionSigils(renderMessagesForCodexContext(contextMessages, {
1076
+ const boundedContext = renderMessagesForCodexContext(contextMessages, {
1074
1077
  maxTextPartChars: resolveTextPartMaxChars(maxRenderedContextChars),
1075
- toolPayloadMode: params.toolPayloadMode ?? "elide"
1076
- }));
1077
- const boundedContext = renderedContext ? truncateOlderContext(renderedContext, maxRenderedContextChars) : void 0;
1078
+ toolPayloadMode: params.toolPayloadMode ?? "elide",
1079
+ maxRenderedContextChars
1080
+ });
1078
1081
  const promptPrefix = boundedContext ? [
1079
1082
  CONTEXT_HEADER,
1080
1083
  CONTEXT_SAFETY_NOTE,
@@ -1187,10 +1190,23 @@ function dropDuplicateTrailingPrompt(messages, prompt) {
1187
1190
  return extractMessageText(trailing).trim() === prompt ? messages.slice(0, -1) : messages;
1188
1191
  }
1189
1192
  function renderMessagesForCodexContext(messages, options) {
1190
- return messages.map((message) => {
1193
+ const tail = [];
1194
+ let totalChars = 0;
1195
+ let retainedChars = 0;
1196
+ for (let index = messages.length - 1; index >= 0; index--) {
1197
+ const message = messages[index];
1191
1198
  const text = renderMessageBody(message, options);
1192
- return text ? `[${message.role}]\n${text}` : void 0;
1193
- }).filter((value) => Boolean(value)).join("\n\n");
1199
+ if (!text) continue;
1200
+ const chunk = `[${message.role}]\n${text}${totalChars > 0 ? "\n\n" : ""}`;
1201
+ totalChars += chunk.length;
1202
+ const remaining = options.maxRenderedContextChars - retainedChars;
1203
+ if (remaining > 0) {
1204
+ const retained = neutralizeCodexExplicitMentionSigils(chunk).slice(-remaining);
1205
+ tail.push(retained);
1206
+ retainedChars += retained.length;
1207
+ }
1208
+ }
1209
+ return truncateOlderContext(tail.toReversed().join(""), options.maxRenderedContextChars, totalChars);
1194
1210
  }
1195
1211
  function renderMessageBody(message, options) {
1196
1212
  if (message.role === "compactionSummary" || message.role === "branchSummary") return truncateText(message.summary.trim(), options.maxTextPartChars);
@@ -1312,13 +1328,13 @@ function truncateText(text, maxChars) {
1312
1328
  const truncated = truncateUtf16Safe(text, maxChars);
1313
1329
  return `${truncated}\n[truncated ${text.length - truncated.length} chars]`;
1314
1330
  }
1315
- function truncateOlderContext(text, maxChars) {
1316
- if (text.length <= maxChars) return text;
1331
+ function truncateOlderContext(text, maxChars, totalChars = text.length) {
1332
+ if (totalChars <= maxChars) return text;
1317
1333
  if (maxChars <= 0) return "";
1318
1334
  const buildMarker = (omittedChars) => `[truncated ${omittedChars} chars from older context]\n`;
1319
- let marker = buildMarker(text.length - maxChars);
1335
+ let marker = buildMarker(totalChars - maxChars);
1320
1336
  let tailChars = Math.max(0, maxChars - marker.length);
1321
- marker = buildMarker(text.length - tailChars);
1337
+ marker = buildMarker(totalChars - tailChars);
1322
1338
  if (marker.length >= maxChars) return marker.slice(0, maxChars);
1323
1339
  tailChars = maxChars - marker.length;
1324
1340
  return `${marker}${sliceUtf16Safe(text, -tailChars).trimStart()}`;
@@ -1556,15 +1572,10 @@ function fingerprintUserMcpServersConfigPatch(configPatch) {
1556
1572
  function redactUserMcpServersFingerprintSecrets(value) {
1557
1573
  if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
1558
1574
  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;
1575
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
1576
+ 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]))];
1577
+ return [key, redactUserMcpServersFingerprintSecrets(entry)];
1578
+ }));
1568
1579
  }
1569
1580
  function fingerprintUserMcpServersAuthorizationHeader(value) {
1570
1581
  return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
@@ -1595,9 +1606,7 @@ function fingerprintDynamicToolSpec(tool) {
1595
1606
  function stabilizeJsonValue(value) {
1596
1607
  if (Array.isArray(value)) return value.map(stabilizeJsonValue);
1597
1608
  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;
1609
+ return Object.fromEntries(Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, stabilizeJsonValue(child)]));
1601
1610
  }
1602
1611
  function readActiveCodexTurnIds(thread) {
1603
1612
  return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
@@ -1827,10 +1836,12 @@ function applyCodexNativeSkillIsolation(config, isolation) {
1827
1836
  function hasCodexNativeToolCatalog(binding) {
1828
1837
  return binding?.connectionScope === "supervision" && !binding.pendingSupervisionBranch;
1829
1838
  }
1830
- /** Pinned serde omits false deferLoading; every other declaration byte remains native-owned. */
1839
+ /** Pinned serde omits empty catalogs and false deferLoading; declarations remain native-owned. */
1831
1840
  function parseCodexNativeToolCatalog(metadata, threadId, fingerprint) {
1832
1841
  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();
1842
+ if (!isJsonObject(metadata) || metadata.id !== threadId) throw fail();
1843
+ const catalog = metadata.dynamic_tools ?? [];
1844
+ if (!Array.isArray(catalog) || Buffer.byteLength(JSON.stringify(catalog)) > 1048576) throw fail();
1834
1845
  const names = /* @__PURE__ */ new Set();
1835
1846
  const namespaces = /* @__PURE__ */ new Set();
1836
1847
  const validName = (name) => typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/u.test(name);
@@ -1851,7 +1862,7 @@ function parseCodexNativeToolCatalog(metadata, threadId, fingerprint) {
1851
1862
  ...value.deferLoading === true ? { deferLoading: true } : {}
1852
1863
  };
1853
1864
  };
1854
- const tools = metadata.dynamic_tools.map((value) => {
1865
+ const tools = catalog.map((value) => {
1855
1866
  if (!isJsonObject(value) || value.type !== "namespace") return readFunction(value);
1856
1867
  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
1868
  "type",
@@ -1982,14 +1993,13 @@ async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, thread
1982
1993
  //#endregion
1983
1994
  //#region extensions/codex/src/app-server/plugin-thread-attestation.ts
1984
1995
  /**
1985
- * Confirms admitted plugin and account apps against their actual Codex thread before
1986
- * OpenClaw commits a binding or starts a turn.
1996
+ * Checks app availability and enforces restricted MCP surfaces before a turn.
1987
1997
  */
1988
1998
  /** Every admission path checks the same surface; its lifecycle owner keeps the claim fenced. */
1989
1999
  async function attestCodexThreadToolSurface(params) {
1990
2000
  params.assertCurrent();
1991
2001
  if (params.appIds.length > 0) {
1992
- await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps(params));
2002
+ await params.lifecycleTiming.measure("plugin-app-attestation", () => checkCodexThreadAppAvailability(params));
1993
2003
  params.assertCurrent();
1994
2004
  }
1995
2005
  if (params.restrictedToolSurface) {
@@ -2004,7 +2014,7 @@ var CodexPluginThreadAppAttestationError = class extends Error {
2004
2014
  }
2005
2015
  };
2006
2016
  /** Reads the existing runtime snapshot with the started thread's effective app policy. */
2007
- async function attestCodexPluginThreadApps(params) {
2017
+ async function checkCodexThreadAppAvailability(params) {
2008
2018
  const appIds = Array.from(new Set(params.appIds.filter(Boolean))).toSorted();
2009
2019
  if (appIds.length === 0) return;
2010
2020
  let response;
@@ -2014,8 +2024,10 @@ async function attestCodexPluginThreadApps(params) {
2014
2024
  forceRefresh: false
2015
2025
  }, { signal: params.signal });
2016
2026
  } catch (error) {
2027
+ params.signal?.throwIfAborted();
2017
2028
  throw new CodexPluginThreadAppAttestationError(`Codex could not confirm admitted apps for thread ${params.threadId}`, { cause: error });
2018
2029
  }
2030
+ params.signal?.throwIfAborted();
2019
2031
  const installedById = new Map(response.apps.map((app) => [app.id, app]));
2020
2032
  const failures = appIds.flatMap((appId) => {
2021
2033
  const app = installedById.get(appId);
@@ -2023,7 +2035,10 @@ async function attestCodexPluginThreadApps(params) {
2023
2035
  if (!app.enabled) return [`${appId}:disabled`];
2024
2036
  return app.callable ? [] : [`${appId}:not-callable`];
2025
2037
  });
2026
- if (failures.length > 0) throw new CodexPluginThreadAppAttestationError(`Codex thread ${params.threadId} did not expose admitted apps: ${failures.join(", ")}`);
2038
+ if (failures.length > 0) embeddedAgentLog.warn("codex apps unavailable; continuing with remaining tools", {
2039
+ threadId: params.threadId,
2040
+ failures
2041
+ });
2027
2042
  }
2028
2043
  /** Deletes a persistent pre-turn thread; ephemeral threads can only be unsubscribed. */
2029
2044
  async function discardUnattestedCodexPluginThread(params) {
@@ -2207,7 +2222,10 @@ function createCodexNativeHookRelay(params) {
2207
2222
  }),
2208
2223
  signal: params.signal,
2209
2224
  runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
2210
- assertActive: params.hostCapabilities.assertActive,
2225
+ assertActive: () => {
2226
+ params.hostCapabilities.assertActive();
2227
+ params.assertCurrent?.();
2228
+ },
2211
2229
  retention: {
2212
2230
  readClaim: readCodexNativeChildThreadId,
2213
2231
  shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
@@ -2686,6 +2704,7 @@ function resolveCodexWebSearchPlan(params) {
2686
2704
  const CODEX_CODE_MODE_THREAD_CONFIG = {
2687
2705
  "features.code_mode": true,
2688
2706
  "features.code_mode_only": false,
2707
+ "features.shell_tool": true,
2689
2708
  "features.apply_patch_streaming_events": true,
2690
2709
  suppress_unstable_features_warning: true
2691
2710
  };
@@ -2712,6 +2731,7 @@ const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
2712
2731
  "code_mode",
2713
2732
  "code_mode_only",
2714
2733
  "computer_use",
2734
+ "context_management",
2715
2735
  "current_time_reminder",
2716
2736
  "default_mode_request_user_input",
2717
2737
  "deferred_executor",
@@ -2764,15 +2784,6 @@ const CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES = /* @__PURE__ */ new Map([
2764
2784
  ["telepathy", "chronicle"],
2765
2785
  ["codex_hooks", "hooks"]
2766
2786
  ]);
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
2787
  /** Common deterministic start/resume/fork fields; no run resources or unsupported setters. */
2777
2788
  function buildCodexThreadConfiguration(params, options) {
2778
2789
  return {
@@ -2926,17 +2937,16 @@ function buildRestrictedToolConfigPatch(inheritedMcpServerNames, scheduledAppAut
2926
2937
  ...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
2927
2938
  };
2928
2939
  }
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");
2940
+ async function readCodexInheritedMcpServerNames(client, cwd, signal, effectiveConfig) {
2941
+ const response = effectiveConfig ?? await readCodexEffectiveConfig(client, cwd, signal);
2935
2942
  if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
2936
2943
  for (const layer of response.layers) {
2937
2944
  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}`);
2945
+ if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") {
2946
+ 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";
2947
+ throw new Error(`Codex restricted tool surface cannot override config layer ${layer.name.type}; ${migrationGuidance}.`);
2948
+ }
2949
+ 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
2950
  }
2941
2951
  const configuredServers = response.config.mcp_servers;
2942
2952
  if (configuredServers === void 0) return [];
@@ -2968,6 +2978,7 @@ async function assertCodexManagedRequirementsDoNotOverrideToolPolicy(client, opt
2968
2978
  for (const [feature, enabled] of Object.entries(featureRequirements)) {
2969
2979
  if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
2970
2980
  const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
2981
+ 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
2982
  const deniedByToolPolicy = options.restrictedToolSurface && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature) || additionalDeniedFeatures.has(canonicalFeature);
2972
2983
  if (canonicalFeature === "hooks" && managedHooksAllowed) continue;
2973
2984
  if (enabled && deniedByToolPolicy) throw new Error(`Codex tool policy cannot override required feature ${feature}`);
@@ -3262,11 +3273,6 @@ function appApprovalCeiling(mode) {
3262
3273
  if (mode === "allow") return "approve";
3263
3274
  return mode === "ask" ? "prompt" : "auto";
3264
3275
  }
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
3276
  /** Intersects a stored app-ID cap with current policy without admitting new apps. */
3271
3277
  function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authority, currentPolicy = {
3272
3278
  config: {},
@@ -3296,13 +3302,17 @@ function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authorit
3296
3302
  };
3297
3303
  }
3298
3304
  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);
3305
+ const configPatch = disableUnlistedCodexApps(buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext), currentPolicy.config);
3300
3306
  const appsPatch = asOptionalRecord(configPatch.apps);
3301
3307
  for (const [appId, captured] of capturedById) {
3302
3308
  const appPatch = asOptionalRecord(appsPatch?.[appId]);
3303
3309
  if (!appPatch || !Object.hasOwn(apps, appId)) continue;
3304
3310
  const currentApp = apps[appId];
3305
3311
  if (!currentApp) continue;
3312
+ if (currentApp.destructiveApprovalMode === "ask") Object.assign(appPatch, buildCodexAppApprovalOverrides(currentPolicy.config, {
3313
+ id: appId,
3314
+ approvalOverrideToolConfigKeys: []
3315
+ }));
3306
3316
  const storedAppCeiling = appApprovalCeiling(captured.destructiveApprovalMode);
3307
3317
  const currentAppCeiling = appApprovalCeiling(defaultApprovalMode(currentApp));
3308
3318
  const tools = currentPolicy.toolsByApp.get(appId) ?? /* @__PURE__ */ new Map();
@@ -3315,7 +3325,7 @@ function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authorit
3315
3325
  }];
3316
3326
  }));
3317
3327
  }
3318
- const fingerprint = crypto.createHash("sha256").update(stableStringify({
3328
+ const fingerprint = crypto.createHash("sha256").update(stringifyCodexPluginPolicy({
3319
3329
  version: 1,
3320
3330
  namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
3321
3331
  authority: scheduled,
@@ -3355,7 +3365,7 @@ function buildLegacyScheduledCodexAppRecoveryPrompt(params) {
3355
3365
  function buildScheduledCodexAppAuthorityInputFingerprint(baseFingerprint, authority) {
3356
3366
  const scheduled = parseScheduledCodexAppAuthority(authority);
3357
3367
  if (!scheduled) return baseFingerprint;
3358
- return crypto.createHash("sha256").update(stableStringify({
3368
+ return crypto.createHash("sha256").update(stringifyCodexPluginPolicy({
3359
3369
  version: 1,
3360
3370
  namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
3361
3371
  baseFingerprint,
@@ -3367,8 +3377,9 @@ function buildScheduledCodexAppAuthorityInputFingerprint(baseFingerprint, author
3367
3377
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
3368
3378
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
3369
3379
  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;
3380
+ const detailed = options.enabled || options.log?.isEnabled?.("trace");
3381
+ const totalThresholdMs = options.totalThresholdMs ?? (detailed ? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS : 1e4);
3382
+ const stageThresholdMs = options.stageThresholdMs ?? (detailed ? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS : 5e3);
3372
3383
  return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
3373
3384
  }
3374
3385
  function formatCodexThreadLifecycleTimingSummary(params) {
@@ -3377,16 +3388,6 @@ function formatCodexThreadLifecycleTimingSummary(params) {
3377
3388
  }
3378
3389
  function createCodexThreadLifecycleTimingTracker(options = {}) {
3379
3390
  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
3391
  const now = options.now ?? Date.now;
3391
3392
  const startedAt = now();
3392
3393
  let didLog = false;
@@ -3427,7 +3428,10 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
3427
3428
  logSummary(params) {
3428
3429
  if (didLog) return;
3429
3430
  const summary = snapshot();
3430
- const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
3431
+ const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, {
3432
+ ...options,
3433
+ log
3434
+ });
3431
3435
  if (!shouldWarn && !log.isEnabled?.("trace")) return;
3432
3436
  didLog = true;
3433
3437
  const message = formatCodexThreadLifecycleTimingSummary({
@@ -3459,10 +3463,10 @@ function resolveCodexThreadAgentDir(params) {
3459
3463
  sessionKey: params.params.sessionKey,
3460
3464
  agentId: params.agentId ?? params.params.agentId
3461
3465
  }).sessionAgentId;
3462
- return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
3466
+ return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$2(params.params.config ?? {}, agentId);
3463
3467
  }
3464
3468
  async function prepareCodexThreadLifecyclePreflight(params) {
3465
- await assertCodexModelBackedReviewerEffectiveConfig({
3469
+ let effectiveConfig = await assertCodexModelBackedReviewerEffectiveConfig({
3466
3470
  client: params.client,
3467
3471
  approvalsReviewer: params.appServer.approvalsReviewer,
3468
3472
  cwd: params.cwd,
@@ -3518,13 +3522,17 @@ async function prepareCodexThreadLifecyclePreflight(params) {
3518
3522
  const allowConfiguredManagedHooks = params.params.pluginHarnessToolPolicyRestricted === true && !ringZeroActive && !messageOnlySourceReply && params.params.scheduledRuntimeAuthority === void 0;
3519
3523
  const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
3520
3524
  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, {
3525
+ if ((restrictedToolSurface || params.nativeCodeModeEnabled !== false) && !effectiveConfig) effectiveConfig = await lifecycleTiming.measure("tool-policy-config-read", () => readCodexEffectiveConfig(params.client, params.cwd, params.signal));
3526
+ const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-policy", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal, effectiveConfig)) : [];
3527
+ if (restrictedToolSurface || imageGenerationDenied || params.nativeCodeModeEnabled !== false) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
3523
3528
  restrictedToolSurface,
3529
+ requiredNativeShell: params.nativeCodeModeEnabled !== false,
3524
3530
  additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0,
3525
3531
  allowedManagedRequirementsFingerprint: readScheduledCodexAppManagedRequirementsFingerprint(params.params.scheduledRuntimeAuthority),
3526
3532
  allowConfiguredManagedHooks
3527
3533
  }, params.signal));
3534
+ const features = effectiveConfig?.config.features;
3535
+ 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
3536
  const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
3529
3537
  version: 1,
3530
3538
  baseInstructions: "",
@@ -3553,71 +3561,26 @@ async function prepareCodexThreadLifecyclePreflight(params) {
3553
3561
  };
3554
3562
  }
3555
3563
  //#endregion
3556
- //#region extensions/codex/src/app-server/thread-lifecycle-result.ts
3564
+ //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
3557
3565
  function resolveCodexThreadRolloutPath(thread) {
3558
3566
  const rolloutPath = thread.path?.trim();
3559
3567
  if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
3560
3568
  return rolloutPath;
3561
3569
  }
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
3570
  async function resumeExistingCodexThread(params, context) {
3608
3571
  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;
3572
+ let acceptedConfiguration;
3573
+ let disposeConfiguration;
3609
3574
  let resumeReservation;
3610
- let resumeResponseAccepted = false;
3611
3575
  let ordinaryAppConfigChanged = false;
3612
3576
  let policyOutcome = "not-written";
3613
- const assertHandoffCurrent = () => {
3614
- params.params.hostCapabilities.assertActive();
3615
- throwIfAborted();
3616
- context.assertResumeOwnership();
3617
- context.assertResumeConfiguration();
3618
- };
3619
3577
  const abandonClient = params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client));
3620
3578
  try {
3579
+ const configuration = await context.prepareResume();
3580
+ const assertHandoffCurrent = configuration.assertConfigured;
3581
+ disposeConfiguration = configuration.dispose;
3582
+ await context.releaseRetainedThread(configuration.assertCurrent);
3583
+ configuration.assertCurrent();
3621
3584
  const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
3622
3585
  const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
3623
3586
  action: "resume",
@@ -3626,9 +3589,8 @@ async function resumeExistingCodexThread(params, context) {
3626
3589
  configPatch: params.finalConfigPatch,
3627
3590
  nativeHookRelayGeneration: params.nativeHookRelayGeneration
3628
3591
  };
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);
3592
+ const pluginThreadConfig = context.prebuiltPluginThreadConfig ?? (params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0);
3593
+ const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
3632
3594
  const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
3633
3595
  threadId: resumeBinding.threadId,
3634
3596
  cwd: params.cwd,
@@ -3657,11 +3619,11 @@ async function resumeExistingCodexThread(params, context) {
3657
3619
  abandonClient,
3658
3620
  request: resumeParams,
3659
3621
  signal: params.signal,
3660
- assertCurrent: context.assertResumeOwnership
3622
+ assertCurrent: configuration.assertCurrent
3661
3623
  }));
3662
- resumeResponseAccepted = true;
3624
+ acceptedConfiguration = configuration;
3663
3625
  assertCodexThreadAcceptsDirectInput(response.thread);
3664
- context.assertResumeConfiguration();
3626
+ configuration.assertConfigured();
3665
3627
  const loadedPluginThreadConfig = await context.buildLoadedPluginThreadConfig?.(resumeBinding);
3666
3628
  if (loadedPluginThreadConfig && loadedPluginThreadConfig.fingerprint !== (pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint)) {
3667
3629
  ordinaryAppConfigChanged = resumeBinding.connectionScope !== "supervision" && !resumeBinding.pendingResumeConfiguration;
@@ -3760,13 +3722,13 @@ async function resumeExistingCodexThread(params, context) {
3760
3722
  };
3761
3723
  } catch (error) {
3762
3724
  resumeReservation?.release();
3763
- if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
3764
- if (resumeResponseAccepted) {
3725
+ if (!acceptedConfiguration && (!(error instanceof CodexAppServerRpcError) || error.method === "thread/read" && !isCodexThreadReadMissingError(error, resumeBinding.threadId) || isCodexAppServerOverloadError(error))) throw error;
3726
+ if (acceptedConfiguration) {
3765
3727
  const handoffError = error instanceof CodexThreadPolicyHandoffError || error instanceof CodexAppServerUnsafeSubscriptionError ? error : new CodexThreadPolicyHandoffError(policyOutcome, error);
3766
3728
  const subscriptionReleased = await unsubscribeCodexThreadBestEffort(params.client, {
3767
3729
  threadId: resumeBinding.threadId,
3768
3730
  timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,
3769
- assertCurrent: context.assertResumeOwnership
3731
+ assertCurrent: acceptedConfiguration.assertCurrent
3770
3732
  }).catch(() => false);
3771
3733
  if (!subscriptionReleased || handoffError instanceof CodexThreadPolicyHandoffError && handoffError.outcome === "unknown") {
3772
3734
  if (resumeBinding.connectionScope === "supervision") await retireUnsafeCodexTurnClientBestEffort(params.client, "session policy handoff");
@@ -3777,11 +3739,13 @@ async function resumeExistingCodexThread(params, context) {
3777
3739
  }
3778
3740
  }
3779
3741
  if (!ordinaryAppConfigChanged || !subscriptionReleased) throw handoffError;
3780
- assertHandoffCurrent();
3742
+ acceptedConfiguration.assertConfigured();
3781
3743
  }
3782
- if (resumeBinding.pendingResumeConfiguration || resumeBinding.connectionScope === "supervision" || params.signal?.aborted) throw error;
3744
+ if (resumeBinding.pendingResumeConfiguration || resumeBinding.preserveNativeModel || resumeBinding.connectionScope === "supervision" || params.signal?.aborted) throw error;
3783
3745
  embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
3784
3746
  await clearCurrentBinding("rotating a stale thread binding");
3747
+ } finally {
3748
+ disposeConfiguration?.();
3785
3749
  }
3786
3750
  }
3787
3751
  async function startFreshCodexThread(params, context) {
@@ -3812,9 +3776,18 @@ async function startFreshCodexThread(params, context) {
3812
3776
  disableLoginShell: params.disableLoginShell
3813
3777
  }));
3814
3778
  const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
3779
+ const assertCurrent = () => {
3780
+ throwIfAborted();
3781
+ params.params.hostCapabilities.assertActive();
3782
+ params.assertCurrent?.();
3783
+ };
3815
3784
  const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
3816
3785
  try {
3817
- return await params.client.request("thread/start", startParams, { signal: params.signal });
3786
+ assertCurrent();
3787
+ return await params.client.request("thread/start", startParams, {
3788
+ signal: params.signal,
3789
+ assertCurrent
3790
+ });
3818
3791
  } catch (error) {
3819
3792
  if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
3820
3793
  throw error;
@@ -3822,10 +3795,6 @@ async function startFreshCodexThread(params, context) {
3822
3795
  });
3823
3796
  const response = assertCodexThreadStartResponse(threadStartResponse);
3824
3797
  const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
3825
- const assertCurrent = () => {
3826
- throwIfAborted();
3827
- params.params.hostCapabilities.assertActive();
3828
- };
3829
3798
  const rejectUncommittedThread = async (cause) => {
3830
3799
  if (!await discardUnattestedCodexPluginThread({
3831
3800
  client: params.client,
@@ -3862,35 +3831,38 @@ async function startFreshCodexThread(params, context) {
3862
3831
  });
3863
3832
  const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
3864
3833
  const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
3834
+ const startedBinding = {
3835
+ threadId: response.thread.id,
3836
+ ...clientId ? { clientId } : {},
3837
+ cwd: params.cwd,
3838
+ ...rolloutPath ? { rolloutPath } : {},
3839
+ authProfileId: params.params.authProfileId,
3840
+ agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
3841
+ model: response.model ?? startParams.model ?? params.params.modelId,
3842
+ modelProvider: bindingModelProvider,
3843
+ dynamicToolsFingerprint,
3844
+ dynamicToolsContainDeferred,
3845
+ nativeSkillIsolationFingerprint,
3846
+ userMcpServersFingerprint,
3847
+ mcpServersFingerprint: nextMcpServersFingerprint,
3848
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
3849
+ ringZeroConfigFingerprint,
3850
+ ringZeroClientInstanceId,
3851
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
3852
+ networkProxyConfigFingerprint,
3853
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
3854
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
3855
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
3856
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
3857
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
3858
+ contextEngine: contextEngineBinding,
3859
+ environmentSelectionFingerprint
3860
+ };
3865
3861
  if (!preserveExistingBinding) {
3866
3862
  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,
3863
+ ...startedBinding,
3877
3864
  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
3865
+ nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0
3894
3866
  };
3895
3867
  const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
3896
3868
  let committed;
@@ -3931,20 +3903,22 @@ async function startFreshCodexThread(params, context) {
3931
3903
  threadId: response.thread.id,
3932
3904
  action: rotatedContextEngineBinding ? "rotated" : "started"
3933
3905
  });
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
- });
3906
+ return {
3907
+ ...startedBinding,
3908
+ modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
3909
+ ...startParams.ephemeral ? { liveThreadEphemeralPolicy: startParams.developerInstructions } : {},
3910
+ ...!preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
3911
+ ...startParams,
3912
+ model: response.model ?? startParams.model ?? null,
3913
+ requestedModel: startParams.model ?? null,
3914
+ modelProvider: bindingModelProvider ?? null,
3915
+ requestedModelProvider: startParams.modelProvider ?? bindingModelProvider ?? null
3916
+ }, params.params.authProfileId, dynamicToolsFingerprint) } : {},
3917
+ lifecycle: {
3918
+ action: "started",
3919
+ ...rotatedContextEngineBinding ? { rotatedContextEngineBinding: true } : {}
3920
+ }
3921
+ };
3948
3922
  }
3949
3923
  //#endregion
3950
3924
  //#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
@@ -4032,6 +4006,7 @@ async function tryReuseCodexLiveThread(options) {
4032
4006
  try {
4033
4007
  retainedThread.assertCurrent();
4034
4008
  params.params.hostCapabilities.assertActive();
4009
+ params.assertCurrent?.();
4035
4010
  } catch (cause) {
4036
4011
  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
4012
  }
@@ -4163,11 +4138,14 @@ async function tryReuseCodexLiveThread(options) {
4163
4138
  //#endregion
4164
4139
  //#region extensions/codex/src/app-server/thread-lifecycle-adoption.ts
4165
4140
  /** Passive refusal must precede releasing or acquiring any native subscription. */
4166
- async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context) {
4141
+ async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context, assertCurrent) {
4167
4142
  const { thread } = await context.lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
4168
4143
  threadId,
4169
4144
  includeTurns: false
4170
- }, { signal: params.signal }));
4145
+ }, {
4146
+ signal: params.signal,
4147
+ assertCurrent
4148
+ }));
4171
4149
  context.throwIfAborted();
4172
4150
  assertCodexThreadAcceptsDirectInput(thread);
4173
4151
  if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
@@ -4182,13 +4160,25 @@ async function withCodexThreadLifecycleBinding(params, run) {
4182
4160
  agentId: params.agentId ?? params.params.agentId,
4183
4161
  config: params.params.config
4184
4162
  });
4185
- const snapshot = await params.bindingStore.read(identity);
4163
+ const { binding: snapshot, assertCurrent } = await resolveCodexSessionBinding({
4164
+ reclaimStale: true,
4165
+ bindingStore: params.bindingStore,
4166
+ identity,
4167
+ config: params.params.config,
4168
+ storePath: params.params.sessionTarget?.storePath,
4169
+ assertCurrent: () => {
4170
+ params.params.hostCapabilities.assertActive();
4171
+ params.assertCurrent?.();
4172
+ },
4173
+ signal: params.signal,
4174
+ assertBinding: params.params.expectedSessionRuntimeOwnership ? (binding) => assertCodexSessionRuntimeOwnership(binding, params.params.expectedSessionRuntimeOwnership) : void 0
4175
+ });
4186
4176
  const runWithLease = () => params.bindingStore.withLease(identity, async () => {
4187
- const binding = await params.bindingStore.read(identity);
4177
+ const binding = params.bindingStore.read(identity);
4178
+ assertCodexSessionRuntimeOwnership(binding, params.params.expectedSessionRuntimeOwnership);
4188
4179
  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);
4180
+ assertCurrent();
4181
+ return await run(identity, binding, assertCurrent);
4192
4182
  });
4193
4183
  return snapshot?.pendingResumeConfiguration ? await withExclusiveCodexAppServerThread({
4194
4184
  bindingStore: params.bindingStore,
@@ -4206,32 +4196,27 @@ async function resumePendingCodexThread(params, context) {
4206
4196
  })) 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
4197
  const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
4208
4198
  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
- });
4199
+ const resumed = await resumeExistingCodexThread(params, {
4200
+ ...context,
4201
+ prebuiltPluginThreadConfig,
4202
+ prepareResume: () => preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint),
4203
+ releaseRetainedThread: async (assertCurrent) => {
4204
+ const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
4205
+ assertCurrent();
4206
+ if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
4207
+ client: params.client,
4208
+ abandonClient: params.abandonClient,
4209
+ lifecycleTiming,
4210
+ threadId: binding.threadId,
4211
+ assertCurrent
4212
+ });
4213
+ }
4219
4214
  });
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
- }
4215
+ if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
4216
+ return resumed;
4232
4217
  }
4233
4218
  /** Manual attachment is intent, never evidence that loaded native overrides took effect. */
4234
- async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
4219
+ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint) {
4235
4220
  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
4221
  const agentDir = resolveCodexThreadAgentDir(params);
4237
4222
  const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
@@ -4240,6 +4225,7 @@ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFing
4240
4225
  const assertClient = captureCodexAppServerClientLifetime(params.client, "native-process");
4241
4226
  const assertCurrent = () => {
4242
4227
  params.params.hostCapabilities.assertActive();
4228
+ params.assertCurrent?.();
4243
4229
  params.signal?.throwIfAborted();
4244
4230
  assertClient();
4245
4231
  if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
@@ -4248,7 +4234,10 @@ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFing
4248
4234
  const { thread } = await params.client.request("thread/read", {
4249
4235
  threadId: binding.threadId,
4250
4236
  includeTurns: false
4251
- }, { signal: params.signal });
4237
+ }, {
4238
+ signal: params.signal,
4239
+ assertCurrent
4240
+ });
4252
4241
  assertCurrent();
4253
4242
  const statusType = thread.status?.type;
4254
4243
  if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
@@ -4262,8 +4251,6 @@ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFing
4262
4251
  const recordedTools = metadata.dynamic_tools ?? [];
4263
4252
  if (!Array.isArray(recordedTools) || codexDynamicToolsFingerprint(recordedTools) !== dynamicToolsFingerprint) throw fail("its immutable native tool catalog does not match the current OpenClaw tools");
4264
4253
  assertCurrent();
4265
- await releaseSubscription(assertCurrent);
4266
- assertCurrent();
4267
4254
  return {
4268
4255
  assertConfigured: observation.assertConfigured,
4269
4256
  assertCurrent,
@@ -4279,13 +4266,18 @@ async function prepareCodexThreadResume(params, binding, context) {
4279
4266
  const assertClient = captureCodexAppServerClientLifetime(params.client, binding.connectionScope === "supervision" ? "connection" : "native-process");
4280
4267
  const assertCurrent = () => {
4281
4268
  params.params.hostCapabilities.assertActive();
4269
+ params.assertCurrent?.();
4282
4270
  params.signal?.throwIfAborted();
4283
4271
  assertClient();
4284
4272
  if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
4285
4273
  };
4286
4274
  assertCurrent();
4287
- const thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context);
4288
- assertCurrent();
4275
+ let thread;
4276
+ try {
4277
+ thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context, assertCurrent);
4278
+ } finally {
4279
+ assertCurrent();
4280
+ }
4289
4281
  assertCodexSupervisionThreadLineage(binding, thread);
4290
4282
  return {
4291
4283
  ...observeCodexThreadConfiguration(params, thread, assertCurrent),
@@ -4309,6 +4301,10 @@ function observeCodexThreadConfiguration(params, thread, assertCurrent) {
4309
4301
  //#region extensions/codex/src/app-server/thread-supervision.ts
4310
4302
  async function materializePendingSupervisionBranch(params) {
4311
4303
  let pending = params.binding.pendingSupervisionBranch;
4304
+ const requestOptions = {
4305
+ signal: params.signal,
4306
+ assertCurrent: params.throwIfAborted
4307
+ };
4312
4308
  const connectionFingerprint = buildCodexAppServerConnectionFingerprint(params.appServer, params.attempt.agentDir);
4313
4309
  if (!pending.connectionFingerprint || pending.connectionFingerprint !== connectionFingerprint) throw new Error("Codex supervision source connection changed before branch materialization");
4314
4310
  pending = await recoverPendingSupervisionArtifacts(params, pending);
@@ -4316,7 +4312,7 @@ async function materializePendingSupervisionBranch(params) {
4316
4312
  const sourceResponse = await params.lifecycleTiming.measure("supervision-source-read", () => params.client.request("thread/read", {
4317
4313
  threadId: pending.sourceThreadId,
4318
4314
  includeTurns: true
4319
- }, { signal: params.signal }));
4315
+ }, requestOptions));
4320
4316
  params.throwIfAborted();
4321
4317
  const sourceThread = sourceResponse.thread;
4322
4318
  if (sourceThread.id !== pending.sourceThreadId) throw new Error(`Codex supervision source read returned ${sourceThread.id} for ${pending.sourceThreadId}`);
@@ -4342,7 +4338,7 @@ async function materializePendingSupervisionBranch(params) {
4342
4338
  });
4343
4339
  } catch (error) {
4344
4340
  try {
4345
- const current = await params.bindingStore.read(params.bindingIdentity);
4341
+ const current = params.bindingStore.read(params.bindingIdentity);
4346
4342
  if (matchesPendingSupervisionState(current, pending)) cleanupExpected = pending;
4347
4343
  else if (matchesPendingSupervisionState(current, expected)) cleanupExpected = expected;
4348
4344
  else throw new CodexThreadBindingConflictError(pending.sourceThreadId, "verifying supervised Codex cleanup tracking");
@@ -4359,7 +4355,7 @@ async function materializePendingSupervisionBranch(params) {
4359
4355
  const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
4360
4356
  const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
4361
4357
  try {
4362
- return await params.client.request("thread/fork", probeParams, { signal: params.signal });
4358
+ return await params.client.request("thread/fork", probeParams, requestOptions);
4363
4359
  } catch (error) {
4364
4360
  if (!(error instanceof CodexAppServerRpcError)) throw new CodexAppServerUnsafeSubscriptionError("Codex model probe fork may have materialized without a response", { cause: error });
4365
4361
  throw error;
@@ -4414,7 +4410,7 @@ async function materializePendingSupervisionBranch(params) {
4414
4410
  });
4415
4411
  const rawStartResponse = await params.lifecycleTiming.measure("supervision-thread-start", async () => {
4416
4412
  try {
4417
- return await params.client.request("thread/start", startParams, { signal: params.signal });
4413
+ return await params.client.request("thread/start", startParams, requestOptions);
4418
4414
  } catch (error) {
4419
4415
  if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
4420
4416
  throw new CodexAppServerUnsafeSubscriptionError("Canonical Codex branch may have started without a response", { cause: error });
@@ -4435,7 +4431,7 @@ async function materializePendingSupervisionBranch(params) {
4435
4431
  });
4436
4432
  if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, finalThreadId, startParams.config, params.signal));
4437
4433
  if (params.provisionalAppIds?.length) try {
4438
- await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
4434
+ await params.lifecycleTiming.measure("plugin-app-attestation", () => checkCodexThreadAppAvailability({
4439
4435
  client: params.client,
4440
4436
  threadId: finalThreadId,
4441
4437
  appIds: params.provisionalAppIds ?? [],
@@ -4457,7 +4453,7 @@ async function materializePendingSupervisionBranch(params) {
4457
4453
  await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
4458
4454
  threadId: finalThreadId,
4459
4455
  items: history.responseItems
4460
- }, { signal: params.signal }));
4456
+ }, requestOptions));
4461
4457
  params.throwIfAborted();
4462
4458
  }
4463
4459
  const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
@@ -4474,11 +4470,11 @@ async function materializePendingSupervisionBranch(params) {
4474
4470
  modelProvider: bindingModelProvider,
4475
4471
  historyCoveredThrough
4476
4472
  }
4477
- });
4473
+ }, params.throwIfAborted);
4478
4474
  } catch (error) {
4479
4475
  let current;
4480
4476
  try {
4481
- current = await params.bindingStore.read(params.bindingIdentity);
4477
+ current = params.bindingStore.read(params.bindingIdentity);
4482
4478
  } catch (readError) {
4483
4479
  provisionalCleanupSafe = false;
4484
4480
  throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding could not be verified: ${finalThreadId}`, { cause: new AggregateError([error, readError]) });
@@ -4669,11 +4665,16 @@ async function archiveSupervisionArtifact(client, threadId) {
4669
4665
  }
4670
4666
  //#endregion
4671
4667
  //#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;
4668
+ async function startOrResumeThread(input) {
4669
+ const incognito = isIncognitoSessionKey(input.params.sessionKey);
4670
+ const clientId = resolveCodexAppServerClientInstanceId(input.client);
4671
+ return await withCodexThreadLifecycleBinding(input, async (bindingIdentity, saved, assert) => {
4672
+ const params = {
4673
+ ...input,
4674
+ assertCurrent: assert
4675
+ };
4676
+ const expectedOwnership = params.params.expectedSessionRuntimeOwnership;
4677
+ let binding = saved;
4677
4678
  if (hasCodexNativeToolCatalog(binding)) {
4678
4679
  const nativeCatalog = await loadCodexNativeToolCatalog({
4679
4680
  client: params.client,
@@ -4682,7 +4683,7 @@ async function startOrResumeThread(params) {
4682
4683
  agentDir: resolveCodexThreadAgentDir(params),
4683
4684
  assertCurrent: () => {
4684
4685
  params.signal?.throwIfAborted();
4685
- params.params.hostCapabilities.assertActive();
4686
+ assert();
4686
4687
  }
4687
4688
  });
4688
4689
  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 +4710,6 @@ async function startOrResumeThread(params) {
4709
4710
  threadId,
4710
4711
  assertCurrent
4711
4712
  });
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
4713
  if (binding?.pendingSupervisionBranch) {
4720
4714
  await releaseRetainedThread(binding.threadId);
4721
4715
  const pendingBinding = binding;
@@ -4749,7 +4743,10 @@ async function startOrResumeThread(params) {
4749
4743
  environmentSelection: params.environmentSelection,
4750
4744
  provisionalAppIds: pluginThreadConfig?.provisionalAppIds,
4751
4745
  signal: params.signal,
4752
- throwIfAborted,
4746
+ throwIfAborted: () => {
4747
+ throwIfAborted();
4748
+ assert();
4749
+ },
4753
4750
  lifecycleTiming,
4754
4751
  normalizeBindingModelProvider,
4755
4752
  bindingPatch: {
@@ -4781,11 +4778,11 @@ async function startOrResumeThread(params) {
4781
4778
  const clearCurrentBinding = async (operation) => {
4782
4779
  const current = binding;
4783
4780
  if (!current?.threadId) return;
4784
- assertCodexBindingMayBeReplaced(current, operation);
4781
+ assertCodexBindingMayBeReplaced(current, operation, expectedOwnership);
4785
4782
  if (!await params.bindingStore.mutate(bindingIdentity, {
4786
4783
  kind: "clear",
4787
4784
  threadId: current.threadId
4788
- })) throw new CodexThreadBindingConflictError(current.threadId, operation);
4785
+ }, assert)) throw new CodexThreadBindingConflictError(current.threadId, operation);
4789
4786
  binding = void 0;
4790
4787
  };
4791
4788
  const resolveRequestContext = () => {
@@ -4879,14 +4876,14 @@ async function startOrResumeThread(params) {
4879
4876
  const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
4880
4877
  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
4878
  const predecessorBinding = binding;
4882
- assertCodexBindingMayBeReplaced(predecessorBinding, "changing configured MCP ownership");
4879
+ assertCodexBindingMayBeReplaced(predecessorBinding, "changing configured MCP ownership", expectedOwnership);
4883
4880
  embeddedAgentLog.debug("codex app-server configured MCP ownership changed; starting a new thread", { threadId: predecessorBinding.threadId });
4884
4881
  replacementPredecessor = predecessorBinding;
4885
4882
  binding = void 0;
4886
4883
  preserveExistingBinding = false;
4887
4884
  }
4888
4885
  if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
4889
- assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
4886
+ assertCodexBindingMayBeReplaced(binding, "changing MCP configuration", expectedOwnership);
4890
4887
  if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
4891
4888
  embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
4892
4889
  preserveExistingBinding = true;
@@ -4898,7 +4895,7 @@ async function startOrResumeThread(params) {
4898
4895
  }
4899
4896
  const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
4900
4897
  if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
4901
- assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
4898
+ assertCodexBindingMayBeReplaced(binding, "changing web-search configuration", expectedOwnership);
4902
4899
  if (!ringZeroActive && transientWebSearchRestriction) {
4903
4900
  embeddedAgentLog.debug("codex app-server tool surface restricted for turn; starting transient thread", { threadId: binding.threadId });
4904
4901
  preserveExistingBinding = true;
@@ -4909,13 +4906,13 @@ async function startOrResumeThread(params) {
4909
4906
  binding = void 0;
4910
4907
  }
4911
4908
  if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
4912
- assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
4909
+ assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn", expectedOwnership);
4913
4910
  embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
4914
4911
  preserveExistingBinding = true;
4915
4912
  binding = void 0;
4916
4913
  }
4917
4914
  if (binding?.threadId && transientDelegationRestriction) {
4918
- assertCodexBindingMayBeReplaced(binding, "starting a delegation-restricted turn");
4915
+ assertCodexBindingMayBeReplaced(binding, "starting a delegation-restricted turn", expectedOwnership);
4919
4916
  embeddedAgentLog.debug("codex app-server delegation restricted for turn; starting transient thread", { threadId: binding.threadId });
4920
4917
  binding = void 0;
4921
4918
  }
@@ -4973,7 +4970,7 @@ async function startOrResumeThread(params) {
4973
4970
  }
4974
4971
  if (binding?.threadId) {
4975
4972
  if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
4976
- assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
4973
+ assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog", expectedOwnership);
4977
4974
  preserveExistingBinding = shouldStartTransientNoToolThread({
4978
4975
  previous: binding.dynamicToolsFingerprint,
4979
4976
  nextHasDynamicTools: params.dynamicTools.length > 0
@@ -4996,27 +4993,24 @@ async function startOrResumeThread(params) {
4996
4993
  throwIfAborted();
4997
4994
  await clearCurrentBinding(incognito ? "rotating an unavailable ephemeral thread binding" : "rotating a stale plugin app binding");
4998
4995
  } 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
- }
4996
+ const resumeBinding = binding;
4997
+ const resumed = await resumeExistingCodexThread(params, {
4998
+ ...requestContext,
4999
+ binding: resumeBinding,
5000
+ clearCurrentBinding,
5001
+ prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
5002
+ prebuiltPluginThreadConfig,
5003
+ buildLoadedPluginThreadConfig,
5004
+ prepareResume: () => prepareCodexThreadResume(params, resumeBinding, requestContext),
5005
+ releaseRetainedThread: async (assertCurrent) => {
5006
+ await releaseRetainedThread(resumeBinding.threadId, resumeBinding.clientId, assertCurrent);
5007
+ }
5008
+ });
5009
+ if (resumed) return resumed;
5017
5010
  }
5018
5011
  }
5019
5012
  }
5013
+ assertCodexBindingMayBeReplaced(binding, "starting a fresh native thread", expectedOwnership);
5020
5014
  if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
5021
5015
  const started = await startFreshCodexThread(params, {
5022
5016
  ...requestContext,
@@ -5231,4 +5225,4 @@ function joinPresentSections(...sections) {
5231
5225
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
5232
5226
  }
5233
5227
  //#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 };
5228
+ export { resolveCodexNativeSkillIsolation as $, readCodexMcpToolConnectorId as A, refreshCodexPluginRuntimeState as At, emitCodexNativePreToolUseFailureDiagnostic as B, buildDeveloperInstructions as C, buildCodexPluginThreadConfigInputFingerprint as Ct, resolveCodexAppServerThreadModelSelection as D, refreshCodexPluginAppApprovalPolicy as Dt, resolveCodexAppServerRequestModelSelection as E, mergeCodexThreadConfigs 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, buildCodexPluginThreadConfig as St, resolveCodexAppServerModelProvider as T, buildDisabledAppsConfigPatch as Tt, scheduleCodexNativeHookRelayUnregister as U, resolveCodexNativeHookRelayEvents as V, checkCodexThreadAppAvailability 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, buildCodexPluginThreadConfigTimeoutFallback as wt, buildCodexNativeWebSearchThreadConfig as x, resolveCodexContinuityProjectionMaxChars as xt, buildCodexThreadConfiguration as y, resolveCodexContextEngineProjectionMaxChars as yt, createCodexNativeHookRelay as z };