@openclaw/codex 2026.9.1-beta.1 → 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/api.js +5 -6
  2. package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-DYHcRQAa.js} +31 -45
  3. package/dist/attempt-context-DmQUNWwT.js +634 -0
  4. package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
  5. package/dist/{binding-connection-BMvPX73o.js → binding-connection-E6uDsPlx.js} +2 -2
  6. package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-wzJrwKKe.js} +15 -20
  7. package/dist/{capabilities-BRMh0aIq.js → capabilities-dIjNVCYH.js} +1 -1
  8. package/dist/cli-metadata.js +1 -1
  9. package/dist/{command-formatters-DEsIufkt.js → command-formatters-CLHhEB1Q.js} +5 -4
  10. package/dist/{command-handlers-D_-1_qRp.js → command-handlers-CTGb2csu.js} +124 -101
  11. package/dist/{compact-BglNFZrQ.js → compact-_t0HQrUS.js} +38 -29
  12. package/dist/{config-giW8X0QA.js → config-BhmfC1s6.js} +294 -282
  13. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  14. package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
  15. package/dist/doctor-contract-api.js +143 -12
  16. package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BU9tmlYm.js} +599 -655
  17. package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
  18. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
  19. package/dist/{event-projector-usage-Bk1I90Bg.js → event-projector-usage-C5mcrGjp.js} +15 -148
  20. package/dist/{harness-Csz0F-DK.js → harness-DXVZ5II5.js} +42 -52
  21. package/dist/harness.js +1 -1
  22. package/dist/index.js +458 -404
  23. package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-kg9kv4iH.js} +4 -4
  24. package/dist/launch-args-RXQwn8zV.js +90 -0
  25. package/dist/{managed-binary-DFYK4DWq.js → managed-binary-4-Vjz5GH.js} +14 -9
  26. package/dist/media-understanding-provider.js +1 -1
  27. package/dist/model-catalog-CUM68Hkl.js +101 -0
  28. package/dist/{models-TS3HBU-q.js → models-sYnEc0gd.js} +25 -28
  29. package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-CnH147jH.js} +55 -115
  30. package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-CstNHCmC.js} +9 -6
  31. package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-CNe-Xrjx.js} +30 -3
  32. package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-CfFlKqDl.js} +15 -14
  33. package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BFrPhb2c.js} +6 -6
  34. package/dist/provider-capabilities-DW5p4k9w.js +101 -0
  35. package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-CawQJfUS.js} +3 -2
  36. package/dist/{sandbox-guard-kuWTn9C5.js → request-DYAI5FQE.js} +195 -2
  37. package/dist/{run-attempt-DYc0QCBF.js → run-attempt-D1hJO4fX.js} +828 -2100
  38. package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-C3jVimFn.js} +21 -14
  39. package/dist/{session-binding-Bcynfd-_.js → session-binding-BHuTCRZQ.js} +198 -127
  40. package/dist/{session-catalog-DaLKVtpH.js → session-catalog-yyvTWFOw.js} +564 -408
  41. package/dist/{session-cli-DMc4oS2w.js → session-cli-CmFEXMAM.js} +1 -1
  42. package/dist/session-history-C5J1AKzG.js +87 -0
  43. package/dist/session-initialization-BeWzjGiR.js +74 -0
  44. package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-DeWVTCMj.js} +2 -2
  45. package/dist/session-retirement-BujDjXVF.js +97 -0
  46. package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-C351-5Qw.js} +19 -4
  47. package/dist/settled-turn-context-D3SNWtUn.js +268 -0
  48. package/dist/settled-turn-finalizer-BVJx4yoc.js +104 -0
  49. package/dist/{shared-client-DKz23hHY.js → shared-client-DxcfF9KX.js} +3480 -679
  50. package/dist/{side-question-DjA_jsNe.js → side-question-BTZAII4l.js} +108 -70
  51. package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DAcEJ_Om.js} +2188 -1915
  52. package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-zGokd8i1.js} +25 -14
  53. package/dist/thread-policy-g4KbJPzp.js +54 -0
  54. package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-C7pyVKeW.js} +59 -7
  55. package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-DTUxFWa_.js} +79 -17
  56. package/dist/transport-stdio-CyPldczm.js +726 -0
  57. package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
  58. package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
  59. package/dist/{usage-ROIaW3wo.js → usage-Bj-rrzkR.js} +3 -3
  60. package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
  61. package/openclaw.plugin.json +1 -30
  62. package/package.json +6 -6
  63. package/dist/command-rpc-CCeCkf_v.js +0 -90
  64. package/dist/incognito-session-ET_ufDH-.js +0 -2
  65. package/dist/model-catalog-i9rNBthX.js +0 -48
  66. package/dist/protocol-validators-VeJxxJ0U.js +0 -2421
  67. package/dist/request-DXXGHko0.js +0 -157
  68. package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
  69. package/dist/session-retirement-CcNC3YSY.js +0 -36
  70. package/dist/settled-turn-finalizer-Dc3ApjU4.js +0 -328
  71. package/dist/thread-resume-BE9qNEW0.js +0 -30
  72. package/dist/transport-stdio-DhoGy33Z.js +0 -99
  73. package/dist/upstream-session-fork-DlaHxSLC.js +0 -326
@@ -1,35 +1,37 @@
1
- import { _ as CODEX_PLUGINS_MARKETPLACE_NAME, b as assertCodexModelBackedReviewerEffectiveConfig, g as resolveCodexPluginsPolicy, n as codexSandboxPolicyForTurn, v as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME } from "./config-giW8X0QA.js";
2
- import { G as CodexAppServerRpcError, J as consumeCodexAppServerLiveThread, L as isCodexAppServerConnectionClosedError, P as getCodexAppServerClientInstanceId, St as resolveCodexAppServerLocalHomeDir, U as resolveCodexAppServerClientInstanceId, _t as flattenCodexDynamicToolFunctions, c as isCodexAppServerStartSelectionChangedError, h as retainSharedCodexAppServerClientByInstanceId, vt as isJsonObject } from "./shared-client-DKz23hHY.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-Bcynfd-_.js";
4
- import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, d as unsubscribeCodexThreadBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-bfsmcQ6m.js";
5
- import { a as assertCodexThreadStartResponse, r as assertCodexThreadForkResponse } from "./protocol-validators-VeJxxJ0U.js";
6
- import { o as defaultCodexAppInventoryCache, s as serializeCodexAppInventoryError, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-C1CAdfcP.js";
7
- import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
8
- import { n as codexCatalogHomeId } from "./session-catalog-home-id-BNwAJ9D7.js";
9
- import { t as resumeCodexAppServerThread } from "./thread-resume-BE9qNEW0.js";
10
- import { u as projectBoundedCodexThreadHistory } from "./transcript-mirror-DsCMWBca.js";
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";
11
9
  import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
12
10
  import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
13
11
  import fs from "node:fs/promises";
14
12
  import path from "node:path";
15
13
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
16
- import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds, parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
17
- import { SKILL_WORKSHOP_TOOL_NAME, TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, formatToolExecutionErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode, resolveToolExecutionErrorKind } from "openclaw/plugin-sdk/agent-harness-runtime";
14
+ import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
15
+ import { AgentHarnessPreflightError, SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, buildTemporalContextText, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
16
+ import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
18
17
  import * as crypto$1 from "node:crypto";
19
18
  import crypto, { createHash } from "node:crypto";
20
19
  import os from "node:os";
21
- import { resolveAgentDir as resolveAgentDir$1, resolveSessionAgentIds as resolveSessionAgentIds$1 } from "openclaw/plugin-sdk/agent-runtime";
20
+ 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";
22
23
  import { z } from "zod";
23
24
  import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
24
25
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
+ import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
27
+ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
25
28
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
26
- import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
27
29
  import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
30
+ import { buildCodexUserMcpServersThreadConfigPatchForRun } from "openclaw/plugin-sdk/codex-mcp-projection";
31
+ import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
32
+ import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-flags";
28
33
  import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
29
34
  import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
30
- import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
31
- import { emitTrustedDiagnosticEvent, hasPendingInternalDiagnosticEvent, isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
32
- import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
33
35
  //#region extensions/codex/src/app-server/plugin-inventory.ts
34
36
  /**
35
37
  * Reads Codex plugin marketplace state and app inventory to decide which
@@ -272,15 +274,34 @@ function resolveOwnedApps(params) {
272
274
  enabled: false,
273
275
  needsAuth: true
274
276
  };
275
- return {
277
+ return Object.assign({
276
278
  id: app.id,
277
279
  name: app.name,
278
280
  accessible: info.isAccessible,
279
281
  enabled: info.isEnabled,
280
282
  needsAuth: !info.isAccessible
281
- };
283
+ }, resolveOwnedAppApprovalOverrideKeys(info));
282
284
  }).toSorted((left, right) => left.id.localeCompare(right.id));
283
285
  }
286
+ /** Returns current tool keys whose overrides could bypass the requested reviewer. */
287
+ function resolveOwnedAppApprovalOverrideKeys(app) {
288
+ if (!app.toolSummaries) return {};
289
+ const appName = app.name.trim();
290
+ const appNameLower = appName.toLowerCase();
291
+ const keys = app.toolSummaries.filter((tool) => !tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
292
+ appName,
293
+ appNameLower,
294
+ tool
295
+ }));
296
+ return { approvalOverrideToolConfigKeys: Array.from(new Set(keys)).toSorted() };
297
+ }
298
+ function resolveAppToolConfigKeys(params) {
299
+ const keys = [params.tool.name];
300
+ if (params.tool.title) keys.push(params.tool.title);
301
+ if (params.appName) keys.push(`${params.appName}_${params.tool.name}`);
302
+ if (params.appNameLower && params.appNameLower !== params.appName) keys.push(`${params.appNameLower}_${params.tool.name}`);
303
+ return keys;
304
+ }
284
305
  function findPluginSummary(marketplace, pluginName) {
285
306
  const exact = marketplace.plugins.find((plugin) => plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}`);
286
307
  if (exact) return exact;
@@ -538,7 +559,8 @@ function toCodexPluginOwnedAccountApp(app) {
538
559
  name: app.name,
539
560
  accessible: app.isAccessible,
540
561
  enabled: app.isEnabled,
541
- needsAuth: !app.isAccessible
562
+ needsAuth: !app.isAccessible,
563
+ ...resolveOwnedAppApprovalOverrideKeys(app)
542
564
  };
543
565
  }
544
566
  function resolveCodexThreadConfigAppsForRecord(params) {
@@ -595,7 +617,7 @@ function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
595
617
  * Builds Codex thread config patches that expose only policy-approved apps
596
618
  * for native Codex turns.
597
619
  */
598
- const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 3;
620
+ const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 4;
599
621
  const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
600
622
  /** Returns true when plugin config exists and thread config may need app patches. */
601
623
  function shouldBuildCodexPluginThreadConfig(pluginConfig) {
@@ -968,10 +990,11 @@ function readPersistedAppToolApprovalOverrideNames(config, app) {
968
990
  const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
969
991
  const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
970
992
  if (!isJsonObject(tools)) return [];
971
- return Object.entries(tools).filter(([, value]) => hasPersistedToolApprovalOverride(value)).map(([toolName]) => toolName).toSorted();
993
+ const keys = app.approvalOverrideToolConfigKeys;
994
+ return Object.entries(tools).flatMap(([name, value]) => (!keys || keys.includes(name)) && hasPersistedToolApprovalOverride(value) ? [name] : []).toSorted();
972
995
  }
973
996
  function hasPersistedToolApprovalOverride(value) {
974
- return isJsonObject(value) && value.approval_mode !== void 0;
997
+ return isJsonObject(value) && value.approval_mode !== void 0 && value.approval_mode !== null;
975
998
  }
976
999
  function quoteConfigKeyPathSegment(segment) {
977
1000
  return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
@@ -1015,11 +1038,11 @@ function mergeJsonObjects(left, right) {
1015
1038
  return merged;
1016
1039
  }
1017
1040
  function fingerprintJson(value) {
1018
- return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
1041
+ return crypto.createHash("sha256").update(stableStringify$1(value)).digest("hex");
1019
1042
  }
1020
- function stableStringify(value) {
1021
- if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
1022
- if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
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(",")}}`;
1023
1046
  return JSON.stringify(value);
1024
1047
  }
1025
1048
  //#endregion
@@ -1170,6 +1193,7 @@ function renderMessagesForCodexContext(messages, options) {
1170
1193
  }).filter((value) => Boolean(value)).join("\n\n");
1171
1194
  }
1172
1195
  function renderMessageBody(message, options) {
1196
+ if (message.role === "compactionSummary" || message.role === "branchSummary") return truncateText(message.summary.trim(), options.maxTextPartChars);
1173
1197
  if (!hasMessageContent(message)) return "";
1174
1198
  if (typeof message.content === "string") return truncateText(message.content.trim(), options.maxTextPartChars);
1175
1199
  if (!Array.isArray(message.content)) return "[non-text content omitted]";
@@ -1638,7 +1662,8 @@ async function collectPersonalSkillRealPaths(homes, codexHome) {
1638
1662
  }
1639
1663
  const configuredCodexHome = codexHome?.trim() || process.env.CODEX_HOME?.trim();
1640
1664
  if (configuredCodexHome) {
1641
- const stateOwned = isPathInside(realStateDir, await canonicalizeExistingPath(configuredCodexHome));
1665
+ const realCodexHome = await canonicalizeExistingPath(configuredCodexHome);
1666
+ const stateOwned = isPathInside(realStateDir, realCodexHome);
1642
1667
  roots.push({
1643
1668
  dir: path.join(configuredCodexHome, "skills"),
1644
1669
  onlyEscapedStateTargets: stateOwned
@@ -1701,12 +1726,14 @@ async function collectPersonalSkillRealPaths(homes, codexHome) {
1701
1726
  try {
1702
1727
  const stat = await fs.stat(entryPath);
1703
1728
  if (entry.name === "SKILL.md" && stat.isFile()) await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
1704
- else if (stat.isDirectory()) if (current.depth < MAX_PERSONAL_SKILL_DEPTH) queue.push({
1705
- dir: entryPath,
1706
- depth: current.depth + 1,
1707
- onlyEscapedStateTargets: current.onlyEscapedStateTargets
1708
- });
1709
- else complete = false;
1729
+ else if (stat.isDirectory()) {
1730
+ if (current.depth < MAX_PERSONAL_SKILL_DEPTH) queue.push({
1731
+ dir: entryPath,
1732
+ depth: current.depth + 1,
1733
+ onlyEscapedStateTargets: current.onlyEscapedStateTargets
1734
+ });
1735
+ else complete = false;
1736
+ }
1710
1737
  } catch (error) {
1711
1738
  if (!isMissingPathError(error)) complete = false;
1712
1739
  }
@@ -1796,32 +1823,64 @@ function applyCodexNativeSkillIsolation(config, isolation) {
1796
1823
  };
1797
1824
  }
1798
1825
  //#endregion
1799
- //#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
1800
- var CodexThreadStartRequestError = class extends Error {
1801
- constructor(cause) {
1802
- super(`thread/start: ${formatErrorMessage(cause)}`, { cause });
1803
- this.name = "CodexThreadStartRequestError";
1804
- }
1805
- };
1806
- var CodexThreadBindingConflictError = class extends Error {
1807
- constructor(threadId, operation) {
1808
- super(`Codex thread binding changed while ${operation}: ${threadId}`);
1809
- this.name = "CodexThreadBindingConflictError";
1810
- }
1811
- };
1812
- var CodexRestrictedToolSurfaceAttestationError = class extends Error {
1813
- constructor(cause) {
1814
- super("Codex restricted-tool-surface MCP attestation failed", { cause });
1815
- this.name = "CodexRestrictedToolSurfaceAttestationError";
1816
- }
1817
- };
1818
- var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
1819
- var CodexAdoptedThreadActiveError = class extends Error {
1820
- constructor() {
1821
- super("Codex session became active in another runner; wait for it to finish before continuing");
1822
- this.name = "CodexAdoptedThreadActiveError";
1823
- }
1824
- };
1826
+ //#region extensions/codex/src/app-server/native-tool-catalog.ts
1827
+ function hasCodexNativeToolCatalog(binding) {
1828
+ return binding?.connectionScope === "supervision" && !binding.pendingSupervisionBranch;
1829
+ }
1830
+ /** Pinned serde omits false deferLoading; every other declaration byte remains native-owned. */
1831
+ function parseCodexNativeToolCatalog(metadata, threadId, fingerprint) {
1832
+ 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();
1834
+ const names = /* @__PURE__ */ new Set();
1835
+ const namespaces = /* @__PURE__ */ new Set();
1836
+ const validName = (name) => typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/u.test(name);
1837
+ const readFunction = (value) => {
1838
+ if (!isJsonObject(value) || value.type !== "function" || !validName(value.name) || typeof value.description !== "string" || !isJsonObject(value.inputSchema) || value.deferLoading !== void 0 && typeof value.deferLoading !== "boolean" || Object.keys(value).some((key) => ![
1839
+ "type",
1840
+ "name",
1841
+ "description",
1842
+ "inputSchema",
1843
+ "deferLoading"
1844
+ ].includes(key)) || names.has(value.name) || names.size >= 2e3) throw fail();
1845
+ names.add(value.name);
1846
+ return {
1847
+ type: "function",
1848
+ name: value.name,
1849
+ description: value.description,
1850
+ inputSchema: structuredClone(value.inputSchema),
1851
+ ...value.deferLoading === true ? { deferLoading: true } : {}
1852
+ };
1853
+ };
1854
+ const tools = metadata.dynamic_tools.map((value) => {
1855
+ if (!isJsonObject(value) || value.type !== "namespace") return readFunction(value);
1856
+ 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
+ "type",
1858
+ "name",
1859
+ "description",
1860
+ "tools"
1861
+ ].includes(key))) throw fail();
1862
+ namespaces.add(value.name);
1863
+ return {
1864
+ type: "namespace",
1865
+ name: value.name,
1866
+ description: value.description,
1867
+ tools: value.tools.map(readFunction)
1868
+ };
1869
+ });
1870
+ if (fingerprint !== void 0 && codexDynamicToolsFingerprint(tools) !== fingerprint) throw fail();
1871
+ return tools;
1872
+ }
1873
+ /** Existing supervised bindings identify data, never authorize an executor or a new adoption. */
1874
+ async function loadCodexNativeToolCatalog(params) {
1875
+ const { binding, client, appServer, agentDir, assertCurrent } = params;
1876
+ assertCurrent();
1877
+ const home = resolveCodexAppServerLocalHomeDir(appServer.start, agentDir);
1878
+ const actualHome = client.getRuntimeIdentity()?.codexHome;
1879
+ if (!hasCodexNativeToolCatalog(binding) || !binding.dynamicToolsFingerprint || appServer.start.transport !== "stdio" || appServer.remoteWorkspaceRoot || !actualHome || path.resolve(actualHome) !== path.resolve(home) || binding.appServerRuntimeFingerprint !== buildCodexAppServerConnectionFingerprint(appServer, agentDir)) throw new Error("Canonical Codex declarations require the original verified local binding and selected native connection; the thread is preserved.");
1880
+ const metadata = await readCodexClientSessionMeta(client, path.join(home, "sessions"), binding.rolloutPath, binding.threadId);
1881
+ assertCurrent();
1882
+ return parseCodexNativeToolCatalog(metadata, binding.threadId, binding.dynamicToolsFingerprint);
1883
+ }
1825
1884
  //#endregion
1826
1885
  //#region extensions/codex/src/app-server/managed-thread-store.ts
1827
1886
  const CODEX_MANAGED_THREAD_NAMESPACE = "app-server-managed-threads";
@@ -1851,6 +1910,10 @@ function managedThreadStoreKey(sourceHomeId, threadId) {
1851
1910
  /** Durable ownership index for Codex threads created by OpenClaw. */
1852
1911
  function createCodexManagedThreadStore(state) {
1853
1912
  return {
1913
+ async has(sourceHomeId, threadId) {
1914
+ const parsed = managedThreadSchema.safeParse(state.lookup(managedThreadStoreKey(sourceHomeId, threadId)));
1915
+ return parsed.success && parsed.data.sourceHomeId === sourceHomeId && parsed.data.threadId === threadId;
1916
+ },
1854
1917
  async mark(params) {
1855
1918
  try {
1856
1919
  const value = managedThreadSchema.parse({
@@ -1881,11 +1944,59 @@ function createCodexManagedThreadStore(state) {
1881
1944
  };
1882
1945
  }
1883
1946
  //#endregion
1947
+ //#region extensions/codex/src/app-server/thread-mcp-attestation.ts
1948
+ async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal, expectedActiveServerNames = []) {
1949
+ const configuredServers = threadConfig?.mcp_servers;
1950
+ if (configuredServers !== void 0 && !isJsonObject(configuredServers)) throw new Error("Codex restricted-tool-surface thread config has invalid mcp_servers");
1951
+ const expectedServers = /* @__PURE__ */ new Map();
1952
+ for (const [name, serverConfig] of Object.entries(configuredServers ?? {})) {
1953
+ if (!isJsonObject(serverConfig) || serverConfig.enabled !== false) throw new Error(`Codex restricted-tool-surface MCP server ${name} is not disabled`);
1954
+ expectedServers.set(name, "disabled");
1955
+ }
1956
+ for (const name of expectedActiveServerNames) {
1957
+ if (expectedServers.get(name) === "disabled") throw new Error(`Codex restricted-tool-surface MCP server ${name} has conflicting policy`);
1958
+ expectedServers.set(name, "active");
1959
+ }
1960
+ const response = await client.request("mcpServerStatus/list", {
1961
+ threadId,
1962
+ detail: "toolsAndAuthOnly"
1963
+ }, { signal });
1964
+ if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
1965
+ const observedServerNames = /* @__PURE__ */ new Set();
1966
+ for (const status of response.data) {
1967
+ if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
1968
+ if (!expectedServers.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
1969
+ if (observedServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
1970
+ observedServerNames.add(status.name);
1971
+ if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
1972
+ if (expectedServers.get(status.name) === "active") {
1973
+ if (status.serverInfo === null || Object.keys(status.tools).length === 0) throw new Error(`Codex restricted-tool-surface MCP attestation found inactive admitted server ${status.name}`);
1974
+ continue;
1975
+ }
1976
+ if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
1977
+ if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
1978
+ }
1979
+ for (const [expectedName, state] of expectedServers) if (!observedServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing ${state === "active" ? "admitted " : ""}server ${expectedName}`);
1980
+ if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
1981
+ }
1982
+ //#endregion
1884
1983
  //#region extensions/codex/src/app-server/plugin-thread-attestation.ts
1885
1984
  /**
1886
1985
  * Confirms admitted plugin and account apps against their actual Codex thread before
1887
1986
  * OpenClaw commits a binding or starts a turn.
1888
1987
  */
1988
+ /** Every admission path checks the same surface; its lifecycle owner keeps the claim fenced. */
1989
+ async function attestCodexThreadToolSurface(params) {
1990
+ params.assertCurrent();
1991
+ if (params.appIds.length > 0) {
1992
+ await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps(params));
1993
+ params.assertCurrent();
1994
+ }
1995
+ if (params.restrictedToolSurface) {
1996
+ await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, params.threadId, params.threadConfig, params.signal, params.appIds.length > 0 ? ["codex_apps"] : []));
1997
+ params.assertCurrent();
1998
+ }
1999
+ }
1889
2000
  var CodexPluginThreadAppAttestationError = class extends Error {
1890
2001
  constructor(message, options) {
1891
2002
  super(message, options);
@@ -1936,213 +2047,506 @@ async function discardUnattestedCodexPluginThread(params) {
1936
2047
  }
1937
2048
  }
1938
2049
  //#endregion
1939
- //#region extensions/codex/src/app-server/thread-lifecycle-result.ts
1940
- /** Materializes the public lifecycle result after a fresh thread is durably committed. */
1941
- function buildStartedCodexThreadBinding(input) {
1942
- const { context, params, response, startParams } = input;
1943
- return {
1944
- threadId: response.thread.id,
1945
- ...input.clientId ? { clientId: input.clientId } : {},
1946
- cwd: params.cwd,
1947
- ...input.rolloutPath ? { rolloutPath: input.rolloutPath } : {},
1948
- authProfileId: params.params.authProfileId,
1949
- agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
1950
- model: response.model ?? startParams.model ?? params.params.modelId,
1951
- modelProvider: response.modelProvider ?? input.startModelProvider ?? input.modelProvider,
1952
- dynamicToolsFingerprint: context.dynamicToolsFingerprint,
1953
- dynamicToolsContainDeferred: context.dynamicToolsContainDeferred,
1954
- nativeSkillIsolationFingerprint: context.nativeSkillIsolationFingerprint,
1955
- userMcpServersFingerprint: context.userMcpServersFingerprint,
1956
- mcpServersFingerprint: input.nextMcpServersFingerprint,
1957
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
1958
- ringZeroConfigFingerprint: context.ringZeroConfigFingerprint,
1959
- ringZeroClientInstanceId: context.ringZeroClientInstanceId,
1960
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
1961
- networkProxyConfigFingerprint: context.networkProxyConfigFingerprint,
1962
- nativeHookRelayGeneration: input.finalConfigPatch.nativeHookRelayGeneration,
1963
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
1964
- pluginAppsFingerprint: input.pluginThreadConfig?.fingerprint,
1965
- pluginAppsInputFingerprint: input.pluginThreadConfig?.inputFingerprint,
1966
- pluginAppPolicyContext: input.pluginThreadConfig?.policyContext,
1967
- contextEngine: context.contextEngineBinding,
1968
- environmentSelectionFingerprint: context.environmentSelectionFingerprint,
1969
- ...!context.preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
1970
- ...startParams,
1971
- model: response.model ?? startParams.model ?? null,
1972
- requestedModel: startParams.model ?? null,
1973
- modelProvider: input.bindingModelProvider ?? null,
1974
- requestedModelProvider: startParams.modelProvider ?? input.bindingModelProvider ?? null
1975
- }, params.params.authProfileId, context.dynamicToolsFingerprint) } : {},
1976
- lifecycle: {
1977
- action: "started",
1978
- ...context.rotatedContextEngineBinding ? { rotatedContextEngineBinding: true } : {}
2050
+ //#region extensions/codex/src/app-server/native-hook-relay-state.ts
2051
+ const pending = /* @__PURE__ */ new Set();
2052
+ /** Owns delayed hook-relay cleanup across runtime scheduling and test teardown. */
2053
+ const nativeHookRelayUnregisterQueue = {
2054
+ add(entry) {
2055
+ pending.add(entry);
2056
+ },
2057
+ delete(entry) {
2058
+ return pending.delete(entry);
2059
+ },
2060
+ flush() {
2061
+ while (pending.size > 0) {
2062
+ const entry = pending.values().next().value;
2063
+ if (!entry) return;
2064
+ clearTimeout(entry.timeout);
2065
+ entry.unregister();
1979
2066
  }
1980
- };
1981
- }
2067
+ },
2068
+ clear() {
2069
+ for (const entry of pending) clearTimeout(entry.timeout);
2070
+ pending.clear();
2071
+ }
2072
+ };
1982
2073
  //#endregion
1983
- //#region extensions/codex/src/app-server/reasoning-effort.ts
1984
- const CODEX_REASONING_EFFORTS = [
1985
- "minimal",
1986
- "low",
1987
- "medium",
1988
- "high",
1989
- "xhigh",
1990
- "max",
1991
- "ultra"
1992
- ];
1993
- const LEGACY_PRO_REASONING_EFFORTS = [
1994
- "medium",
1995
- "high",
1996
- "xhigh"
2074
+ //#region extensions/codex/src/app-server/native-hook-relay.ts
2075
+ /**
2076
+ * Bridges Codex native hook callbacks into OpenClaw's native hook relay so
2077
+ * app-server tool events can still run OpenClaw policy and diagnostics.
2078
+ */
2079
+ /** Codex hook events that can be registered through OpenClaw's native relay. */
2080
+ const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
2081
+ "pre_tool_use",
2082
+ "post_tool_use",
2083
+ "permission_request",
2084
+ "before_agent_finalize"
1997
2085
  ];
1998
- const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
1999
- const MODERN_GPT_5_MODEL_ID_RE = /^gpt-5\.(?:[3-9]|[1-9]\d)(?:$|-)/u;
2000
- function normalizeCodexReasoningEfforts(efforts) {
2001
- if (!efforts) return [];
2002
- const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
2003
- return CODEX_REASONING_EFFORTS.filter((effort) => supported.has(effort));
2004
- }
2005
- /** Read reasoning metadata after the Codex app-server route has been selected. */
2006
- function readCodexSupportedReasoningEfforts(compat) {
2007
- if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
2008
- const efforts = compat.supportedReasoningEfforts;
2009
- if (!Array.isArray(efforts)) return;
2010
- return efforts.filter((effort) => typeof effort === "string");
2086
+ const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
2087
+ const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 18e5;
2088
+ /** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
2089
+ const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 3e5;
2090
+ const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
2091
+ const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
2092
+ const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
2093
+ const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
2094
+ const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
2095
+ const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
2096
+ const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
2097
+ const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
2098
+ exec: [
2099
+ "Bash",
2100
+ "exec",
2101
+ "exec_command"
2102
+ ],
2103
+ apply_patch: [
2104
+ "apply_patch",
2105
+ "Write",
2106
+ "Edit"
2107
+ ],
2108
+ spawn_agent: ["spawn_agent", "Agent"]
2109
+ };
2110
+ /** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
2111
+ async function assertCodexNativeHookRelayAllowed(client, signal) {
2112
+ let attestation = nativeHookPolicyByClient.get(client);
2113
+ if (!attestation) {
2114
+ attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
2115
+ if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
2116
+ const requirements = response.requirements;
2117
+ if (requirements === null) return;
2118
+ if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
2119
+ const managedOnly = requirements.allowManagedHooksOnly;
2120
+ if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
2121
+ if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
2122
+ });
2123
+ nativeHookPolicyByClient.set(client, attestation);
2124
+ attestation.catch(() => {
2125
+ if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
2126
+ });
2127
+ }
2128
+ await attestation;
2011
2129
  }
2012
- function resolveSupportedReasoningEffort(params) {
2013
- const supported = normalizeCodexReasoningEfforts(params.supportedReasoningEfforts);
2014
- if (supported.includes(params.requested)) return params.requested;
2015
- const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
2016
- const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
2017
- return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
2130
+ /** Defers relay unregister so late native hook subprocesses can still resolve. */
2131
+ function scheduleCodexNativeHookRelayUnregister(params) {
2132
+ let pending;
2133
+ const unregister = () => {
2134
+ if (!pending) return;
2135
+ const current = pending;
2136
+ pending = void 0;
2137
+ if (!nativeHookRelayUnregisterQueue.delete(current)) return;
2138
+ params.relay.unregister();
2139
+ };
2140
+ const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
2141
+ pending = {
2142
+ timeout,
2143
+ unregister
2144
+ };
2145
+ nativeHookRelayUnregisterQueue.add(pending);
2146
+ timeout.unref();
2018
2147
  }
2019
- /** Resolve a turn effort from the selected model's provider-owned metadata. */
2020
- function resolveCodexAppServerReasoningEffort(params) {
2021
- if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
2022
- if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
2023
- requested: params.thinkLevel,
2024
- supportedReasoningEfforts: params.supportedReasoningEfforts
2025
- }) ?? null;
2026
- const modelId = params.modelId.trim().toLowerCase();
2027
- if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
2028
- requested: params.thinkLevel,
2029
- supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
2030
- }) ?? null;
2031
- if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
2032
- if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
2033
- return null;
2148
+ /** Computes the delayed unregister window from Codex's hook timeout. */
2149
+ function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
2150
+ const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
2151
+ return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
2034
2152
  }
2035
- //#endregion
2036
- //#region extensions/codex/src/app-server/thread-model-selection.ts
2037
- const CODEX_NATIVE_PERSONALITY_NONE = "none";
2038
- function resolveCodexBindingModelProviderFallback(params) {
2039
- const provider = params.provider?.trim().toLowerCase();
2040
- if (provider && provider !== "codex") return;
2041
- const currentModel = params.currentModel?.trim();
2042
- const bindingModel = params.bindingModel?.trim();
2043
- if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
2044
- return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
2153
+ /** Records a native pre-tool failure that Codex does not project as a tool item. */
2154
+ function emitCodexNativePreToolUseFailureDiagnostic(params) {
2155
+ emitTrustedDiagnosticEvent({
2156
+ type: "tool.execution.error",
2157
+ ...params.agentId ? { agentId: params.agentId } : {},
2158
+ sessionId: params.sessionId,
2159
+ ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
2160
+ runId: params.runId,
2161
+ toolName: params.failure.toolName,
2162
+ toolCallId: params.failure.toolCallId,
2163
+ durationMs: params.failure.durationMs,
2164
+ errorCategory: "before_tool_call",
2165
+ terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
2166
+ ...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
2167
+ });
2045
2168
  }
2046
- function resolveCodexAppServerThreadModelSelection(params) {
2047
- const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
2048
- const explicitModelProvider = resolveCodexAppServerModelProvider({
2049
- provider: params.provider,
2050
- authProfileId,
2051
- authProfileStore: params.authProfileStore,
2052
- agentDir: params.agentDir,
2053
- config: params.config
2054
- });
2055
- const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
2056
- provider: params.provider,
2057
- currentModel: params.model,
2058
- bindingModel: params.binding.model,
2059
- bindingModelProvider: params.binding.modelProvider
2060
- }) : void 0;
2061
- return resolveCodexAppServerRequestModelSelection({
2062
- model: params.model,
2063
- modelProvider: explicitModelProvider ?? bindingModelProvider,
2064
- authProfileId,
2065
- authProfileStore: params.authProfileStore,
2066
- agentDir: params.agentDir,
2067
- config: params.config
2068
- });
2069
- }
2070
- function resolveCodexAppServerRequestModelSelection(params) {
2071
- const model = params.model.trim();
2072
- const modelProvider = params.modelProvider?.trim();
2073
- if (modelProvider) return {
2074
- model,
2075
- modelProvider
2169
+ /** Registers an OpenClaw native hook relay for a Codex app-server turn. */
2170
+ function createCodexNativeHookRelay(params) {
2171
+ if (params.options?.enabled === false) return;
2172
+ const directChildClaims = /* @__PURE__ */ new Map();
2173
+ const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
2174
+ let foregroundClosed = false;
2175
+ let successfulYieldRetentionAuthorized = false;
2176
+ const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
2177
+ const rejectPendingAdmissions = (reason) => {
2178
+ for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
2179
+ pendingDirectChildAdmissions.clear();
2076
2180
  };
2077
- const slashIndex = model.indexOf("/");
2078
- if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
2079
- const inferredModelProvider = resolveCodexAppServerModelProvider({
2080
- provider: model.slice(0, slashIndex),
2081
- authProfileId: params.authProfileId,
2082
- authProfileStore: params.authProfileStore,
2083
- agentDir: params.agentDir,
2084
- config: params.config
2181
+ const relay = registerRetainedNativeHookRelayForBundledRuntime({
2182
+ provider: "codex",
2183
+ relayId: buildCodexNativeHookRelayId({
2184
+ agentId: params.agentId,
2185
+ sessionId: params.sessionId,
2186
+ sessionKey: params.sessionKey
2187
+ }),
2188
+ ...params.generation ? { generation: params.generation } : {},
2189
+ ...params.generationMismatchGraceMs ? { generationMismatchGraceMs: params.generationMismatchGraceMs } : {},
2190
+ ...params.agentId ? { agentId: params.agentId } : {},
2191
+ sessionId: params.sessionId,
2192
+ ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
2193
+ ...params.config ? { config: params.config } : {},
2194
+ autoApproveMcpTools: params.autoApproveMcpTools,
2195
+ projectedMcpServers: params.projectedMcpServers,
2196
+ runId: params.runId,
2197
+ ...params.channelId ? { channelId: params.channelId } : {},
2198
+ ...params.requester ? { requester: params.requester } : {},
2199
+ ...params.approvalContext ? { approvalContext: params.approvalContext } : {},
2200
+ allowedEvents: params.events,
2201
+ preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
2202
+ ttlMs: resolveCodexNativeHookRelayTtlMs({
2203
+ explicitTtlMs: params.options?.ttlMs,
2204
+ attemptTimeoutMs: params.attemptTimeoutMs,
2205
+ startupTimeoutMs: params.startupTimeoutMs,
2206
+ turnStartTimeoutMs: params.turnStartTimeoutMs
2207
+ }),
2208
+ signal: params.signal,
2209
+ runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
2210
+ assertActive: params.hostCapabilities.assertActive,
2211
+ retention: {
2212
+ readClaim: readCodexNativeChildThreadId,
2213
+ shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
2214
+ allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
2215
+ awaitForegroundAdmission: (childThreadId) => {
2216
+ if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
2217
+ const existingClaim = directChildClaims.get(childThreadId);
2218
+ if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
2219
+ const existingPending = pendingDirectChildAdmissions.get(childThreadId);
2220
+ if (existingPending) return existingPending.promise.then((claim) => assertClaim(childThreadId, claim));
2221
+ if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
2222
+ const { promise, resolve, reject } = createDeferred();
2223
+ pendingDirectChildAdmissions.set(childThreadId, {
2224
+ promise,
2225
+ resolve,
2226
+ reject
2227
+ });
2228
+ return promise.then((claim) => assertClaim(childThreadId, claim));
2229
+ },
2230
+ onDispose: () => {
2231
+ foregroundClosed = true;
2232
+ rejectPendingAdmissions("native hook relay registration closed");
2233
+ }
2234
+ },
2235
+ onPreToolUseFailure: params.onPreToolUseFailure,
2236
+ command: {
2237
+ nice: 10,
2238
+ timeoutMs: params.options?.gatewayTimeoutMs
2239
+ }
2085
2240
  });
2241
+ const unregister = () => {
2242
+ foregroundClosed = true;
2243
+ rejectPendingAdmissions("native hook relay foreground closed");
2244
+ relay.unregister();
2245
+ };
2086
2246
  return {
2087
- model: model.slice(slashIndex + 1).trim(),
2088
- ...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
2247
+ ...relay,
2248
+ unregister,
2249
+ authorizeRetentionAfterSuccessfulYield: () => {
2250
+ successfulYieldRetentionAuthorized = true;
2251
+ },
2252
+ hasClaimedDirectChild: () => directChildClaims.size > 0,
2253
+ rejectPendingDirectChild: (threadIdInput, reason) => {
2254
+ const threadId = threadIdInput.trim();
2255
+ const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
2256
+ if (!pending) return;
2257
+ pendingDirectChildAdmissions.delete(threadId);
2258
+ pending.reject(new Error(reason));
2259
+ },
2260
+ claimDirectChild: (threadIdInput) => {
2261
+ const threadId = threadIdInput.trim();
2262
+ if (!threadId) return () => void 0;
2263
+ if (directChildClaims.get(threadId)) return () => void 0;
2264
+ const claim = Symbol(threadId);
2265
+ directChildClaims.set(threadId, claim);
2266
+ const pending = pendingDirectChildAdmissions.get(threadId);
2267
+ pendingDirectChildAdmissions.delete(threadId);
2268
+ pending?.resolve(claim);
2269
+ let released = false;
2270
+ return () => {
2271
+ if (released) return;
2272
+ released = true;
2273
+ if (directChildClaims.get(threadId) !== claim) return;
2274
+ directChildClaims.delete(threadId);
2275
+ if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
2276
+ };
2277
+ }
2089
2278
  };
2090
2279
  }
2091
- function hasProviderQualifiedModelRef(model) {
2092
- const trimmed = model?.trim();
2093
- const slashIndex = trimmed?.indexOf("/") ?? -1;
2094
- return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
2280
+ function readCodexNativeChildThreadId(rawPayload) {
2281
+ if (!isJsonObject(rawPayload) || typeof rawPayload.agent_id !== "string") return;
2282
+ return rawPayload.agent_id.trim() || void 0;
2095
2283
  }
2096
- function resolveCodexAppServerModelProvider(params) {
2097
- const normalized = params.provider.trim();
2098
- const normalizedLower = normalized.toLowerCase();
2099
- if (!normalized || normalizedLower === "codex") return;
2100
- if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
2101
- return normalizedLower === "openai" ? "openai" : normalized;
2284
+ /** Selects the native hook events Codex should install for the current approval mode. */
2285
+ function resolveCodexNativeHookRelayEvents(params) {
2286
+ if (params.configuredEvents?.length) return params.configuredEvents;
2287
+ return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
2102
2288
  }
2103
- function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
2104
- return resolveCodexAppServerReasoningEffort({
2105
- thinkLevel,
2106
- modelId,
2107
- supportedReasoningEfforts
2108
- });
2289
+ /** Derives the native hook relay TTL from the turn budget unless explicitly configured. */
2290
+ function resolveCodexNativeHookRelayTtlMs(params) {
2291
+ if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
2292
+ const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
2293
+ return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
2109
2294
  }
2110
- //#endregion
2111
- //#region extensions/codex/src/app-server/project-doc-thread-config.ts
2112
- const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES = 128 * 1024;
2113
- function buildCodexProjectDocThreadConfig(config) {
2114
- const defaults = { project_doc_max_bytes: CODEX_NATIVE_PROJECT_DOC_MAX_BYTES };
2115
- return mergeCodexThreadConfigs(defaults, config) ?? defaults;
2295
+ /** Builds a stable relay id scoped to the agent and session identity. */
2296
+ function buildCodexNativeHookRelayId(params) {
2297
+ const hash = createHash("sha256");
2298
+ hash.update("openclaw:codex:native-hook-relay:v1");
2299
+ hash.update("\0");
2300
+ hash.update(params.agentId?.trim() || "");
2301
+ hash.update("\0");
2302
+ hash.update(params.sessionKey?.trim() || params.sessionId);
2303
+ return `codex-${hash.digest("hex").slice(0, 40)}`;
2116
2304
  }
2117
- //#endregion
2118
- //#region extensions/codex/src/app-server/thread-prompt.ts
2119
- function buildDeveloperInstructions(params, options = {}) {
2120
- const deferredToolNames = /* @__PURE__ */ new Set();
2121
- let hasSkillWorkshop = false;
2122
- let hasSessionsSpawn = false;
2123
- let hasSessionsYield = false;
2124
- let hasSubagentsList = false;
2125
- let hasSessionsSend = false;
2126
- let hasSeenDirectNamespace = false;
2127
- let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
2128
- for (const spec of options.dynamicTools ?? []) {
2129
- const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
2130
- if (isDirectNamespace) hasSeenDirectNamespace = true;
2131
- for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
2132
- const name = tool.name.trim();
2133
- if (tool.deferLoading === true && name) deferredToolNames.add(name);
2134
- hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
2135
- hasSessionsSpawn ||= name === "sessions_spawn";
2136
- hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
2137
- hasSubagentsList ||= name === "subagents";
2138
- hasSessionsSend ||= name === "sessions_send";
2139
- messageToolAvailable ||= name === "message";
2305
+ const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
2306
+ pre_tool_use: "PreToolUse",
2307
+ post_tool_use: "PostToolUse",
2308
+ permission_request: "PermissionRequest",
2309
+ before_agent_finalize: "Stop"
2310
+ };
2311
+ const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
2312
+ pre_tool_use: "pre_tool_use",
2313
+ post_tool_use: "post_tool_use",
2314
+ permission_request: "permission_request",
2315
+ before_agent_finalize: "stop"
2316
+ };
2317
+ const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
2318
+ /** Builds the Codex config overlay that installs trusted command hooks for relay events. */
2319
+ function buildCodexNativeHookRelayConfig(params) {
2320
+ const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
2321
+ const selectedEvents = new Set(events);
2322
+ const config = { "features.hooks": true };
2323
+ const hookState = {};
2324
+ for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
2325
+ const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
2326
+ const selected = selectedEvents.has(event);
2327
+ const shouldRelay = params.relay.shouldRelayEvent(event);
2328
+ if (!selected || !shouldRelay) {
2329
+ if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
2330
+ if (params.clearOmittedEvents) for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = { enabled: false };
2331
+ continue;
2140
2332
  }
2333
+ const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
2334
+ const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
2335
+ const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
2336
+ config[`hooks.${codexEvent}`] = [{
2337
+ ...matcher ? { matcher } : {},
2338
+ hooks: [{
2339
+ type: "command",
2340
+ command,
2341
+ timeout,
2342
+ async: false,
2343
+ statusMessage: "OpenClaw native hook relay"
2344
+ }]
2345
+ }];
2346
+ const state = {
2347
+ enabled: true,
2348
+ trusted_hash: codexCommandHookTrustedHash({
2349
+ event,
2350
+ command,
2351
+ matcher,
2352
+ timeout,
2353
+ statusMessage: "OpenClaw native hook relay"
2354
+ })
2355
+ };
2356
+ for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
2141
2357
  }
2142
- const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
2143
- surface: "codex_app_server",
2144
- includeLegacyGlobalGuidance: false
2145
- }).join("\n");
2358
+ config["hooks.state"] = hookState;
2359
+ return config;
2360
+ }
2361
+ /** Builds a Codex config overlay that disables native hooks and clears hook arrays. */
2362
+ function buildCodexNativeHookRelayDisabledConfig() {
2363
+ return {
2364
+ "features.hooks": false,
2365
+ "hooks.PreToolUse": [],
2366
+ "hooks.PostToolUse": [],
2367
+ "hooks.PermissionRequest": [],
2368
+ "hooks.Stop": []
2369
+ };
2370
+ }
2371
+ function normalizeHookTimeoutSec(value) {
2372
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC;
2373
+ }
2374
+ function resolveCodexNativeHookRelayCommandTimeoutMs(hookTimeoutSec) {
2375
+ const parentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 5e3;
2376
+ const parentMarginMs = Math.min(CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS, Math.max(CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS, Math.floor(parentTimeoutMs / 5)));
2377
+ return Math.max(1, parentTimeoutMs - parentMarginMs);
2378
+ }
2379
+ function buildCodexNativeToolMatcher(toolNames) {
2380
+ if (toolNames === void 0) return;
2381
+ if (toolNames.length === 0) throw new TypeError("Codex native hook matcher requires at least one tool name");
2382
+ const nativeNames = /* @__PURE__ */ new Set();
2383
+ let hasCustomToolName = false;
2384
+ for (const toolName of toolNames) {
2385
+ const canonicalToolName = toolName.trim();
2386
+ if (!canonicalToolName || canonicalToolName === "*") throw new TypeError("Codex native hook matcher requires canonical OpenClaw tool ids");
2387
+ const nativeAliases = CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID[canonicalToolName];
2388
+ if (!nativeAliases) hasCustomToolName = true;
2389
+ for (const nativeName of nativeAliases ?? [canonicalToolName]) nativeNames.add(nativeName);
2390
+ }
2391
+ const sortedNames = Array.from(nativeNames).toSorted();
2392
+ if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
2393
+ return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
2394
+ }
2395
+ function codexCommandHookTrustedHash(params) {
2396
+ const identity = {
2397
+ event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
2398
+ ...params.matcher ? { matcher: params.matcher } : {},
2399
+ hooks: [{
2400
+ async: false,
2401
+ command: params.command,
2402
+ statusMessage: params.statusMessage,
2403
+ timeout: params.timeout,
2404
+ type: "command"
2405
+ }]
2406
+ };
2407
+ return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
2408
+ }
2409
+ function sortJsonValue(value) {
2410
+ if (!value || typeof value !== "object") return value;
2411
+ if (Array.isArray(value)) return value.map(sortJsonValue);
2412
+ const sorted = {};
2413
+ for (const [key, entry] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) sorted[key] = sortJsonValue(entry);
2414
+ return sorted;
2415
+ }
2416
+ //#endregion
2417
+ //#region extensions/codex/src/app-server/profiler-flag.ts
2418
+ const PROFILER_FLAGS = ["profiler", "codex.profiler"];
2419
+ /** Checks the generic and Codex-specific profiler diagnostic flags. */
2420
+ function isCodexAppServerProfilerEnabled(config, env = process.env) {
2421
+ return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
2422
+ }
2423
+ //#endregion
2424
+ //#region extensions/codex/src/app-server/mcp-tool-metadata.ts
2425
+ /** Hosted app ownership is authoritative only on metadata supplied by Codex. */
2426
+ function readCodexMcpToolConnectorId(tool) {
2427
+ const metadata = asOptionalRecord(asOptionalRecord(tool)?.["_meta"]);
2428
+ return normalizeOptionalString(metadata?.connector_id) ?? normalizeOptionalString(metadata?.connectorId);
2429
+ }
2430
+ /** Preserve MCP App visibility so model-only tools cannot become widget authority. */
2431
+ function readCodexMcpToolUiVisibility(tool) {
2432
+ const metadata = asOptionalRecord(asOptionalRecord(tool)?.["_meta"]);
2433
+ const visibility = asOptionalRecord(metadata?.ui)?.visibility;
2434
+ if (!Array.isArray(visibility)) return;
2435
+ return [...new Set(visibility.filter((value) => value === "app" || value === "model"))].toSorted();
2436
+ }
2437
+ //#endregion
2438
+ //#region extensions/codex/src/app-server/project-doc-thread-config.ts
2439
+ const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES = 131072;
2440
+ function buildCodexProjectDocThreadConfig(config) {
2441
+ const defaults = { project_doc_max_bytes: CODEX_NATIVE_PROJECT_DOC_MAX_BYTES };
2442
+ return mergeCodexThreadConfigs(defaults, config) ?? defaults;
2443
+ }
2444
+ //#endregion
2445
+ //#region extensions/codex/src/app-server/thread-model-selection.ts
2446
+ const CODEX_NATIVE_PERSONALITY_NONE = "none";
2447
+ function resolveCodexBindingModelProviderFallback(params) {
2448
+ const provider = params.provider?.trim().toLowerCase();
2449
+ if (provider && provider !== "codex") return;
2450
+ const currentModel = params.currentModel?.trim();
2451
+ const bindingModel = params.bindingModel?.trim();
2452
+ if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
2453
+ return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
2454
+ }
2455
+ function resolveCodexAppServerThreadModelSelection(params) {
2456
+ const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
2457
+ const explicitModelProvider = resolveCodexAppServerModelProvider({
2458
+ provider: params.provider,
2459
+ authProfileId,
2460
+ authProfileStore: params.authProfileStore,
2461
+ agentDir: params.agentDir,
2462
+ config: params.config
2463
+ });
2464
+ const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
2465
+ provider: params.provider,
2466
+ currentModel: params.model,
2467
+ bindingModel: params.binding.model,
2468
+ bindingModelProvider: params.binding.modelProvider
2469
+ }) : void 0;
2470
+ return resolveCodexAppServerRequestModelSelection({
2471
+ model: params.model,
2472
+ modelProvider: explicitModelProvider ?? bindingModelProvider,
2473
+ authProfileId,
2474
+ authProfileStore: params.authProfileStore,
2475
+ agentDir: params.agentDir,
2476
+ config: params.config
2477
+ });
2478
+ }
2479
+ function resolveCodexAppServerRequestModelSelection(params) {
2480
+ const model = params.model.trim();
2481
+ const modelProvider = params.modelProvider?.trim();
2482
+ if (modelProvider) return {
2483
+ model,
2484
+ modelProvider
2485
+ };
2486
+ const slashIndex = model.indexOf("/");
2487
+ if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
2488
+ const inferredModelProvider = resolveCodexAppServerModelProvider({
2489
+ provider: model.slice(0, slashIndex),
2490
+ authProfileId: params.authProfileId,
2491
+ authProfileStore: params.authProfileStore,
2492
+ agentDir: params.agentDir,
2493
+ config: params.config
2494
+ });
2495
+ return {
2496
+ model: model.slice(slashIndex + 1).trim(),
2497
+ ...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
2498
+ };
2499
+ }
2500
+ function hasProviderQualifiedModelRef(model) {
2501
+ const trimmed = model?.trim();
2502
+ const slashIndex = trimmed?.indexOf("/") ?? -1;
2503
+ return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
2504
+ }
2505
+ function resolveCodexAppServerModelProvider(params) {
2506
+ const normalized = params.provider.trim();
2507
+ const normalizedLower = normalized.toLowerCase();
2508
+ if (!normalized || normalizedLower === "codex") return;
2509
+ if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
2510
+ return normalizedLower === "openai" ? "openai" : normalized;
2511
+ }
2512
+ //#endregion
2513
+ //#region extensions/codex/src/app-server/thread-prompt.ts
2514
+ function buildDeveloperInstructions(params, options = {}) {
2515
+ const deferredToolNames = /* @__PURE__ */ new Set();
2516
+ let secretsToolName;
2517
+ let showWidgetToolName;
2518
+ let dashboardToolName;
2519
+ let portalToolName;
2520
+ let hasSkillWorkshop = false;
2521
+ let hasSessionsSpawn = false;
2522
+ let hasSessionsYield = false;
2523
+ let hasSubagentsList = false;
2524
+ let hasSessionsSend = false;
2525
+ let hasSeenDirectNamespace = false;
2526
+ let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
2527
+ for (const spec of options.dynamicTools ?? []) {
2528
+ const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
2529
+ if (isDirectNamespace) hasSeenDirectNamespace = true;
2530
+ for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
2531
+ const name = tool.name.trim();
2532
+ const qualifiedName = spec.type === "namespace" ? `${spec.name}.${name}` : name;
2533
+ if (tool.deferLoading === true && name) deferredToolNames.add(name);
2534
+ if (name === "secrets" && params.disableTools !== true) secretsToolName ??= qualifiedName;
2535
+ if (name === "show_widget") showWidgetToolName ??= qualifiedName;
2536
+ if (name === "dashboard") dashboardToolName ??= qualifiedName;
2537
+ if (name === "portal") portalToolName ??= qualifiedName;
2538
+ hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
2539
+ hasSessionsSpawn ||= name === "sessions_spawn";
2540
+ hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
2541
+ hasSubagentsList ||= name === "subagents";
2542
+ hasSessionsSend ||= name === "sessions_send";
2543
+ messageToolAvailable ||= name === "message";
2544
+ }
2545
+ }
2546
+ const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
2547
+ surface: "codex_app_server",
2548
+ includeLegacyGlobalGuidance: false
2549
+ }).join("\n");
2146
2550
  const delegationGuidanceAvailable = params.disableTools !== true && params.delegationCapability !== "report_only" && !isMessageOnlyCodexSourceReply(params);
2147
2551
  const nativeDelegationAvailable = delegationGuidanceAvailable && !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) && !shouldDisableCodexToolSearchForModel(params.modelId);
2148
2552
  const deferredToolDiscoveryGuidance = deferredToolNames.size > 0 || nativeDelegationAvailable ? "Deferred tools may be absent from the direct tool list. Use `tool_search` when directly callable. On code-mode-only models, use `exec` instead: filter `ALL_TOOLS` by name and description, then call the matching entry through `tools`." : void 0;
@@ -2168,9 +2572,14 @@ function buildDeveloperInstructions(params, options = {}) {
2168
2572
  }).join("\n") : void 0,
2169
2573
  buildHarnessVisibleReplyGuidance({
2170
2574
  sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
2171
- messageToolAvailable
2575
+ messageToolAvailable,
2576
+ uiPresentation: params.disableTools !== true && params.promptMode !== "minimal" && params.promptMode !== "none" ? {
2577
+ showWidgetToolName,
2578
+ dashboardToolName,
2579
+ portalToolName
2580
+ } : void 0
2172
2581
  }),
2173
- TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT,
2582
+ buildCredentialSafetyPrompt(secretsToolName),
2174
2583
  nativeCommandGuidance,
2175
2584
  params.extraSystemPrompt
2176
2585
  ].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
@@ -2364,8 +2773,31 @@ const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
2364
2773
  "project",
2365
2774
  "sessionFlags"
2366
2775
  ]);
2776
+ /** Common deterministic start/resume/fork fields; no run resources or unsupported setters. */
2777
+ function buildCodexThreadConfiguration(params, options) {
2778
+ return {
2779
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
2780
+ ...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
2781
+ approvalPolicy: options.appServer.approvalPolicy,
2782
+ approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2783
+ ...codexThreadSandboxOrPermissions(options.appServer),
2784
+ ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2785
+ config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2786
+ nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2787
+ nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2788
+ nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2789
+ directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
2790
+ webSearchAllowed: options.webSearchAllowed,
2791
+ appServer: options.appServer,
2792
+ hostSystemAgentActive: options.hostSystemAgentActive,
2793
+ restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
2794
+ shellEnvironment: options.shellEnvironment,
2795
+ disableLoginShell: options.disableLoginShell
2796
+ }),
2797
+ developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
2798
+ };
2799
+ }
2367
2800
  function buildThreadStartParams(params, options) {
2368
- const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
2369
2801
  const resolvedModelProvider = resolveCodexAppServerModelProvider({
2370
2802
  provider: params.provider,
2371
2803
  authProfileId: params.authProfileId,
@@ -2384,29 +2816,11 @@ function buildThreadStartParams(params, options) {
2384
2816
  return {
2385
2817
  model: modelSelection.model,
2386
2818
  ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
2387
- cwd: options.cwd,
2388
- ...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
2389
- approvalPolicy: options.appServer.approvalPolicy,
2390
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2391
- ...codexThreadSandboxOrPermissions(options.appServer),
2392
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2819
+ ...buildCodexThreadConfiguration(params, options),
2820
+ ...(options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) ? { baseInstructions: "" } : {},
2393
2821
  personality: CODEX_NATIVE_PERSONALITY_NONE,
2394
2822
  serviceName: "OpenClaw",
2395
- ...ringZeroActive ? { baseInstructions: "" } : {},
2396
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2397
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2398
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2399
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2400
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
2401
- webSearchAllowed: options.webSearchAllowed,
2402
- appServer: options.appServer,
2403
- hostSystemAgentActive: options.hostSystemAgentActive,
2404
- restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
2405
- shellEnvironment: options.shellEnvironment,
2406
- disableLoginShell: options.disableLoginShell
2407
- }),
2408
2823
  ...resolveCodexThreadEnvironmentSelection(options),
2409
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
2410
2824
  dynamicTools: [...options.dynamicTools],
2411
2825
  experimentalRawEvents: true,
2412
2826
  ...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
@@ -2429,8 +2843,6 @@ function buildThreadResumeParams(params, options) {
2429
2843
  });
2430
2844
  return {
2431
2845
  threadId: options.threadId,
2432
- ...options.cwd ? { cwd: options.cwd } : {},
2433
- ...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
2434
2846
  excludeTurns: true,
2435
2847
  initialTurnsPage: {
2436
2848
  limit: 1,
@@ -2441,24 +2853,8 @@ function buildThreadResumeParams(params, options) {
2441
2853
  model: modelSelection.model,
2442
2854
  ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
2443
2855
  } : {},
2444
- approvalPolicy: options.appServer.approvalPolicy,
2445
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2446
- ...codexThreadSandboxOrPermissions(options.appServer),
2447
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2448
- personality: CODEX_NATIVE_PERSONALITY_NONE,
2449
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2450
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2451
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2452
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2453
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
2454
- webSearchAllowed: options.webSearchAllowed,
2455
- appServer: options.appServer,
2456
- hostSystemAgentActive: options.hostSystemAgentActive,
2457
- restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
2458
- shellEnvironment: options.shellEnvironment,
2459
- disableLoginShell: options.disableLoginShell
2460
- }),
2461
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
2856
+ ...buildCodexThreadConfiguration(params, options),
2857
+ personality: CODEX_NATIVE_PERSONALITY_NONE
2462
2858
  };
2463
2859
  }
2464
2860
  function buildCodexRuntimeThreadConfig(config, options = {}) {
@@ -2468,33 +2864,24 @@ function buildCodexRuntimeThreadConfig(config, options = {}) {
2468
2864
  "features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
2469
2865
  };
2470
2866
  if (options.nativeCodeModeEnabled === false) {
2471
- const disabledConfig = mergeCodexThreadConfigs(configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG) ?? {
2472
- ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG,
2473
- ...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
2474
- ...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG
2475
- };
2867
+ const disabledConfig = expectDefined(mergeCodexThreadConfigs(configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG), "Codex disabled code mode config");
2476
2868
  delete disabledConfig["features.apply_patch_streaming_events"];
2477
2869
  return disabledConfig;
2478
2870
  }
2479
- if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true }) ?? {
2480
- ...codeModeConfig,
2481
- ...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
2482
- ...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG,
2483
- "features.code_mode_only": true
2484
- }, options.directOnlyToolNamespaces);
2485
- return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG) ?? {
2486
- ...codeModeConfig,
2487
- ...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
2488
- ...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG
2489
- }, options.directOnlyToolNamespaces);
2871
+ if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(expectDefined(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true }), "Codex code mode only config"), options.directOnlyToolNamespaces);
2872
+ return ensureDirectOnlyToolNamespaces(expectDefined(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG), "Codex code mode config"), options.directOnlyToolNamespaces);
2490
2873
  }
2491
2874
  function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
2492
2875
  if (!requiredNamespaces?.length) return config;
2493
- const configured = config["code_mode.direct_only_tool_namespaces"];
2494
- const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
2876
+ const feature = expectDefined(config["features.code_mode"], "Codex code mode config");
2877
+ const configured = isJsonObject(feature) ? feature : { enabled: feature };
2878
+ const namespaces = Array.isArray(configured.direct_only_tool_namespaces) ? configured.direct_only_tool_namespaces.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
2495
2879
  return {
2496
2880
  ...config,
2497
- "code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
2881
+ "features.code_mode": {
2882
+ ...configured,
2883
+ direct_only_tool_namespaces: [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
2884
+ }
2498
2885
  };
2499
2886
  }
2500
2887
  function resolveDirectOnlyToolNamespaces(dynamicTools) {
@@ -2508,10 +2895,6 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2508
2895
  const configMcpServers = config?.mcp_servers;
2509
2896
  if (restrictedToolSurface && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex restricted tool surface received invalid thread mcp_servers config");
2510
2897
  const restrictedToolSurfaceMcpServerNames = [...options.restrictedToolSurfaceInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
2511
- const restrictedRunConfig = restrictedToolSurface && isJsonObject(configMcpServers) ? {
2512
- ...config,
2513
- mcp_servers: {}
2514
- } : config;
2515
2898
  const webSearchConfig = resolveCodexWebSearchPlan({
2516
2899
  config: params.config,
2517
2900
  disableTools: params.disableTools,
@@ -2519,9 +2902,9 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2519
2902
  nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2520
2903
  webSearchAllowed: options.webSearchAllowed
2521
2904
  }).threadConfig;
2522
- const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(restrictedRunConfig, webSearchConfig), options);
2905
+ const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
2523
2906
  return applyCodexManagedShellEnvironment({
2524
- ...mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") ? { "features.image_generation": false } : void 0, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildRestrictedToolConfigPatch(restrictedToolSurfaceMcpServerNames) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames), restrictedTurnDisablesProjectDocs ? CODEX_NO_PROJECT_DOCS_CONFIG : void 0, params.authoredContextTokenCap === void 0 ? void 0 : { model_context_window: params.authoredContextTokenCap }) ?? baseConfig,
2907
+ ...mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") ? { "features.image_generation": false } : void 0, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildRestrictedToolConfigPatch(restrictedToolSurfaceMcpServerNames, Boolean(params.scheduledRuntimeAuthority)) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames), restrictedTurnDisablesProjectDocs ? CODEX_NO_PROJECT_DOCS_CONFIG : void 0, params.authoredContextTokenCap === void 0 ? void 0 : { model_context_window: params.authoredContextTokenCap }) ?? baseConfig,
2525
2908
  ...params.bootstrapContextMode === "lightweight" ? CODEX_NO_PROJECT_DOCS_CONFIG : {}
2526
2909
  }, options.shellEnvironment, options.disableLoginShell);
2527
2910
  }
@@ -2532,10 +2915,14 @@ function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isH
2532
2915
  ...CODEX_NO_PROJECT_DOCS_CONFIG
2533
2916
  };
2534
2917
  }
2535
- function buildRestrictedToolConfigPatch(inheritedMcpServerNames) {
2918
+ function buildRestrictedToolConfigPatch(inheritedMcpServerNames, scheduledAppAuthorityActive = false) {
2536
2919
  const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
2537
2920
  return {
2538
2921
  ...CODEX_RING_ZERO_THREAD_CONFIG,
2922
+ ...scheduledAppAuthorityActive ? {
2923
+ "features.apps": true,
2924
+ "orchestrator.mcp.enabled": true
2925
+ } : {},
2539
2926
  ...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
2540
2927
  };
2541
2928
  }
@@ -2557,60 +2944,55 @@ async function readCodexInheritedMcpServerNames(client, cwd, signal) {
2557
2944
  return Object.keys(configuredServers).toSorted();
2558
2945
  }
2559
2946
  async function assertCodexManagedRequirementsDoNotOverrideToolPolicy(client, options, signal) {
2560
- const response = await client.request("configRequirements/read", void 0, { signal });
2561
- if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
2562
- if (response.requirements === null) return;
2563
- if (!isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
2947
+ const requirements = await readCodexManagedRequirements(client, signal);
2948
+ const managedRequirementsFingerprint = buildCodexManagedRequirementsFingerprint(requirements);
2949
+ const managedRequirementsMatch = options.allowedManagedRequirementsFingerprint !== void 0 && managedRequirementsFingerprint === options.allowedManagedRequirementsFingerprint;
2950
+ const managedHooksAllowed = managedRequirementsMatch || options.allowConfiguredManagedHooks === true;
2951
+ if (options.allowedManagedRequirementsFingerprint !== void 0 && !managedRequirementsMatch) throw new Error("Codex managed requirements changed since this automation was authorized; reauthorize the automation from a fresh owner turn");
2952
+ if (requirements === null) return;
2564
2953
  if (options.restrictedToolSurface) for (const key of [
2565
2954
  "hooks",
2566
2955
  "managedHooks",
2567
2956
  "managed_hooks"
2568
2957
  ]) {
2569
- const hooks = response.requirements[key];
2958
+ const hooks = requirements[key];
2570
2959
  if (hooks === void 0 || hooks === null) continue;
2571
2960
  if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
2572
- if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex restricted tool surface cannot override managed hooks");
2961
+ if (hasNonEmptyJsonValue(hooks) && !managedHooksAllowed) throw new Error("Codex restricted tool surface cannot override managed hooks");
2573
2962
  }
2574
2963
  const additionalDeniedFeatures = new Set(options.additionalDeniedFeatures);
2575
2964
  for (const key of ["featureRequirements", "feature_requirements"]) {
2576
- const requirements = response.requirements[key];
2577
- if (requirements === void 0 || requirements === null) continue;
2578
- if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
2579
- for (const [feature, enabled] of Object.entries(requirements)) {
2965
+ const featureRequirements = requirements[key];
2966
+ if (featureRequirements === void 0 || featureRequirements === null) continue;
2967
+ if (!isJsonObject(featureRequirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
2968
+ for (const [feature, enabled] of Object.entries(featureRequirements)) {
2580
2969
  if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
2581
2970
  const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
2582
2971
  const deniedByToolPolicy = options.restrictedToolSurface && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature) || additionalDeniedFeatures.has(canonicalFeature);
2972
+ if (canonicalFeature === "hooks" && managedHooksAllowed) continue;
2583
2973
  if (enabled && deniedByToolPolicy) throw new Error(`Codex tool policy cannot override required feature ${feature}`);
2584
2974
  }
2585
2975
  }
2586
2976
  }
2587
- async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal) {
2588
- const configuredServers = threadConfig?.mcp_servers;
2589
- if (configuredServers !== void 0 && !isJsonObject(configuredServers)) throw new Error("Codex restricted-tool-surface thread config has invalid mcp_servers");
2590
- const expectedDisabledServerNames = /* @__PURE__ */ new Set();
2591
- for (const [name, serverConfig] of Object.entries(configuredServers ?? {})) {
2592
- if (!isJsonObject(serverConfig) || serverConfig.enabled !== false) throw new Error(`Codex restricted-tool-surface MCP server ${name} is not disabled`);
2593
- expectedDisabledServerNames.add(name);
2594
- }
2595
- const response = await client.request("mcpServerStatus/list", {
2596
- threadId,
2597
- detail: "toolsAndAuthOnly"
2598
- }, { signal });
2599
- if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
2600
- const observedDisabledServerNames = /* @__PURE__ */ new Set();
2601
- for (const status of response.data) {
2602
- if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
2603
- if (!expectedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
2604
- if (observedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
2605
- observedDisabledServerNames.add(status.name);
2606
- if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
2607
- if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
2608
- if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
2609
- }
2610
- for (const expectedName of expectedDisabledServerNames) if (!observedDisabledServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing server ${expectedName}`);
2611
- if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
2977
+ /** Hashes the exact managed requirements without retaining their hook commands or policy details. */
2978
+ function buildCodexManagedRequirementsFingerprint(requirements) {
2979
+ const fingerprint = fingerprintJsonObject({
2980
+ version: 1,
2981
+ requirements
2982
+ });
2983
+ return crypto.createHash("sha256").update(fingerprint).digest("hex");
2612
2984
  }
2613
- function hasNonEmptyJsonValue(value) {
2985
+ /** Reads and fingerprints the exact managed requirements active on this app-server. */
2986
+ async function readCodexManagedRequirementsFingerprint(client, signal) {
2987
+ return buildCodexManagedRequirementsFingerprint(await readCodexManagedRequirements(client, signal));
2988
+ }
2989
+ async function readCodexManagedRequirements(client, signal) {
2990
+ const response = await client.request("configRequirements/read", void 0, { signal });
2991
+ if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
2992
+ if (response.requirements !== null && !isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
2993
+ return response.requirements;
2994
+ }
2995
+ function hasNonEmptyJsonValue(value) {
2614
2996
  if (value === null || value === false || value === "") return false;
2615
2997
  if (Array.isArray(value)) return value.length > 0;
2616
2998
  if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
@@ -2629,1334 +3011,940 @@ function resolveCodexThreadEnvironmentSelection(options) {
2629
3011
  return {};
2630
3012
  }
2631
3013
  //#endregion
2632
- //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
2633
- function resolveManagedThreadSourceHomeId(params) {
2634
- const agentId = resolveSessionAgentIds$1({
2635
- config: params.params.config,
2636
- sessionKey: params.params.sessionKey,
2637
- agentId: params.agentId ?? params.params.agentId
2638
- }).sessionAgentId;
2639
- const agentDir = params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
2640
- return codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir));
3014
+ //#region extensions/codex/src/app-server/scheduled-app-authority.ts
3015
+ const CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE = "codex.apps";
3016
+ const CODEX_APPS_MCP_SERVER = "codex_apps";
3017
+ const MCP_STATUS_PAGE_SIZE = 100;
3018
+ const MCP_STATUS_MAX_PAGES = 100;
3019
+ const CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS = 6e4;
3020
+ const CODEX_APP_AUTHORITY_CAPTURE_MIN_TIMEOUT_MS = 100;
3021
+ /** Hashes stable configured endpoint identity without retaining credentials or endpoint details. */
3022
+ function buildScheduledCodexAppServerConnectionIdentity(appServer) {
3023
+ const start = appServer.start;
3024
+ return crypto.createHash("sha256").update("openclaw:codex:scheduled-app-server:v1\0").update(JSON.stringify({
3025
+ transport: start.transport,
3026
+ command: start.command,
3027
+ commandSource: start.commandSource ?? null,
3028
+ args: start.args,
3029
+ cwd: start.cwd ?? null,
3030
+ url: start.url ?? null,
3031
+ homeScope: start.homeScope ?? null,
3032
+ connectionClass: appServer.connectionClass,
3033
+ remoteWorkspaceRoot: appServer.remoteWorkspaceRoot ?? null
3034
+ })).digest("hex");
3035
+ }
3036
+ function resolveScheduledCodexAppCreatorCaptureDecision(params) {
3037
+ if (!params.appsMayBeVisible) return {
3038
+ required: false,
3039
+ supported: false
3040
+ };
3041
+ const unavailableReason = params.authenticatedScheduledMode ? "A scheduled Codex continuation cannot create new app-authorized automations. Recreate it from a fresh authenticated owner turn; no automation changes were saved." : params.usesSupervisionConnection ? "Codex apps are visible through a supervised connection that cannot capture creator authority. Use an isolated prepared-profile Codex creator turn; no automation changes were saved." : params.homeScope === "user" ? "Codex apps are visible through a user-home runtime that cannot capture isolated creator authority. Use an agent-scoped prepared-profile Codex creator turn; no automation changes were saved." : !params.hasPreparedAccountIdentity && !params.hasConfiguredAppServerIdentity ? "Codex app authority requires either a prepared ChatGPT profile or an isolated configured app-server identity. Reauthenticate the selected Codex profile or configured app-server, then retry; no automation changes were saved." : void 0;
3042
+ return {
3043
+ required: true,
3044
+ supported: !unavailableReason,
3045
+ ...unavailableReason ? { unavailableReason } : {}
3046
+ };
2641
3047
  }
2642
- function resolveCodexThreadRolloutPath(thread) {
2643
- const rolloutPath = thread.path?.trim();
2644
- if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
2645
- return rolloutPath;
3048
+ function normalizeApprovalMode(value) {
3049
+ return value === "allow" || value === "deny" || value === "auto" || value === "ask" ? value : void 0;
3050
+ }
3051
+ function normalizeAppToolApprovalMode(value) {
3052
+ return value === "auto" || value === "prompt" || value === "writes" || value === "approve" ? value : void 0;
3053
+ }
3054
+ function defaultApprovalMode(entry) {
3055
+ return entry.destructiveApprovalMode ?? (entry.allowDestructiveActions ? "allow" : "deny");
3056
+ }
3057
+ function parseScheduledCodexAppAuthority(authority) {
3058
+ if (!authority || authority.runtimeId !== "codex") return;
3059
+ if (authority.version !== 1) throw new Error("Unsupported Codex scheduled authority version; reauthorize this automation.");
3060
+ if (authority.namespace !== CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE) throw new Error(`Unsupported Codex scheduled authority namespace ${authority.namespace}; reauthorize this automation.`);
3061
+ const payload = asOptionalRecord(authority.payload);
3062
+ const auth = asOptionalRecord(payload?.auth);
3063
+ const profileId = normalizeOptionalString(auth?.profileId);
3064
+ const connectionFingerprint = normalizeOptionalString(auth?.connectionFingerprint);
3065
+ const managedRequirementsFingerprint = normalizeOptionalString(auth?.managedRequirementsFingerprint);
3066
+ const accountId = normalizeOptionalString(auth?.accountId);
3067
+ const parsedAuth = auth?.kind === "configured-app-server" && connectionFingerprint && managedRequirementsFingerprint ? {
3068
+ kind: "configured-app-server",
3069
+ connectionFingerprint,
3070
+ managedRequirementsFingerprint
3071
+ } : auth?.kind === void 0 && profileId && accountId ? {
3072
+ profileId,
3073
+ accountId
3074
+ } : void 0;
3075
+ if (payload?.version !== 1 || !parsedAuth || !Array.isArray(payload.apps)) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
3076
+ const seen = /* @__PURE__ */ new Set();
3077
+ return {
3078
+ version: 1,
3079
+ auth: parsedAuth,
3080
+ apps: payload.apps.map((raw) => {
3081
+ const app = asOptionalRecord(raw);
3082
+ const id = normalizeOptionalString(app?.id);
3083
+ const destructiveApprovalMode = normalizeApprovalMode(app?.destructiveApprovalMode);
3084
+ const rawTools = asOptionalRecord(app?.tools);
3085
+ if (!id || seen.has(id) || typeof app?.allowDestructiveActions !== "boolean" || typeof app.allowOpenWorld !== "boolean" || !destructiveApprovalMode || !rawTools) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
3086
+ seen.add(id);
3087
+ const tools = {};
3088
+ for (const [name, rawMode] of Object.entries(rawTools)) {
3089
+ const toolName = normalizeOptionalString(name);
3090
+ const mode = normalizeAppToolApprovalMode(rawMode);
3091
+ if (!toolName || !mode) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
3092
+ tools[toolName] = mode;
3093
+ }
3094
+ return {
3095
+ id,
3096
+ allowDestructiveActions: app.allowDestructiveActions,
3097
+ allowOpenWorld: app.allowOpenWorld,
3098
+ destructiveApprovalMode,
3099
+ tools
3100
+ };
3101
+ })
3102
+ };
2646
3103
  }
2647
- async function resumeExistingCodexThread(params, context) {
2648
- const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
2649
- let resumeReservation;
2650
- try {
2651
- const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
2652
- const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
2653
- action: "resume",
2654
- binding: resumeBinding
2655
- }) ?? {
2656
- configPatch: params.finalConfigPatch,
2657
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
2658
- };
2659
- const pluginAppsConfigPatch = context.prebuiltPluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
2660
- const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
2661
- const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
2662
- threadId: resumeBinding.threadId,
2663
- cwd: params.cwd,
2664
- authProfileId,
2665
- model: startModelSelection.model,
2666
- modelProvider: startModelProvider,
2667
- preserveNativeModel: resumeBinding.preserveNativeModel === true,
2668
- appServer: params.appServer,
2669
- dynamicTools: params.dynamicTools,
2670
- developerInstructions: params.developerInstructions,
2671
- config: resumeConfig,
2672
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2673
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2674
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2675
- webSearchAllowed: params.webSearchAllowed,
2676
- hostSystemAgentActive,
2677
- restrictedToolSurfaceInheritedMcpServerNames,
2678
- shellEnvironment: params.shellEnvironment,
2679
- disableLoginShell: params.disableLoginShell
2680
- }));
2681
- const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
2682
- throwIfAborted();
2683
- if (resumeBinding.preserveNativeModel === true) {
2684
- const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
2685
- threadId: resumeBinding.threadId,
2686
- includeTurns: false
2687
- }, { signal: params.signal }));
2688
- throwIfAborted();
2689
- if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
2690
- }
2691
- resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
2692
- const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
2693
- client: params.client,
2694
- abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
2695
- request: resumeParams,
2696
- signal: params.signal
2697
- }));
2698
- if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
2699
- await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
2700
- } catch (error) {
2701
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2702
- throw new CodexRestrictedToolSurfaceAttestationError(error);
2703
- }
2704
- throwIfAborted();
2705
- const boundAuthProfileId = authProfileId;
2706
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
2707
- const resumePatch = {
2708
- clientId: resolveCodexAppServerClientInstanceId(params.client),
2709
- cwd: params.cwd,
2710
- rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
2711
- authProfileId: boundAuthProfileId,
2712
- model: response.model ?? resumeParams.model ?? params.params.modelId,
2713
- preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
2714
- modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
2715
- dynamicToolsFingerprint,
2716
- dynamicToolsContainDeferred,
2717
- webSearchThreadConfigFingerprint,
2718
- nativeSkillIsolationFingerprint,
2719
- userMcpServersFingerprint,
2720
- mcpServersFingerprint: nextMcpServersFingerprint,
2721
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
2722
- ringZeroConfigFingerprint,
2723
- ringZeroClientInstanceId,
2724
- nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
2725
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2726
- networkProxyConfigFingerprint,
2727
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
2728
- appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
2729
- pluginAppsFingerprint: context.prebuiltPluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
2730
- pluginAppsInputFingerprint: context.prebuiltPluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
2731
- pluginAppPolicyContext: context.prebuiltPluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
2732
- contextEngine: contextEngineBinding,
2733
- environmentSelectionFingerprint
2734
- };
2735
- if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2736
- kind: "patch",
2737
- threadId: resumeBinding.threadId,
2738
- patch: resumePatch
2739
- }))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
2740
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2741
- sessionId: params.params.sessionId,
2742
- sessionKey: params.params.sessionKey,
2743
- threadId: response.thread.id,
2744
- engineId: contextEngineBinding.engineId,
2745
- epoch: contextEngineBinding.projection?.epoch,
2746
- fingerprint: contextEngineBinding.projection?.fingerprint,
2747
- action: "resumed"
2748
- });
2749
- lifecycleTiming.mark("thread-ready");
2750
- lifecycleTiming.logSummary({
2751
- runId: params.params.runId,
2752
- sessionId: params.params.sessionId,
2753
- sessionKey: params.params.sessionKey,
2754
- threadId: response.thread.id,
2755
- action: "resumed"
3104
+ async function readCodexScheduledAppToolsByApp(params) {
3105
+ const toolsByApp = /* @__PURE__ */ new Map();
3106
+ const seenCursors = /* @__PURE__ */ new Set();
3107
+ let cursor;
3108
+ for (let page = 0; page < MCP_STATUS_MAX_PAGES; page += 1) {
3109
+ const response = await params.request("mcpServerStatus/list", {
3110
+ ...params.threadId ? { threadId: params.threadId } : {},
3111
+ detail: "toolsAndAuthOnly",
3112
+ limit: MCP_STATUS_PAGE_SIZE,
3113
+ ...cursor ? { cursor } : {}
2756
3114
  });
2757
- const activeTurnIds = readActiveCodexTurnIdsFromResume(response);
2758
- return {
2759
- ...resumeBinding,
2760
- threadId: response.thread.id,
2761
- ...resumePatch,
2762
- liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
2763
- ...resumeParams,
2764
- model: resumeBinding.preserveNativeModel === true ? null : response.model ?? resumeParams.model ?? null,
2765
- requestedModel: resumeBinding.preserveNativeModel === true ? null : resumeParams.model ?? null,
2766
- modelProvider: resumeBinding.preserveNativeModel === true ? null : resumePatch.modelProvider ?? null,
2767
- requestedModelProvider: resumeBinding.preserveNativeModel === true ? null : resumeParams.modelProvider ?? resumePatch.modelProvider ?? null
2768
- }, authProfileId, dynamicToolsFingerprint),
2769
- lifecycle: {
2770
- action: "resumed",
2771
- ...activeTurnIds.length ? { activeTurnIds } : {}
3115
+ if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned invalid scheduled app inventory");
3116
+ for (const status of response.data) {
3117
+ if (!isJsonObject(status) || !isJsonObject(status.tools)) throw new Error("Codex scheduled app inventory contained an invalid server status");
3118
+ if (status.name !== CODEX_APPS_MCP_SERVER) continue;
3119
+ for (const [toolName, tool] of Object.entries(status.tools)) {
3120
+ const connectorId = readCodexMcpToolConnectorId(tool);
3121
+ if (connectorId) {
3122
+ const tools = toolsByApp.get(connectorId) ?? /* @__PURE__ */ new Map();
3123
+ const metadata = asOptionalRecord(tool);
3124
+ const annotations = asOptionalRecord(metadata?.annotations);
3125
+ tools.set(toolName, {
3126
+ title: typeof metadata?.title === "string" ? metadata.title : void 0,
3127
+ destructiveHint: annotations?.destructiveHint === false ? false : void 0,
3128
+ openWorldHint: annotations?.openWorldHint === false ? false : void 0
3129
+ });
3130
+ toolsByApp.set(connectorId, tools);
3131
+ }
2772
3132
  }
2773
- };
2774
- } catch (error) {
2775
- resumeReservation?.release();
2776
- if (isCodexAppServerStartSelectionChangedError(error)) throw error;
2777
- if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
2778
- await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
2779
- throw error;
2780
3133
  }
2781
- if (error instanceof CodexAdoptedThreadActiveError) throw error;
2782
- if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
2783
- const resumeRejected = error instanceof CodexAppServerRpcError;
2784
- if (!await unsubscribeCodexThreadBestEffort(params.client, {
2785
- threadId: resumeBinding.threadId,
2786
- timeoutMs: 5e3
2787
- }) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
2788
- if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
2789
- embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
2790
- await clearCurrentBinding("rotating a stale thread binding");
3134
+ if (response.nextCursor !== void 0 && response.nextCursor !== null && typeof response.nextCursor !== "string") throw new Error("Codex scheduled app inventory returned an invalid pagination cursor");
3135
+ cursor = response.nextCursor;
3136
+ if (!cursor) return toolsByApp;
3137
+ if (seenCursors.has(cursor)) throw new Error("Codex app connector inventory repeated its pagination cursor");
3138
+ seenCursors.add(cursor);
2791
3139
  }
3140
+ throw new Error("Codex app connector inventory exceeded its bounded page limit");
3141
+ }
3142
+ /** Reads current account policy and connector-backed tool metadata under one caller deadline. */
3143
+ async function readCurrentCodexScheduledAppPolicy(params) {
3144
+ const [configResponse, toolsByApp] = await Promise.all([params.request("config/read", {
3145
+ includeLayers: false,
3146
+ ...params.configCwd ? { cwd: params.configCwd } : {}
3147
+ }), readCodexScheduledAppToolsByApp(params)]);
3148
+ if (!isJsonObject(configResponse)) throw new Error("Codex config/read returned an invalid scheduled app policy response");
3149
+ return {
3150
+ config: isJsonObject(configResponse.config) ? configResponse.config : {},
3151
+ toolsByApp
3152
+ };
2792
3153
  }
2793
- async function startFreshCodexThread(params, context) {
2794
- const clientId = resolveCodexAppServerClientInstanceId(params.client);
2795
- const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding, replacementPredecessor } = context;
2796
- const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2797
- const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2798
- configPatch: params.finalConfigPatch,
2799
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
3154
+ function readCurrentToolPolicy(config, appId, toolName, metadata, fallbackApprovalMode = "auto") {
3155
+ const apps = asOptionalRecord(config.apps);
3156
+ const app = asOptionalRecord(apps?.[appId]);
3157
+ const defaults = asOptionalRecord(apps?.["_default"]);
3158
+ const tools = asOptionalRecord(app?.tools);
3159
+ const tool = asOptionalRecord(tools?.[toolName] ?? (metadata?.title !== void 0 ? tools?.[metadata.title] : void 0));
3160
+ const defaultToolsEnabled = app?.default_tools_enabled;
3161
+ return {
3162
+ enabled: (app ? app.enabled !== false : defaults?.enabled !== false) && (typeof tool?.enabled === "boolean" ? tool.enabled : typeof defaultToolsEnabled === "boolean" ? defaultToolsEnabled : appToolHintsAllowed(metadata, {
3163
+ allowDestructiveActions: (app?.destructive_enabled ?? defaults?.destructive_enabled) !== false,
3164
+ allowOpenWorld: (app?.open_world_enabled ?? defaults?.open_world_enabled) !== false
3165
+ })),
3166
+ approvalMode: normalizeAppToolApprovalMode(tool?.approval_mode) ?? normalizeAppToolApprovalMode(app?.default_tools_approval_mode) ?? normalizeAppToolApprovalMode(defaults?.default_tools_approval_mode) ?? fallbackApprovalMode
2800
3167
  };
2801
- const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
2802
- const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
2803
- cwd: params.cwd,
2804
- dynamicTools: params.dynamicTools,
2805
- appServer: params.appServer,
2806
- developerInstructions: params.developerInstructions,
2807
- config,
2808
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2809
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2810
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2811
- webSearchAllowed: params.webSearchAllowed,
2812
- environmentSelection: params.environmentSelection,
2813
- model: startModelSelection.model,
2814
- modelProvider: startModelProvider,
2815
- hostSystemAgentActive,
2816
- restrictedToolSurfaceInheritedMcpServerNames,
2817
- shellEnvironment: params.shellEnvironment,
2818
- disableLoginShell: params.disableLoginShell
2819
- }));
2820
- const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
2821
- const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
2822
- try {
2823
- return await params.client.request("thread/start", startParams, { signal: params.signal });
2824
- } catch (error) {
2825
- if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
2826
- throw error;
2827
- }
2828
- }));
2829
- const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
2830
- if (provisionalAppIds?.length) try {
2831
- await lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
2832
- client: params.client,
2833
- threadId: response.thread.id,
2834
- appIds: provisionalAppIds,
3168
+ }
3169
+ function appToolHintsAllowed(tool, policy) {
3170
+ return (policy.allowDestructiveActions || tool?.destructiveHint === false) && (policy.allowOpenWorld !== false || tool?.openWorldHint === false);
3171
+ }
3172
+ /** Captures only apps callable on the exact active Codex client/thread. */
3173
+ async function captureScheduledCodexAppAuthority(params) {
3174
+ const requestedTimeoutMs = params.timeoutMs ?? CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS;
3175
+ const timeoutMs = Math.min(CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS, Math.max(CODEX_APP_AUTHORITY_CAPTURE_MIN_TIMEOUT_MS, Number.isFinite(requestedTimeoutMs) ? Math.floor(requestedTimeoutMs) : CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS));
3176
+ const deadlineMs = Date.now() + timeoutMs;
3177
+ const boundedClient = { request: ((method, requestParams) => {
3178
+ const remainingTimeoutMs = deadlineMs - Date.now();
3179
+ if (remainingTimeoutMs <= 0) throw new CodexScheduledAppAuthorityCaptureTimeoutError();
3180
+ return params.client.request(method, requestParams, {
3181
+ timeoutMs: remainingTimeoutMs,
2835
3182
  signal: params.signal
2836
- }));
3183
+ });
3184
+ }) };
3185
+ let installed;
3186
+ let currentPolicy;
3187
+ let auth;
3188
+ const creatorAuth = params.auth;
3189
+ try {
3190
+ [installed, currentPolicy, auth] = await withAbortableTimeout({
3191
+ promise: Promise.all([
3192
+ boundedClient.request("app/installed", {
3193
+ threadId: params.threadId,
3194
+ forceRefresh: false
3195
+ }),
3196
+ readCurrentCodexScheduledAppPolicy({
3197
+ request: (method, requestParams) => boundedClient.request(method, requestParams),
3198
+ threadId: params.threadId,
3199
+ configCwd: params.configCwd
3200
+ }),
3201
+ creatorAuth.kind === "prepared-profile" ? Promise.resolve({
3202
+ profileId: creatorAuth.profileId,
3203
+ accountId: creatorAuth.accountId
3204
+ }) : readCodexManagedRequirementsFingerprint(boundedClient, params.signal).then((managedRequirementsFingerprint) => ({
3205
+ kind: creatorAuth.kind,
3206
+ connectionFingerprint: creatorAuth.connectionFingerprint,
3207
+ managedRequirementsFingerprint
3208
+ }))
3209
+ ]),
3210
+ timeoutMs,
3211
+ signal: params.signal,
3212
+ timeoutMessage: "Codex scheduled app authority capture deadline elapsed",
3213
+ createTimeoutError: () => new CodexScheduledAppAuthorityCaptureTimeoutError()
3214
+ });
2837
3215
  } catch (error) {
2838
- if (!await discardUnattestedCodexPluginThread({
2839
- client: params.client,
2840
- threadId: response.thread.id,
2841
- ephemeral: startParams.ephemeral === true
2842
- })) {
2843
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2844
- throw new CodexAppServerUnsafeSubscriptionError("Codex plugin app attestation cleanup failed", { cause: error });
3216
+ if (params.signal?.aborted || !(error instanceof CodexScheduledAppAuthorityCaptureTimeoutError) && !isCodexAppServerRequestTimeoutError(error)) throw error;
3217
+ throw new Error(`Codex app authority capture exceeded its ${timeoutMs} ms total budget. No automation changes were saved; retry after Codex app inventory is responsive.`, { cause: error });
3218
+ }
3219
+ const callableIds = new Set(installed.apps.filter((app) => app.enabled && app.callable).map((app) => app.id));
3220
+ const apps = Object.entries(params.policyContext.apps).filter(([id]) => callableIds.has(id) && currentPolicy.toolsByApp.has(id)).map(([id, policy]) => ({
3221
+ id,
3222
+ allowDestructiveActions: policy.allowDestructiveActions,
3223
+ allowOpenWorld: policy.allowOpenWorld !== false,
3224
+ destructiveApprovalMode: defaultApprovalMode(policy),
3225
+ tools: Object.fromEntries([...currentPolicy.toolsByApp.get(id)?.keys() ?? []].toSorted().map((toolName) => [toolName, readCurrentToolPolicy(currentPolicy.config, id, toolName, currentPolicy.toolsByApp.get(id)?.get(toolName), appApprovalCeiling(defaultApprovalMode(policy))).approvalMode]))
3226
+ })).toSorted((left, right) => left.id.localeCompare(right.id));
3227
+ if (apps.length === 0) return;
3228
+ return {
3229
+ version: 1,
3230
+ runtimeId: "codex",
3231
+ namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
3232
+ payload: {
3233
+ version: 1,
3234
+ auth,
3235
+ apps
2845
3236
  }
2846
- throw error;
3237
+ };
3238
+ }
3239
+ var CodexScheduledAppAuthorityCaptureTimeoutError = class extends Error {
3240
+ constructor() {
3241
+ super("Codex scheduled app authority capture deadline elapsed");
3242
+ this.name = "CodexScheduledAppAuthorityCaptureTimeoutError";
2847
3243
  }
2848
- const rolloutPath = resolveCodexThreadRolloutPath(response.thread);
2849
- if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
2850
- await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, startParams.config, params.signal));
2851
- } catch (error) {
2852
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2853
- throw error;
3244
+ };
3245
+ const APPROVAL_RANK = {
3246
+ deny: 0,
3247
+ ask: 1,
3248
+ auto: 2,
3249
+ allow: 3
3250
+ };
3251
+ function stricterApprovalMode(left, right) {
3252
+ return APPROVAL_RANK[left] <= APPROVAL_RANK[right] ? left : right;
3253
+ }
3254
+ function intersectToolApprovalMode(captured, current) {
3255
+ if (captured === current) return captured;
3256
+ if (captured === "prompt" || current === "prompt") return "prompt";
3257
+ if (captured === "approve") return current;
3258
+ if (current === "approve") return captured;
3259
+ return "prompt";
3260
+ }
3261
+ function appApprovalCeiling(mode) {
3262
+ if (mode === "allow") return "approve";
3263
+ return mode === "ask" ? "prompt" : "auto";
3264
+ }
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
+ /** Intersects a stored app-ID cap with current policy without admitting new apps. */
3271
+ function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authority, currentPolicy = {
3272
+ config: {},
3273
+ toolsByApp: /* @__PURE__ */ new Map()
3274
+ }) {
3275
+ const scheduled = parseScheduledCodexAppAuthority(authority);
3276
+ if (!scheduled) return config;
3277
+ const omittedAppIds = scheduled.apps.map((app) => app.id).filter((id) => {
3278
+ const currentTools = currentPolicy.toolsByApp.get(id);
3279
+ return !Object.hasOwn(config.policyContext.apps, id) || !currentTools || currentTools.size === 0;
3280
+ }).toSorted();
3281
+ if (omittedAppIds.length > 0) {
3282
+ const visibleIds = omittedAppIds.slice(0, 10).join(", ");
3283
+ const remaining = omittedAppIds.length - Math.min(omittedAppIds.length, 10);
3284
+ throw new AgentHarnessPreflightError(`Scheduled Codex apps are unavailable under the current policy or account: ${visibleIds}${remaining > 0 ? ` (and ${remaining} more)` : ""}. Restore access or reauthorize the automation from a fresh authenticated Codex owner turn.`);
2854
3285
  }
2855
- try {
2856
- throwIfAborted();
2857
- } catch (error) {
2858
- if (replacementPredecessor) {
2859
- if (!await discardUnattestedCodexPluginThread({
2860
- client: params.client,
2861
- threadId: response.thread.id,
2862
- ephemeral: startParams.ephemeral === true
2863
- })) {
2864
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2865
- throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after an aborted binding replacement", { cause: error });
2866
- }
2867
- }
2868
- throw error;
3286
+ const capturedById = new Map(scheduled.apps.map((app) => [app.id, app]));
3287
+ const apps = {};
3288
+ for (const [id, current] of Object.entries(config.policyContext.apps)) {
3289
+ const captured = capturedById.get(id);
3290
+ if (!captured) continue;
3291
+ apps[id] = {
3292
+ ...current,
3293
+ allowDestructiveActions: current.allowDestructiveActions && captured.allowDestructiveActions,
3294
+ allowOpenWorld: current.allowOpenWorld !== false && captured.allowOpenWorld,
3295
+ destructiveApprovalMode: stricterApprovalMode(defaultApprovalMode(current), captured.destructiveApprovalMode)
3296
+ };
2869
3297
  }
2870
- const modelProvider = resolveCodexAppServerModelProvider({
2871
- provider: params.params.provider,
2872
- authProfileId: params.params.authProfileId,
2873
- authProfileStore: params.params.authProfileStore,
2874
- agentDir: params.params.agentDir,
2875
- config: params.params.config
2876
- });
2877
- const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
2878
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2879
- if (!preserveExistingBinding) {
2880
- const nextBinding = {
2881
- threadId: response.thread.id,
2882
- ...clientId ? { clientId } : {},
2883
- cwd: params.cwd,
2884
- ...rolloutPath ? { rolloutPath } : {},
2885
- authProfileId: params.params.authProfileId,
2886
- agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
2887
- model: response.model ?? startParams.model ?? params.params.modelId,
2888
- modelProvider: bindingModelProvider,
2889
- dynamicToolsFingerprint,
2890
- dynamicToolsContainDeferred,
2891
- webSearchThreadConfigFingerprint,
2892
- nativeSkillIsolationFingerprint,
2893
- userMcpServersFingerprint,
2894
- mcpServersFingerprint: nextMcpServersFingerprint,
2895
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
2896
- ringZeroConfigFingerprint,
2897
- ringZeroClientInstanceId,
2898
- nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
2899
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2900
- networkProxyConfigFingerprint,
2901
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2902
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2903
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2904
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2905
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2906
- contextEngine: contextEngineBinding,
2907
- environmentSelectionFingerprint
2908
- };
2909
- const cleanupUncommittedSuccessor = async (cause) => {
2910
- if (!await discardUnattestedCodexPluginThread({
2911
- client: params.client,
2912
- threadId: response.thread.id,
2913
- ephemeral: startParams.ephemeral === true
2914
- })) {
2915
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2916
- throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
2917
- }
2918
- };
2919
- const managedSourceHomeId = resolveManagedThreadSourceHomeId(params);
2920
- await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
2921
- sourceHomeId: managedSourceHomeId,
2922
- threadId: response.thread.id,
2923
- ...rolloutPath ? { rolloutPath } : {}
3298
+ 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);
3300
+ const appsPatch = asOptionalRecord(configPatch.apps);
3301
+ for (const [appId, captured] of capturedById) {
3302
+ const appPatch = asOptionalRecord(appsPatch?.[appId]);
3303
+ if (!appPatch || !Object.hasOwn(apps, appId)) continue;
3304
+ const currentApp = apps[appId];
3305
+ if (!currentApp) continue;
3306
+ const storedAppCeiling = appApprovalCeiling(captured.destructiveApprovalMode);
3307
+ const currentAppCeiling = appApprovalCeiling(defaultApprovalMode(currentApp));
3308
+ const tools = currentPolicy.toolsByApp.get(appId) ?? /* @__PURE__ */ new Map();
3309
+ appPatch.tools = Object.fromEntries([...tools.keys()].toSorted().map((toolName) => {
3310
+ const capturedMode = captured.tools[toolName] ?? storedAppCeiling;
3311
+ const currentToolPolicy = readCurrentToolPolicy(currentPolicy.config, appId, toolName, tools.get(toolName), currentAppCeiling);
3312
+ return [toolName, {
3313
+ enabled: currentToolPolicy.enabled && appToolHintsAllowed(tools.get(toolName), currentApp),
3314
+ approval_mode: intersectToolApprovalMode(intersectToolApprovalMode(capturedMode, storedAppCeiling), intersectToolApprovalMode(currentToolPolicy.approvalMode, currentAppCeiling))
3315
+ }];
2924
3316
  }));
2925
- let committed;
2926
- try {
2927
- committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
2928
- kind: "replace-thread",
2929
- expectedThreadId: replacementPredecessor.threadId,
2930
- binding: nextBinding
2931
- } : {
2932
- kind: "set",
2933
- if: { kind: "absent" },
2934
- binding: nextBinding
2935
- }));
2936
- } catch (error) {
2937
- if (replacementPredecessor) await cleanupUncommittedSuccessor(error);
2938
- throw error;
2939
- }
2940
- if (!committed) {
2941
- if (replacementPredecessor) await cleanupUncommittedSuccessor();
2942
- throw new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread");
2943
- }
2944
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2945
- sessionId: params.params.sessionId,
2946
- sessionKey: params.params.sessionKey,
2947
- threadId: response.thread.id,
2948
- engineId: contextEngineBinding.engineId,
2949
- epoch: contextEngineBinding.projection?.epoch,
2950
- fingerprint: contextEngineBinding.projection?.fingerprint,
2951
- action: rotatedContextEngineBinding ? "rotated" : "started"
2952
- });
2953
- }
2954
- lifecycleTiming.mark("thread-ready");
2955
- lifecycleTiming.logSummary({
2956
- runId: params.params.runId,
2957
- sessionId: params.params.sessionId,
2958
- sessionKey: params.params.sessionKey,
2959
- threadId: response.thread.id,
2960
- action: rotatedContextEngineBinding ? "rotated" : "started"
2961
- });
2962
- return buildStartedCodexThreadBinding({
2963
- bindingModelProvider,
2964
- clientId,
2965
- context,
2966
- finalConfigPatch,
2967
- nextMcpServersFingerprint,
2968
- params,
2969
- pluginThreadConfig,
2970
- response,
2971
- rolloutPath,
2972
- startModelProvider: requestModelProvider ?? startModelProvider,
2973
- startParams,
2974
- modelProvider
2975
- });
2976
- }
2977
- //#endregion
2978
- //#region extensions/codex/src/app-server/dynamic-tool-response-state.ts
2979
- /** Retains the host-owned app preview without adding it to Codex's response payload. */
2980
- function withDynamicToolTranscriptDetails(response, details) {
2981
- if (details === void 0) return response;
2982
- Object.defineProperty(response, "transcriptDetails", {
2983
- configurable: true,
2984
- enumerable: false,
2985
- value: details
2986
- });
2987
- return response;
2988
- }
2989
- function withDynamicToolTerminalResolution(response, terminalResolution) {
2990
- if (terminalResolution) {
2991
- Object.defineProperties(response, {
2992
- terminalResolution: {
2993
- configurable: true,
2994
- enumerable: false,
2995
- value: terminalResolution
2996
- },
2997
- executionStarted: {
2998
- configurable: true,
2999
- enumerable: false,
3000
- value: terminalResolution.executionStarted
3001
- },
3002
- ...terminalResolution.executedArguments ? { executedArguments: {
3003
- configurable: true,
3004
- enumerable: false,
3005
- value: terminalResolution.executedArguments
3006
- } } : {}
3007
- });
3008
- withDynamicToolSideEffectEvidence(response, terminalResolution.sideEffectEvidence);
3009
- }
3010
- return response;
3011
- }
3012
- function withDynamicToolExecutionState(response, state) {
3013
- Object.defineProperties(response, {
3014
- executedArguments: {
3015
- configurable: true,
3016
- enumerable: false,
3017
- value: state.executedArguments
3018
- },
3019
- executionStarted: {
3020
- configurable: true,
3021
- enumerable: false,
3022
- value: state.executionStarted
3023
- }
3024
- });
3025
- return withDynamicToolSideEffectEvidence(response, state.sideEffectEvidence === true);
3026
- }
3027
- function withDynamicToolSideEffectEvidence(response, sideEffectEvidence) {
3028
- if (!sideEffectEvidence) {
3029
- delete response.sideEffectEvidence;
3030
- return response;
3031
3317
  }
3032
- Object.defineProperty(response, "sideEffectEvidence", {
3033
- configurable: true,
3034
- enumerable: false,
3035
- value: true
3036
- });
3037
- return response;
3038
- }
3039
- function createFailedDynamicToolResponse(message, options) {
3040
- const response = {
3041
- contentItems: [{
3042
- type: "inputText",
3043
- text: message
3044
- }],
3045
- success: false
3318
+ const fingerprint = crypto.createHash("sha256").update(stableStringify({
3319
+ version: 1,
3320
+ namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
3321
+ authority: scheduled,
3322
+ inputFingerprint: config.inputFingerprint,
3323
+ policyContext,
3324
+ configPatch
3325
+ })).digest("hex");
3326
+ return {
3327
+ ...config,
3328
+ fingerprint,
3329
+ configPatch,
3330
+ provisionalAppIds: Object.keys(apps).toSorted(),
3331
+ policyContext
3046
3332
  };
3047
- Object.defineProperties(response, {
3048
- diagnosticTerminalReason: {
3049
- configurable: true,
3050
- enumerable: false,
3051
- value: options?.terminalReason ?? "failed"
3052
- },
3053
- diagnosticTerminalType: {
3054
- configurable: true,
3055
- enumerable: false,
3056
- value: "error"
3057
- }
3058
- });
3059
- if (options?.executionStarted !== void 0) Object.defineProperty(response, "executionStarted", {
3060
- configurable: true,
3061
- enumerable: false,
3062
- value: options.executionStarted
3063
- });
3064
- if (options?.executedArguments !== void 0) Object.defineProperty(response, "executedArguments", {
3065
- configurable: true,
3066
- enumerable: false,
3067
- value: options.executedArguments
3068
- });
3069
- return withDynamicToolSideEffectEvidence(response, options?.sideEffectEvidence === true);
3070
3333
  }
3071
- //#endregion
3072
- //#region extensions/codex/src/app-server/tool-abort-terminal-reason.ts
3073
- /** Leaf helper shared by native and dynamic tool diagnostics. */
3074
- const CODEX_TIMEOUT_ABORT_REASONS = /* @__PURE__ */ new Set([
3075
- "codex_startup_timeout",
3076
- "turn_completion_idle_timeout",
3077
- "turn_progress_idle_timeout",
3078
- "turn_terminal_idle_timeout"
3079
- ]);
3080
- /** Preserves timeout provenance when an enclosing run aborts an active tool. */
3081
- function resolveCodexToolAbortTerminalReason(signal) {
3082
- try {
3083
- const reason = signal.reason;
3084
- if (typeof reason === "string") {
3085
- if (CODEX_TIMEOUT_ABORT_REASONS.has(reason)) return "timed_out";
3086
- return reason === "client_closed" ? "failed" : "cancelled";
3087
- }
3088
- if (reason && typeof reason === "object") {
3089
- const record = reason;
3090
- if (record.name === "TimeoutError" || record.reason === "timeout") return "timed_out";
3091
- }
3092
- } catch {
3093
- return "cancelled";
3334
+ /** Returns the managed-requirements identity captured for a configured app-server job. */
3335
+ function readScheduledCodexAppManagedRequirementsFingerprint(authority) {
3336
+ const auth = parseScheduledCodexAppAuthority(authority)?.auth;
3337
+ return auth?.kind === "configured-app-server" ? auth.managedRequirementsFingerprint : void 0;
3338
+ }
3339
+ function assertScheduledCodexAppAuthorityRuntime(connection, params) {
3340
+ const scheduledAuth = parseScheduledCodexAppAuthority(params.scheduledRuntimeAuthority)?.auth;
3341
+ if (!scheduledAuth) return;
3342
+ if (params.trigger !== "cron" || connection.usesSupervisionConnection || connection.appServer.start.homeScope === "user") throw new AgentHarnessPreflightError("This automation's Codex app authority requires an isolated scheduled runtime. Reauthorize it from a supported Codex creator turn.");
3343
+ if (scheduledAuth.kind === "configured-app-server") {
3344
+ if (buildScheduledCodexAppServerConnectionIdentity(connection.appServer) !== scheduledAuth.connectionFingerprint) throw new AgentHarnessPreflightError("This automation was authorized for a different configured Codex app-server. Restore that connection or reauthorize the automation from a fresh owner turn.");
3345
+ return;
3094
3346
  }
3095
- return "cancelled";
3347
+ const prepared = connection.startupPreparedAuth;
3348
+ if (prepared?.kind !== "profile" || prepared.profileId !== scheduledAuth.profileId || prepared.snapshot?.loginParams.type !== "chatgptAuthTokens" || prepared.snapshot.chatgptAccountId !== scheduledAuth.accountId) throw new AgentHarnessPreflightError(`This automation was authorized for Codex profile ${scheduledAuth.profileId}, but that exact prepared account is not active. Restore the profile or reauthorize the automation from a fresh owner turn.`);
3349
+ }
3350
+ function buildLegacyScheduledCodexAppRecoveryPrompt(params) {
3351
+ if (params.trigger !== "cron" || !params.scheduledRuntimeAuthorityRecoveryRequired || params.scheduledRuntimeAuthority) return;
3352
+ return "Scheduled Codex app access is unavailable because this automation predates runtime-specific app authority capture. Tell the operator to recreate or reauthorize it from a fresh authenticated Codex owner turn; do not claim an app action succeeded.";
3353
+ }
3354
+ /** Makes stored-cap identity part of thread reuse admission, including cap removal. */
3355
+ function buildScheduledCodexAppAuthorityInputFingerprint(baseFingerprint, authority) {
3356
+ const scheduled = parseScheduledCodexAppAuthority(authority);
3357
+ if (!scheduled) return baseFingerprint;
3358
+ return crypto.createHash("sha256").update(stableStringify({
3359
+ version: 1,
3360
+ namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
3361
+ baseFingerprint,
3362
+ authority: scheduled
3363
+ })).digest("hex");
3096
3364
  }
3097
3365
  //#endregion
3098
- //#region extensions/codex/src/app-server/dynamic-tool-execution.ts
3099
- /**
3100
- * Timeout, terminal-release, and diagnostic helpers for Codex dynamic tool
3101
- * calls.
3102
- */
3103
- /** Default timeout for Codex dynamic tool calls. */
3104
- const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 9e4;
3105
- /** Hard cap for per-call Codex dynamic tool timeout overrides. */
3106
- const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
3107
- const CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS = 3e4;
3108
- const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
3109
- const CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS = 3e4;
3110
- const CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS = 3e4;
3111
- /** Timeout for image-understanding style dynamic tool calls. */
3112
- const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
3113
- /** Timeout for message-delivery dynamic tool calls. */
3114
- const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 6e5;
3115
- /** Outer default for collector waits: full swarm budget plus completion grace. */
3116
- const CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS = 63e4;
3117
- const LOG_FIELD_MAX_LENGTH = 160;
3118
- function normalizeLogField(value) {
3119
- if (typeof value !== "string") return;
3120
- const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
3121
- if (!normalized) return;
3122
- return normalized.length > LOG_FIELD_MAX_LENGTH ? `${truncateUtf16Safe(normalized, LOG_FIELD_MAX_LENGTH - 3)}...` : normalized;
3123
- }
3124
- function readNumericTimeoutMs(value) {
3125
- if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
3126
- if (typeof value === "string") {
3127
- const parsed = parseStrictNonNegativeInteger(value);
3128
- if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
3129
- }
3366
+ //#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
3367
+ const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
3368
+ const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
3369
+ 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;
3372
+ return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
3130
3373
  }
3131
- function formatDynamicToolTimeoutDetails(params) {
3132
- const tool = normalizeLogField(params.call.tool) ?? "unknown";
3133
- const baseMeta = {
3134
- tool: params.call.tool,
3135
- toolCallId: params.call.callId,
3136
- threadId: params.call.threadId,
3137
- turnId: params.call.turnId,
3138
- timeoutMs: params.timeoutMs,
3139
- timeoutKind: "codex_dynamic_tool_rpc"
3140
- };
3141
- if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
3142
- responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
3143
- consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
3144
- meta: baseMeta
3145
- };
3146
- const action = normalizeLogField(params.call.arguments.action);
3147
- const sessionId = normalizeLogField(params.call.arguments.sessionId);
3148
- const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
3149
- const actionPart = action ? ` action=${action}` : "";
3150
- const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
3151
- const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
3152
- const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
3153
- const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
3154
- return {
3155
- responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
3156
- consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
3157
- meta: {
3158
- ...baseMeta,
3159
- processAction: action,
3160
- processSessionId: sessionId,
3161
- processRequestedTimeoutMs: requestedTimeoutMs
3162
- }
3163
- };
3374
+ function formatCodexThreadLifecycleTimingSummary(params) {
3375
+ const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
3376
+ return `[trace:codex-app-server] thread lifecycle: runId=${params.runId} sessionId=${params.sessionId} sessionKey=${params.sessionKey ?? "unknown"} action=${params.action} totalMs=${params.summary.totalMs} stages=${spans}`;
3164
3377
  }
3165
- /**
3166
- * Runs a dynamic tool call with run-abort and per-call timeout handling,
3167
- * returning a Codex protocol response instead of throwing.
3168
- */
3169
- async function handleDynamicToolCallWithTimeout(params) {
3170
- let didNotifyAgentToolResult = false;
3171
- const conservativeRaceResponses = /* @__PURE__ */ new WeakSet();
3172
- const finalizeTerminal = (response) => {
3173
- const executionSnapshot = params.toolBridge.consumeToolExecutionSnapshot?.(params.call.callId);
3174
- const ownerKey = params.toolBridge.sideEffectOwnerKeyForTool?.(params.call.tool);
3175
- const observedExecutionStarted = executionSnapshot?.executionStarted ?? (conservativeRaceResponses.has(response) ? void 0 : response.executionStarted);
3176
- const terminalResolution = params.observeToolTerminal?.({
3177
- toolCallId: params.call.callId,
3178
- toolName: params.call.tool,
3179
- arguments: response.executedArguments ?? executionSnapshot?.executedArguments ?? params.call.arguments,
3180
- ...params.toolMeta ? { meta: params.toolMeta } : {},
3181
- ...ownerKey ? { ownerMutation: { ownerKey } } : {},
3182
- ...observedExecutionStarted !== void 0 ? { executionStarted: observedExecutionStarted } : {},
3183
- outcome: response.success ? "success" : "failure",
3184
- ...!response.success ? { failure: { error: readDynamicToolResponseText(response) } } : {}
3185
- });
3186
- return withDynamicToolTerminalResolution(response, terminalResolution);
3187
- };
3188
- const createFailedAfterPossibleDispatch = (message, terminalReason) => {
3189
- const response = createFailedDynamicToolResponse(message, {
3190
- executionStarted: true,
3191
- sideEffectEvidence: true,
3192
- terminalReason
3193
- });
3194
- conservativeRaceResponses.add(response);
3195
- return response;
3196
- };
3197
- const notifyAgentToolResult = (event) => {
3198
- if (didNotifyAgentToolResult) return;
3199
- didNotifyAgentToolResult = true;
3200
- try {
3201
- params.onAgentToolResult?.(event);
3202
- } catch (error) {
3203
- embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
3204
- }
3378
+ function createCodexThreadLifecycleTimingTracker(options = {}) {
3379
+ 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() {}
3205
3389
  };
3206
- const notifyFailedToolResult = (message, terminalReason = "failed") => {
3207
- notifyAgentToolResult({
3208
- toolName: params.call.tool,
3209
- result: {
3210
- content: [{
3211
- type: "text",
3212
- text: message
3213
- }],
3214
- details: {
3215
- status: terminalReason,
3216
- error: message
3217
- }
3218
- },
3219
- isError: true
3390
+ const now = options.now ?? Date.now;
3391
+ const startedAt = now();
3392
+ let didLog = false;
3393
+ const spans = [];
3394
+ const toMs = (value) => Math.max(0, Math.round(value));
3395
+ const record = (name, spanStartedAt) => {
3396
+ const currentAt = now();
3397
+ spans.push({
3398
+ name,
3399
+ durationMs: toMs(currentAt - spanStartedAt),
3400
+ elapsedMs: toMs(currentAt - startedAt)
3220
3401
  });
3221
3402
  };
3222
- if (params.signal.aborted) {
3223
- const message = "OpenClaw dynamic tool call aborted before execution.";
3224
- const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
3225
- params.onFallbackSelected?.();
3226
- notifyFailedToolResult(message, terminalReason);
3227
- return finalizeTerminal(createFailedDynamicToolResponse(message, {
3228
- executionStarted: false,
3229
- terminalReason
3230
- }));
3231
- }
3232
- const controller = new AbortController();
3233
- let timeout;
3234
- let timedOut = false;
3235
- let resolveAbort;
3236
- const abortFromRun = () => {
3237
- const message = "OpenClaw dynamic tool call aborted.";
3238
- const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
3239
- params.onFallbackSelected?.();
3240
- controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
3241
- notifyFailedToolResult(message, terminalReason);
3242
- resolveAbort?.(createFailedAfterPossibleDispatch(message, terminalReason));
3243
- };
3244
- const abortPromise = new Promise((resolve) => {
3245
- resolveAbort = resolve;
3246
- });
3247
- const timeoutPromise = new Promise((resolve) => {
3248
- const timeoutMs = clampDynamicToolTimeoutMs(params.timeoutMs);
3249
- timeout = setTimeout(() => {
3250
- timedOut = true;
3251
- const timeoutDetails = formatDynamicToolTimeoutDetails({
3252
- call: params.call,
3253
- timeoutMs
3254
- });
3255
- params.onFallbackSelected?.();
3256
- controller.abort(new Error(timeoutDetails.responseMessage));
3257
- params.onTimeout?.();
3258
- embeddedAgentLog.warn("codex dynamic tool call timed out", {
3259
- ...timeoutDetails.meta,
3260
- consoleMessage: timeoutDetails.consoleMessage
3261
- });
3262
- notifyFailedToolResult(timeoutDetails.responseMessage, "timed_out");
3263
- resolve(createFailedAfterPossibleDispatch(timeoutDetails.responseMessage, "timed_out"));
3264
- }, timeoutMs);
3265
- timeout.unref?.();
3403
+ const snapshot = () => ({
3404
+ totalMs: toMs(now() - startedAt),
3405
+ spans: spans.slice()
3266
3406
  });
3267
- try {
3268
- params.signal.addEventListener("abort", abortFromRun, { once: true });
3269
- if (params.signal.aborted) abortFromRun();
3270
- const response = await Promise.race([
3271
- params.toolBridge.handleToolCall(params.call, {
3272
- signal: controller.signal,
3273
- onAgentToolResult: notifyAgentToolResult,
3274
- toolCallOrdinal: params.toolCallOrdinal,
3275
- retainExecutionSnapshot: true
3276
- }),
3277
- abortPromise,
3278
- timeoutPromise
3279
- ]);
3280
- if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response), response.diagnosticTerminalReason ?? "failed");
3281
- return finalizeTerminal(response);
3282
- } catch (error) {
3283
- const terminalReason = params.signal.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : resolveToolExecutionErrorKind(error);
3284
- const message = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
3285
- notifyFailedToolResult(message, terminalReason);
3286
- return finalizeTerminal(createFailedAfterPossibleDispatch(message, terminalReason));
3287
- } finally {
3288
- if (timeout) clearTimeout(timeout);
3289
- params.signal.removeEventListener("abort", abortFromRun);
3290
- resolveAbort = void 0;
3291
- if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
3292
- }
3293
- }
3294
- function readDynamicToolResponseText(response) {
3295
- return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
3296
- }
3297
- /** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
3298
- function toCodexDynamicToolProtocolResponse(response) {
3299
3407
  return {
3300
- contentItems: response.contentItems,
3301
- success: response.success
3408
+ async measure(name, run) {
3409
+ const spanStartedAt = now();
3410
+ try {
3411
+ return await run();
3412
+ } finally {
3413
+ record(name, spanStartedAt);
3414
+ }
3415
+ },
3416
+ measureSync(name, run) {
3417
+ const spanStartedAt = now();
3418
+ try {
3419
+ return run();
3420
+ } finally {
3421
+ record(name, spanStartedAt);
3422
+ }
3423
+ },
3424
+ mark(name) {
3425
+ record(name, now());
3426
+ },
3427
+ logSummary(params) {
3428
+ if (didLog) return;
3429
+ const summary = snapshot();
3430
+ const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
3431
+ if (!shouldWarn && !log.isEnabled?.("trace")) return;
3432
+ didLog = true;
3433
+ const message = formatCodexThreadLifecycleTimingSummary({
3434
+ runId: params.runId,
3435
+ sessionId: params.sessionId,
3436
+ sessionKey: params.sessionKey,
3437
+ action: params.action,
3438
+ summary
3439
+ });
3440
+ const meta = {
3441
+ runId: params.runId,
3442
+ sessionId: params.sessionId,
3443
+ sessionKey: params.sessionKey,
3444
+ action: params.action,
3445
+ threadId: params.threadId,
3446
+ totalMs: summary.totalMs,
3447
+ spans: summary.spans
3448
+ };
3449
+ if (shouldWarn) log.warn(message, meta);
3450
+ else log.trace(message, meta);
3451
+ }
3302
3452
  };
3303
3453
  }
3304
- /** Adds async-started progress details when a tool result continues out of band. */
3305
- function toCodexDynamicToolProgressResponse(response, protocolResponse) {
3306
- const transcriptDetails = isJsonObject(response.transcriptDetails) ? response.transcriptDetails : void 0;
3307
- const mcpAppPreview = isJsonObject(transcriptDetails?.mcpAppPreview) ? transcriptDetails.mcpAppPreview : void 0;
3308
- const progressDetails = mcpAppPreview ? { mcpAppPreview } : void 0;
3309
- if (response.asyncStarted !== true && progressDetails === void 0) return protocolResponse;
3310
- return {
3311
- ...protocolResponse,
3312
- ...progressDetails ? { details: progressDetails } : {},
3313
- ...response.asyncStarted === true ? { details: {
3314
- ...progressDetails,
3315
- async: true,
3316
- status: "started"
3317
- } } : {}
3318
- };
3454
+ //#endregion
3455
+ //#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
3456
+ function resolveCodexThreadAgentDir(params) {
3457
+ const agentId = resolveSessionAgentIdsStrict({
3458
+ config: params.params.config,
3459
+ sessionKey: params.params.sessionKey,
3460
+ agentId: params.agentId ?? params.params.agentId
3461
+ }).sessionAgentId;
3462
+ return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
3319
3463
  }
3320
- /** Decides whether a terminal dynamic tool response can release the Codex turn. */
3321
- function shouldReleaseTurnAfterTerminalDynamicTool(state) {
3322
- return !state.completed && !state.aborted && state.responseSuccess && !state.currentTurnHadNonTerminalDynamicToolResult && state.activeAppServerTurnRequests === 0 && state.activeTurnItemIdsCount === 0 && state.pendingOpenClawDynamicToolCompletionIdsCount === 0;
3323
- }
3324
- /** Returns true when a non-async result should block terminal-release shortcuts. */
3325
- function shouldBlockTerminalReleaseForNonTerminalDynamicToolResult(response) {
3326
- return response.asyncStarted !== true;
3327
- }
3328
- /** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
3329
- function resolveTerminalDynamicToolBatchAction(state) {
3330
- if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
3331
- if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
3332
- if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
3333
- return "idle";
3334
- }
3335
- /** Returns true for diagnostic events that terminate a dynamic tool call. */
3336
- function isDynamicToolTerminalDiagnosticEvent(event) {
3337
- return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
3338
- }
3339
- /** Matches terminal diagnostics to a specific dynamic tool call id/name. */
3340
- function isMatchingDynamicToolTerminalDiagnostic(params) {
3341
- if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
3342
- if (params.runId !== void 0) return params.event.runId === params.runId;
3343
- if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
3344
- if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
3345
- return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
3346
- }
3347
- /** Checks pending diagnostics for a terminal event matching a tool call. */
3348
- function hasPendingDynamicToolTerminalDiagnostic(params) {
3349
- return hasPendingInternalDiagnosticEvent((event) => {
3350
- if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
3351
- return isMatchingDynamicToolTerminalDiagnostic({
3352
- event,
3353
- call: params.call,
3354
- runId: params.runId,
3355
- sessionId: params.sessionId,
3356
- sessionKey: params.sessionKey
3357
- });
3464
+ async function prepareCodexThreadLifecyclePreflight(params) {
3465
+ await assertCodexModelBackedReviewerEffectiveConfig({
3466
+ client: params.client,
3467
+ approvalsReviewer: params.appServer.approvalsReviewer,
3468
+ cwd: params.cwd,
3469
+ signal: params.signal
3358
3470
  });
3359
- }
3360
- /** Resolves per-tool timeout, applying media/message defaults and hard caps. */
3361
- function resolveDynamicToolCallTimeoutMs(params) {
3362
- if (params.call.tool === "computer") return clampDynamicToolTimeoutMs(readComputerToolTimeoutMs(params.call.arguments));
3363
- if (params.call.tool === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
3364
- if (params.call.tool === "agents_wait") {
3365
- const requestedMs = readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS;
3366
- return Math.max(1, Math.min(63e4, Math.floor(requestedMs)));
3367
- }
3368
- return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_TOOL_TIMEOUT_MS);
3369
- }
3370
- function readComputerToolTimeoutMs(value) {
3371
- const args = isJsonObject(value) ? value : void 0;
3372
- const action = typeof args?.action === "string" ? args.action : void 0;
3373
- const gatewayTimeoutMs = readPositiveFiniteTimeoutMs(args?.timeoutMs) ?? CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS;
3374
- const gatewayCallCount = action === "screenshot" || action === "wait" ? 3 : 4;
3375
- return (action === "wait" || action === "hold_key" ? Math.max(0, Number(args?.duration) || 0) * 1e3 : 0) + gatewayCallCount * gatewayTimeoutMs + CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS;
3376
- }
3377
- function readDynamicToolCallTimeoutMs(value) {
3378
- if (!isJsonObject(value)) return;
3379
- const timeoutMs = readPositiveFiniteTimeoutMs(value.timeoutMs);
3380
- if (timeoutMs !== void 0) return timeoutMs;
3381
- const timeoutSecondsMs = readDynamicToolTimeoutSecondsAsMs(value.timeoutSeconds);
3382
- return timeoutSecondsMs === void 0 ? void 0 : addTimerTimeoutGraceMs(timeoutSecondsMs, CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS);
3383
- }
3384
- function readConfiguredDynamicToolTimeoutMs(toolName, config) {
3385
- if (toolName === "image_generate") {
3386
- const imageModel = config?.agents?.defaults?.mediaModels?.image;
3387
- if (!imageModel || typeof imageModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
3388
- return readPositiveFiniteTimeoutMs(imageModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
3389
- }
3390
- if (toolName === "view_image") {
3391
- const candidates = (config?.tools?.media?.models ?? []).filter((entry) => !entry.capabilities || entry.capabilities.includes("image"));
3392
- const capabilityTimeoutMs = readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds);
3393
- return Math.max(capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS, ...candidates.map((entry) => readTimeoutSecondsAsMs(entry.timeoutSeconds) ?? capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS));
3394
- }
3395
- if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
3396
- }
3397
- function readTimeoutSecondsAsMs(value) {
3398
- const seconds = readPositiveFiniteTimeoutMs(value);
3399
- return seconds === void 0 ? void 0 : seconds * 1e3;
3400
- }
3401
- function readDynamicToolTimeoutSecondsAsMs(value) {
3402
- if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) return;
3403
- return value * 1e3;
3404
- }
3405
- function readPositiveFiniteTimeoutMs(value) {
3406
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
3407
- }
3408
- function clampDynamicToolTimeoutMs(timeoutMs) {
3409
- return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
3410
- }
3411
- //#endregion
3412
- //#region extensions/codex/src/app-server/native-hook-relay-state.ts
3413
- const pending = /* @__PURE__ */ new Set();
3414
- /** Owns delayed hook-relay cleanup across runtime scheduling and test teardown. */
3415
- const nativeHookRelayUnregisterQueue = {
3416
- add(entry) {
3417
- pending.add(entry);
3418
- },
3419
- delete(entry) {
3420
- return pending.delete(entry);
3421
- },
3422
- flush() {
3423
- while (pending.size > 0) {
3424
- const entry = pending.values().next().value;
3425
- if (!entry) return;
3426
- clearTimeout(entry.timeout);
3427
- entry.unregister();
3428
- }
3429
- },
3430
- clear() {
3431
- for (const entry of pending) clearTimeout(entry.timeout);
3432
- pending.clear();
3433
- }
3434
- };
3435
- //#endregion
3436
- //#region extensions/codex/src/app-server/native-hook-relay.ts
3437
- /**
3438
- * Bridges Codex native hook callbacks into OpenClaw's native hook relay so
3439
- * app-server tool events can still run OpenClaw policy and diagnostics.
3440
- */
3441
- /** Codex hook events that can be registered through OpenClaw's native relay. */
3442
- const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
3443
- "pre_tool_use",
3444
- "post_tool_use",
3445
- "permission_request",
3446
- "before_agent_finalize"
3447
- ];
3448
- const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
3449
- const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
3450
- /** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
3451
- const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 6e4;
3452
- const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
3453
- const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
3454
- const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
3455
- const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
3456
- const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
3457
- const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
3458
- const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
3459
- const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
3460
- exec: [
3461
- "Bash",
3462
- "exec",
3463
- "exec_command"
3464
- ],
3465
- apply_patch: [
3466
- "apply_patch",
3467
- "Write",
3468
- "Edit"
3469
- ],
3470
- spawn_agent: ["spawn_agent", "Agent"]
3471
- };
3472
- /** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
3473
- async function assertCodexNativeHookRelayAllowed(client, signal) {
3474
- let attestation = nativeHookPolicyByClient.get(client);
3475
- if (!attestation) {
3476
- attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
3477
- if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
3478
- const requirements = response.requirements;
3479
- if (requirements === null) return;
3480
- if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
3481
- const managedOnly = requirements.allowManagedHooksOnly;
3482
- if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
3483
- if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
3484
- });
3485
- nativeHookPolicyByClient.set(client, attestation);
3486
- attestation.catch(() => {
3487
- if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
3488
- });
3489
- }
3490
- await attestation;
3491
- }
3492
- /** Defers relay unregister so late native hook subprocesses can still resolve. */
3493
- function scheduleCodexNativeHookRelayUnregister(params) {
3494
- let pending;
3495
- const unregister = () => {
3496
- if (!pending) return;
3497
- const current = pending;
3498
- pending = void 0;
3499
- if (!nativeHookRelayUnregisterQueue.delete(current)) return;
3500
- params.relay.unregister();
3501
- };
3502
- const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
3503
- pending = {
3504
- timeout,
3505
- unregister
3506
- };
3507
- nativeHookRelayUnregisterQueue.add(pending);
3508
- timeout.unref();
3509
- }
3510
- /** Computes the delayed unregister window from Codex's hook timeout. */
3511
- function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
3512
- const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
3513
- return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
3514
- }
3515
- /** Records a native pre-tool failure that Codex does not project as a tool item. */
3516
- function emitCodexNativePreToolUseFailureDiagnostic(params) {
3517
- emitTrustedDiagnosticEvent({
3518
- type: "tool.execution.error",
3519
- ...params.agentId ? { agentId: params.agentId } : {},
3520
- sessionId: params.sessionId,
3521
- ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3522
- runId: params.runId,
3523
- toolName: params.failure.toolName,
3524
- toolCallId: params.failure.toolCallId,
3525
- durationMs: params.failure.durationMs,
3526
- errorCategory: "before_tool_call",
3527
- terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
3528
- ...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
3471
+ if (params.nativeHookRelayRequired) await assertCodexNativeHookRelayAllowed(params.client, params.signal);
3472
+ const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
3473
+ ...params.timing,
3474
+ enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
3529
3475
  });
3530
- }
3531
- /** Registers an OpenClaw native hook relay for a Codex app-server turn. */
3532
- function createCodexNativeHookRelay(params) {
3533
- if (params.options?.enabled === false) return;
3534
- const directChildClaims = /* @__PURE__ */ new Map();
3535
- const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
3536
- let foregroundClosed = false;
3537
- let successfulYieldRetentionAuthorized = false;
3538
- const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
3539
- const rejectPendingAdmissions = (reason) => {
3540
- for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
3541
- pendingDirectChildAdmissions.clear();
3542
- };
3543
- const relay = registerRetainedNativeHookRelayForBundledRuntime({
3544
- provider: "codex",
3545
- relayId: buildCodexNativeHookRelayId({
3546
- agentId: params.agentId,
3547
- sessionId: params.sessionId,
3548
- sessionKey: params.sessionKey
3549
- }),
3550
- ...params.generation ? { generation: params.generation } : {},
3551
- ...params.generationMismatchGraceMs ? { generationMismatchGraceMs: params.generationMismatchGraceMs } : {},
3552
- ...params.agentId ? { agentId: params.agentId } : {},
3553
- sessionId: params.sessionId,
3554
- ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3555
- ...params.config ? { config: params.config } : {},
3556
- runId: params.runId,
3557
- ...params.channelId ? { channelId: params.channelId } : {},
3558
- ...params.requester ? { requester: params.requester } : {},
3559
- ...params.approvalContext ? { approvalContext: params.approvalContext } : {},
3560
- allowedEvents: params.events,
3561
- preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
3562
- ttlMs: resolveCodexNativeHookRelayTtlMs({
3563
- explicitTtlMs: params.options?.ttlMs,
3564
- attemptTimeoutMs: params.attemptTimeoutMs,
3565
- startupTimeoutMs: params.startupTimeoutMs,
3566
- turnStartTimeoutMs: params.turnStartTimeoutMs
3567
- }),
3568
- signal: params.signal,
3569
- runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
3570
- assertActive: params.hostCapabilities.assertActive,
3571
- retention: {
3572
- readClaim: readCodexNativeChildThreadId,
3573
- shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
3574
- allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
3575
- awaitForegroundAdmission: (childThreadId) => {
3576
- if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
3577
- const existingClaim = directChildClaims.get(childThreadId);
3578
- if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
3579
- const existingPending = pendingDirectChildAdmissions.get(childThreadId);
3580
- if (existingPending) return existingPending.promise.then((claim) => assertClaim(childThreadId, claim));
3581
- if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
3582
- const { promise, resolve, reject } = createDeferred();
3583
- pendingDirectChildAdmissions.set(childThreadId, {
3584
- promise,
3585
- resolve,
3586
- reject
3587
- });
3588
- return promise.then((claim) => assertClaim(childThreadId, claim));
3589
- },
3590
- onDispose: () => {
3591
- foregroundClosed = true;
3592
- rejectPendingAdmissions("native hook relay registration closed");
3593
- }
3594
- },
3595
- onPreToolUseFailure: params.onPreToolUseFailure,
3596
- command: {
3597
- nice: 10,
3598
- timeoutMs: params.options?.gatewayTimeoutMs
3599
- }
3476
+ const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
3477
+ const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
3478
+ const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
3479
+ const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
3480
+ config: params.params.config,
3481
+ disableTools: params.params.disableTools,
3482
+ nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
3483
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3484
+ webSearchAllowed: params.webSearchAllowed
3485
+ })).threadConfig);
3486
+ const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
3487
+ const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
3488
+ const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRun({
3489
+ run: params.params,
3490
+ cwd: params.cwd,
3491
+ agentId: params.agentId ?? params.params.agentId,
3492
+ allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
3493
+ warn: (message) => embeddedAgentLog.warn(message),
3494
+ onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
3495
+ serverName,
3496
+ error: formatErrorMessage(error)
3497
+ })
3600
3498
  });
3601
- const unregister = () => {
3602
- foregroundClosed = true;
3603
- rejectPendingAdmissions("native hook relay foreground closed");
3604
- relay.unregister();
3605
- };
3499
+ const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
3500
+ client: params.client,
3501
+ codexHome: params.appServer.start.env?.CODEX_HOME,
3502
+ cwd: params.cwd,
3503
+ home: params.appServer.start.env?.HOME,
3504
+ signal: params.signal,
3505
+ userProfile: params.appServer.start.env?.USERPROFILE
3506
+ }));
3507
+ const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
3508
+ version: 1,
3509
+ disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
3510
+ }) : void 0;
3511
+ const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
3512
+ const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
3513
+ const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
3514
+ const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
3515
+ const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
3516
+ const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
3517
+ const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
3518
+ const allowConfiguredManagedHooks = params.params.pluginHarnessToolPolicyRestricted === true && !ringZeroActive && !messageOnlySourceReply && params.params.scheduledRuntimeAuthority === void 0;
3519
+ const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
3520
+ 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, {
3523
+ restrictedToolSurface,
3524
+ additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0,
3525
+ allowedManagedRequirementsFingerprint: readScheduledCodexAppManagedRequirementsFingerprint(params.params.scheduledRuntimeAuthority),
3526
+ allowConfiguredManagedHooks
3527
+ }, params.signal));
3528
+ const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
3529
+ version: 1,
3530
+ baseInstructions: "",
3531
+ config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
3532
+ }) : void 0;
3606
3533
  return {
3607
- ...relay,
3608
- unregister,
3609
- authorizeRetentionAfterSuccessfulYield: () => {
3610
- successfulYieldRetentionAuthorized = true;
3611
- },
3612
- hasClaimedDirectChild: () => directChildClaims.size > 0,
3613
- rejectPendingDirectChild: (threadIdInput, reason) => {
3614
- const threadId = threadIdInput.trim();
3615
- const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
3616
- if (!pending) return;
3617
- pendingDirectChildAdmissions.delete(threadId);
3618
- pending.reject(new Error(reason));
3619
- },
3620
- claimDirectChild: (threadIdInput) => {
3621
- const threadId = threadIdInput.trim();
3622
- if (!threadId) return () => void 0;
3623
- if (directChildClaims.get(threadId)) return () => void 0;
3624
- const claim = Symbol(threadId);
3625
- directChildClaims.set(threadId, claim);
3626
- const pending = pendingDirectChildAdmissions.get(threadId);
3627
- pendingDirectChildAdmissions.delete(threadId);
3628
- pending?.resolve(claim);
3629
- let released = false;
3630
- return () => {
3631
- if (released) return;
3632
- released = true;
3633
- if (directChildClaims.get(threadId) !== claim) return;
3634
- directChildClaims.delete(threadId);
3635
- if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
3636
- };
3637
- }
3534
+ contextEngineBinding,
3535
+ dynamicToolsContainDeferred,
3536
+ dynamicToolsFingerprint,
3537
+ environmentSelectionFingerprint,
3538
+ hostSystemAgentActive,
3539
+ legacyDynamicToolsFingerprint,
3540
+ legacyUserMcpServersFingerprint,
3541
+ lifecycleTiming,
3542
+ nativeSkillIsolation,
3543
+ nativeSkillIsolationFingerprint,
3544
+ networkProxyConfigFingerprint,
3545
+ ringZeroActive,
3546
+ ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
3547
+ ringZeroConfigFingerprint,
3548
+ restrictedToolSurface,
3549
+ restrictedToolSurfaceInheritedMcpServerNames,
3550
+ userMcpServersConfigPatch,
3551
+ userMcpServersFingerprint,
3552
+ webSearchThreadConfigFingerprint
3638
3553
  };
3639
3554
  }
3640
- function readCodexNativeChildThreadId(rawPayload) {
3641
- if (!isJsonObject(rawPayload) || typeof rawPayload.agent_id !== "string") return;
3642
- return rawPayload.agent_id.trim() || void 0;
3643
- }
3644
- /** Selects the native hook events Codex should install for the current approval mode. */
3645
- function resolveCodexNativeHookRelayEvents(params) {
3646
- if (params.configuredEvents?.length) return params.configuredEvents;
3647
- return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
3648
- }
3649
- /** Derives the native hook relay TTL from the turn budget unless explicitly configured. */
3650
- function resolveCodexNativeHookRelayTtlMs(params) {
3651
- if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
3652
- const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
3653
- return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
3654
- }
3655
- /** Builds a stable relay id scoped to the agent and session identity. */
3656
- function buildCodexNativeHookRelayId(params) {
3657
- const hash = createHash("sha256");
3658
- hash.update("openclaw:codex:native-hook-relay:v1");
3659
- hash.update("\0");
3660
- hash.update(params.agentId?.trim() || "");
3661
- hash.update("\0");
3662
- hash.update(params.sessionKey?.trim() || params.sessionId);
3663
- return `codex-${hash.digest("hex").slice(0, 40)}`;
3664
- }
3665
- const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
3666
- pre_tool_use: "PreToolUse",
3667
- post_tool_use: "PostToolUse",
3668
- permission_request: "PermissionRequest",
3669
- before_agent_finalize: "Stop"
3670
- };
3671
- const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
3672
- pre_tool_use: "pre_tool_use",
3673
- post_tool_use: "post_tool_use",
3674
- permission_request: "permission_request",
3675
- before_agent_finalize: "stop"
3676
- };
3677
- const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
3678
- /** Builds the Codex config overlay that installs trusted command hooks for relay events. */
3679
- function buildCodexNativeHookRelayConfig(params) {
3680
- const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
3681
- const selectedEvents = new Set(events);
3682
- const config = { "features.hooks": true };
3683
- const hookState = {};
3684
- for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
3685
- const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
3686
- const selected = selectedEvents.has(event);
3687
- const shouldRelay = params.relay.shouldRelayEvent(event);
3688
- if (!selected || !shouldRelay) {
3689
- if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
3690
- if (params.clearOmittedEvents) for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = { enabled: false };
3691
- continue;
3692
- }
3693
- const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
3694
- const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
3695
- const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
3696
- config[`hooks.${codexEvent}`] = [{
3697
- ...matcher ? { matcher } : {},
3698
- hooks: [{
3699
- type: "command",
3700
- command,
3701
- timeout,
3702
- async: false,
3703
- statusMessage: "OpenClaw native hook relay"
3704
- }]
3705
- }];
3706
- const state = {
3707
- enabled: true,
3708
- trusted_hash: codexCommandHookTrustedHash({
3709
- event,
3710
- command,
3711
- matcher,
3712
- timeout,
3713
- statusMessage: "OpenClaw native hook relay"
3714
- })
3715
- };
3716
- for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
3717
- }
3718
- config["hooks.state"] = hookState;
3719
- return config;
3555
+ //#endregion
3556
+ //#region extensions/codex/src/app-server/thread-lifecycle-result.ts
3557
+ function resolveCodexThreadRolloutPath(thread) {
3558
+ const rolloutPath = thread.path?.trim();
3559
+ if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
3560
+ return rolloutPath;
3720
3561
  }
3721
- /** Builds a Codex config overlay that disables native hooks and clears hook arrays. */
3722
- function buildCodexNativeHookRelayDisabledConfig() {
3562
+ /** Materializes the public lifecycle result after a fresh thread is durably committed. */
3563
+ function buildStartedCodexThreadBinding(input) {
3564
+ const { context, params, response, startParams } = input;
3723
3565
  return {
3724
- "features.hooks": false,
3725
- "hooks.PreToolUse": [],
3726
- "hooks.PostToolUse": [],
3727
- "hooks.PermissionRequest": [],
3728
- "hooks.Stop": []
3729
- };
3730
- }
3731
- function normalizeHookTimeoutSec(value) {
3732
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC;
3733
- }
3734
- function resolveCodexNativeHookRelayCommandTimeoutMs(hookTimeoutSec) {
3735
- const parentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 5e3;
3736
- const parentMarginMs = Math.min(CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS, Math.max(CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS, Math.floor(parentTimeoutMs / 5)));
3737
- return Math.max(1, parentTimeoutMs - parentMarginMs);
3738
- }
3739
- function buildCodexNativeToolMatcher(toolNames) {
3740
- if (toolNames === void 0) return;
3741
- if (toolNames.length === 0) throw new TypeError("Codex native hook matcher requires at least one tool name");
3742
- const nativeNames = /* @__PURE__ */ new Set();
3743
- let hasCustomToolName = false;
3744
- for (const toolName of toolNames) {
3745
- const canonicalToolName = toolName.trim();
3746
- if (!canonicalToolName || canonicalToolName === "*") throw new TypeError("Codex native hook matcher requires canonical OpenClaw tool ids");
3747
- const nativeAliases = CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID[canonicalToolName];
3748
- if (!nativeAliases) hasCustomToolName = true;
3749
- for (const nativeName of nativeAliases ?? [canonicalToolName]) nativeNames.add(nativeName);
3750
- }
3751
- const sortedNames = Array.from(nativeNames).toSorted();
3752
- if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
3753
- return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
3754
- }
3755
- function codexCommandHookTrustedHash(params) {
3756
- const identity = {
3757
- event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
3758
- ...params.matcher ? { matcher: params.matcher } : {},
3759
- hooks: [{
3760
- async: false,
3761
- command: params.command,
3762
- statusMessage: params.statusMessage,
3763
- timeout: params.timeout,
3764
- type: "command"
3765
- }]
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
+ }
3766
3603
  };
3767
- return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
3768
- }
3769
- function sortJsonValue(value) {
3770
- if (!value || typeof value !== "object") return value;
3771
- if (Array.isArray(value)) return value.map(sortJsonValue);
3772
- const sorted = {};
3773
- for (const [key, entry] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) sorted[key] = sortJsonValue(entry);
3774
- return sorted;
3775
- }
3776
- //#endregion
3777
- //#region extensions/codex/src/app-server/profiler-flag.ts
3778
- const PROFILER_FLAGS = ["profiler", "codex.profiler"];
3779
- /** Checks the generic and Codex-specific profiler diagnostic flags. */
3780
- function isCodexAppServerProfilerEnabled(config, env = process.env) {
3781
- return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
3782
3604
  }
3783
3605
  //#endregion
3784
- //#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
3785
- const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
3786
- const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
3787
- function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
3788
- const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
3789
- const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
3790
- return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
3791
- }
3792
- function formatCodexThreadLifecycleTimingSummary(params) {
3793
- const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
3794
- return `[trace:codex-app-server] thread lifecycle: runId=${params.runId} sessionId=${params.sessionId} sessionKey=${params.sessionKey ?? "unknown"} action=${params.action} totalMs=${params.summary.totalMs} stages=${spans}`;
3795
- }
3796
- function createCodexThreadLifecycleTimingTracker(options = {}) {
3797
- const log = options.log ?? embeddedAgentLog;
3798
- if (!options.enabled && log.isEnabled?.("trace") !== true) return {
3799
- async measure(_name, run) {
3800
- return await run();
3801
- },
3802
- measureSync(_name, run) {
3803
- return run();
3804
- },
3805
- mark() {},
3806
- logSummary() {}
3606
+ //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
3607
+ async function resumeExistingCodexThread(params, context) {
3608
+ 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;
3609
+ let resumeReservation;
3610
+ let resumeResponseAccepted = false;
3611
+ let ordinaryAppConfigChanged = false;
3612
+ let policyOutcome = "not-written";
3613
+ const assertHandoffCurrent = () => {
3614
+ params.params.hostCapabilities.assertActive();
3615
+ throwIfAborted();
3616
+ context.assertResumeOwnership();
3617
+ context.assertResumeConfiguration();
3807
3618
  };
3808
- const now = options.now ?? Date.now;
3809
- const startedAt = now();
3810
- let didLog = false;
3811
- const spans = [];
3812
- const toMs = (value) => Math.max(0, Math.round(value));
3813
- const record = (name, spanStartedAt) => {
3814
- const currentAt = now();
3815
- spans.push({
3816
- name,
3817
- durationMs: toMs(currentAt - spanStartedAt),
3818
- elapsedMs: toMs(currentAt - startedAt)
3619
+ const abandonClient = params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client));
3620
+ try {
3621
+ const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
3622
+ const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
3623
+ action: "resume",
3624
+ binding: resumeBinding
3625
+ }) ?? {
3626
+ configPatch: params.finalConfigPatch,
3627
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
3628
+ };
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);
3632
+ const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
3633
+ threadId: resumeBinding.threadId,
3634
+ cwd: params.cwd,
3635
+ authProfileId,
3636
+ model: startModelSelection.model,
3637
+ modelProvider: startModelProvider,
3638
+ preserveNativeModel: resumeBinding.preserveNativeModel === true,
3639
+ appServer: params.appServer,
3640
+ dynamicTools: params.dynamicTools,
3641
+ developerInstructions: params.developerInstructions,
3642
+ config: resumeConfig,
3643
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
3644
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3645
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
3646
+ webSearchAllowed: params.webSearchAllowed,
3647
+ hostSystemAgentActive,
3648
+ restrictedToolSurfaceInheritedMcpServerNames,
3649
+ shellEnvironment: params.shellEnvironment,
3650
+ disableLoginShell: params.disableLoginShell
3651
+ }));
3652
+ const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
3653
+ throwIfAborted();
3654
+ resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
3655
+ const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
3656
+ client: params.client,
3657
+ abandonClient,
3658
+ request: resumeParams,
3659
+ signal: params.signal,
3660
+ assertCurrent: context.assertResumeOwnership
3661
+ }));
3662
+ resumeResponseAccepted = true;
3663
+ assertCodexThreadAcceptsDirectInput(response.thread);
3664
+ context.assertResumeConfiguration();
3665
+ const loadedPluginThreadConfig = await context.buildLoadedPluginThreadConfig?.(resumeBinding);
3666
+ if (loadedPluginThreadConfig && loadedPluginThreadConfig.fingerprint !== (pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint)) {
3667
+ ordinaryAppConfigChanged = resumeBinding.connectionScope !== "supervision" && !resumeBinding.pendingResumeConfiguration;
3668
+ throw new Error("Codex thread app policy changed; a fresh thread configuration is required");
3669
+ }
3670
+ const provisionalAppIds = loadedPluginThreadConfig?.provisionalAppIds ?? pluginThreadConfig?.provisionalAppIds ?? [];
3671
+ await attestCodexThreadToolSurface({
3672
+ client: params.client,
3673
+ threadId: response.thread.id,
3674
+ appIds: provisionalAppIds,
3675
+ signal: params.signal,
3676
+ threadConfig: resumeParams.config,
3677
+ restrictedToolSurface,
3678
+ lifecycleTiming,
3679
+ assertCurrent: assertHandoffCurrent
3819
3680
  });
3820
- };
3821
- const snapshot = () => ({
3822
- totalMs: toMs(now() - startedAt),
3823
- spans: spans.slice()
3824
- });
3825
- return {
3826
- async measure(name, run) {
3827
- const spanStartedAt = now();
3828
- try {
3829
- return await run();
3830
- } finally {
3831
- record(name, spanStartedAt);
3681
+ throwIfAborted();
3682
+ await refreshCodexThreadPolicy({
3683
+ client: params.client,
3684
+ threadId: resumeBinding.threadId,
3685
+ developerInstructions: resumeParams.developerInstructions,
3686
+ timeoutMs: params.appServer.requestTimeoutMs,
3687
+ signal: params.signal,
3688
+ assertCurrent: assertHandoffCurrent
3689
+ });
3690
+ policyOutcome = "acknowledged";
3691
+ assertHandoffCurrent();
3692
+ const resumePatch = {
3693
+ clientId: resolveCodexAppServerClientInstanceId(params.client),
3694
+ pendingResumeConfiguration: void 0,
3695
+ cwd: params.cwd,
3696
+ rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
3697
+ authProfileId,
3698
+ model: response.model ?? resumeParams.model ?? params.params.modelId,
3699
+ preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
3700
+ modelProvider: normalizeBindingModelProvider(authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
3701
+ dynamicToolsFingerprint,
3702
+ dynamicToolsContainDeferred,
3703
+ webSearchThreadConfigFingerprint,
3704
+ nativeSkillIsolationFingerprint,
3705
+ userMcpServersFingerprint,
3706
+ mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint,
3707
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
3708
+ ringZeroConfigFingerprint,
3709
+ ringZeroClientInstanceId,
3710
+ nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
3711
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
3712
+ networkProxyConfigFingerprint,
3713
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
3714
+ appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
3715
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
3716
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
3717
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
3718
+ contextEngine: contextEngineBinding,
3719
+ environmentSelectionFingerprint
3720
+ };
3721
+ if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
3722
+ kind: "patch",
3723
+ threadId: resumeBinding.threadId,
3724
+ patch: resumePatch
3725
+ }, assertHandoffCurrent))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
3726
+ assertHandoffCurrent();
3727
+ if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
3728
+ sessionId: params.params.sessionId,
3729
+ sessionKey: params.params.sessionKey,
3730
+ threadId: response.thread.id,
3731
+ engineId: contextEngineBinding.engineId,
3732
+ epoch: contextEngineBinding.projection?.epoch,
3733
+ fingerprint: contextEngineBinding.projection?.fingerprint,
3734
+ action: "resumed"
3735
+ });
3736
+ lifecycleTiming.mark("thread-ready");
3737
+ lifecycleTiming.logSummary({
3738
+ runId: params.params.runId,
3739
+ sessionId: params.params.sessionId,
3740
+ sessionKey: params.params.sessionKey,
3741
+ threadId: response.thread.id,
3742
+ action: "resumed"
3743
+ });
3744
+ const activeTurnIds = readActiveCodexTurnIdsFromResume(response);
3745
+ return {
3746
+ ...resumeBinding,
3747
+ threadId: response.thread.id,
3748
+ ...resumePatch,
3749
+ liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
3750
+ ...resumeParams,
3751
+ model: resumeBinding.preserveNativeModel === true ? null : response.model ?? resumeParams.model ?? null,
3752
+ requestedModel: resumeBinding.preserveNativeModel === true ? null : resumeParams.model ?? null,
3753
+ modelProvider: resumeBinding.preserveNativeModel === true ? null : resumePatch.modelProvider ?? null,
3754
+ requestedModelProvider: resumeBinding.preserveNativeModel === true ? null : resumeParams.modelProvider ?? resumePatch.modelProvider ?? null
3755
+ }, authProfileId, dynamicToolsFingerprint),
3756
+ lifecycle: {
3757
+ action: "resumed",
3758
+ ...activeTurnIds.length ? { activeTurnIds } : {}
3832
3759
  }
3833
- },
3834
- measureSync(name, run) {
3835
- const spanStartedAt = now();
3836
- try {
3837
- return run();
3838
- } finally {
3839
- record(name, spanStartedAt);
3760
+ };
3761
+ } catch (error) {
3762
+ resumeReservation?.release();
3763
+ if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
3764
+ if (resumeResponseAccepted) {
3765
+ const handoffError = error instanceof CodexThreadPolicyHandoffError || error instanceof CodexAppServerUnsafeSubscriptionError ? error : new CodexThreadPolicyHandoffError(policyOutcome, error);
3766
+ const subscriptionReleased = await unsubscribeCodexThreadBestEffort(params.client, {
3767
+ threadId: resumeBinding.threadId,
3768
+ timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,
3769
+ assertCurrent: context.assertResumeOwnership
3770
+ }).catch(() => false);
3771
+ if (!subscriptionReleased || handoffError instanceof CodexThreadPolicyHandoffError && handoffError.outcome === "unknown") {
3772
+ if (resumeBinding.connectionScope === "supervision") await retireUnsafeCodexTurnClientBestEffort(params.client, "session policy handoff");
3773
+ else try {
3774
+ await abandonClient();
3775
+ } catch (cause) {
3776
+ throw new CodexThreadPolicyHandoffError(handoffError instanceof CodexThreadPolicyHandoffError ? handoffError.outcome : policyOutcome, new AggregateError([handoffError, cause], "Codex thread/resume client could not be retired"));
3777
+ }
3840
3778
  }
3841
- },
3842
- mark(name) {
3843
- record(name, now());
3844
- },
3845
- logSummary(params) {
3846
- if (didLog) return;
3847
- const summary = snapshot();
3848
- const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
3849
- if (!shouldWarn && !log.isEnabled?.("trace")) return;
3850
- didLog = true;
3851
- const message = formatCodexThreadLifecycleTimingSummary({
3852
- runId: params.runId,
3853
- sessionId: params.sessionId,
3854
- sessionKey: params.sessionKey,
3855
- action: params.action,
3856
- summary
3857
- });
3858
- const meta = {
3859
- runId: params.runId,
3860
- sessionId: params.sessionId,
3861
- sessionKey: params.sessionKey,
3862
- action: params.action,
3863
- threadId: params.threadId,
3864
- totalMs: summary.totalMs,
3865
- spans: summary.spans
3866
- };
3867
- if (shouldWarn) log.warn(message, meta);
3868
- else log.trace(message, meta);
3779
+ if (!ordinaryAppConfigChanged || !subscriptionReleased) throw handoffError;
3780
+ assertHandoffCurrent();
3781
+ }
3782
+ if (resumeBinding.pendingResumeConfiguration || resumeBinding.connectionScope === "supervision" || params.signal?.aborted) throw error;
3783
+ embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
3784
+ await clearCurrentBinding("rotating a stale thread binding");
3785
+ }
3786
+ }
3787
+ async function startFreshCodexThread(params, context) {
3788
+ const clientId = resolveCodexAppServerClientInstanceId(params.client);
3789
+ const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding, replacementPredecessor } = context;
3790
+ const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
3791
+ const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
3792
+ configPatch: params.finalConfigPatch,
3793
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
3794
+ };
3795
+ const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
3796
+ const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
3797
+ cwd: params.cwd,
3798
+ dynamicTools: params.dynamicTools,
3799
+ appServer: params.appServer,
3800
+ developerInstructions: params.developerInstructions,
3801
+ config,
3802
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
3803
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3804
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
3805
+ webSearchAllowed: params.webSearchAllowed,
3806
+ environmentSelection: params.environmentSelection,
3807
+ model: startModelSelection.model,
3808
+ modelProvider: startModelProvider,
3809
+ hostSystemAgentActive,
3810
+ restrictedToolSurfaceInheritedMcpServerNames,
3811
+ shellEnvironment: params.shellEnvironment,
3812
+ disableLoginShell: params.disableLoginShell
3813
+ }));
3814
+ const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
3815
+ const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
3816
+ try {
3817
+ return await params.client.request("thread/start", startParams, { signal: params.signal });
3818
+ } catch (error) {
3819
+ if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
3820
+ throw error;
3869
3821
  }
3822
+ });
3823
+ const response = assertCodexThreadStartResponse(threadStartResponse);
3824
+ const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
3825
+ const assertCurrent = () => {
3826
+ throwIfAborted();
3827
+ params.params.hostCapabilities.assertActive();
3870
3828
  };
3871
- }
3872
- //#endregion
3873
- //#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
3874
- async function prepareCodexThreadLifecyclePreflight(params) {
3875
- await assertCodexModelBackedReviewerEffectiveConfig({
3876
- client: params.client,
3877
- approvalsReviewer: params.appServer.approvalsReviewer,
3878
- cwd: params.cwd,
3879
- signal: params.signal
3829
+ const rejectUncommittedThread = async (cause) => {
3830
+ if (!await discardUnattestedCodexPluginThread({
3831
+ client: params.client,
3832
+ threadId: response.thread.id,
3833
+ ephemeral: startParams.ephemeral === true
3834
+ })) {
3835
+ await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
3836
+ throw new CodexAppServerUnsafeSubscriptionError("Codex uncommitted thread cleanup failed", { cause });
3837
+ }
3838
+ throw cause;
3839
+ };
3840
+ try {
3841
+ await attestCodexThreadToolSurface({
3842
+ client: params.client,
3843
+ threadId: response.thread.id,
3844
+ appIds: provisionalAppIds ?? [],
3845
+ signal: params.signal,
3846
+ threadConfig: startParams.config,
3847
+ restrictedToolSurface,
3848
+ lifecycleTiming,
3849
+ assertCurrent
3850
+ });
3851
+ assertCurrent();
3852
+ } catch (error) {
3853
+ return await rejectUncommittedThread(error);
3854
+ }
3855
+ const rolloutPath = resolveCodexThreadRolloutPath(response.thread);
3856
+ const modelProvider = resolveCodexAppServerModelProvider({
3857
+ provider: params.params.provider,
3858
+ authProfileId: params.params.authProfileId,
3859
+ authProfileStore: params.params.authProfileStore,
3860
+ agentDir: params.params.agentDir,
3861
+ config: params.params.config
3880
3862
  });
3881
- if (params.nativeHookRelayRequired) await assertCodexNativeHookRelayAllowed(params.client, params.signal);
3882
- const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
3883
- ...params.timing,
3884
- enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
3863
+ const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
3864
+ const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
3865
+ if (!preserveExistingBinding) {
3866
+ 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,
3877
+ 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
3894
+ };
3895
+ const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
3896
+ let committed;
3897
+ try {
3898
+ await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
3899
+ sourceHomeId: managedSourceHomeId,
3900
+ threadId: response.thread.id,
3901
+ ...rolloutPath ? { rolloutPath } : {}
3902
+ }));
3903
+ committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
3904
+ kind: "replace-thread",
3905
+ expectedThreadId: replacementPredecessor.threadId,
3906
+ binding: nextBinding
3907
+ } : {
3908
+ kind: "set",
3909
+ if: { kind: "absent" },
3910
+ binding: nextBinding
3911
+ }, assertCurrent));
3912
+ } catch (error) {
3913
+ return await rejectUncommittedThread(error);
3914
+ }
3915
+ if (!committed) return await rejectUncommittedThread(new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread"));
3916
+ if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
3917
+ sessionId: params.params.sessionId,
3918
+ sessionKey: params.params.sessionKey,
3919
+ threadId: response.thread.id,
3920
+ engineId: contextEngineBinding.engineId,
3921
+ epoch: contextEngineBinding.projection?.epoch,
3922
+ fingerprint: contextEngineBinding.projection?.fingerprint,
3923
+ action: rotatedContextEngineBinding ? "rotated" : "started"
3924
+ });
3925
+ }
3926
+ lifecycleTiming.mark("thread-ready");
3927
+ lifecycleTiming.logSummary({
3928
+ runId: params.params.runId,
3929
+ sessionId: params.params.sessionId,
3930
+ sessionKey: params.params.sessionKey,
3931
+ threadId: response.thread.id,
3932
+ action: rotatedContextEngineBinding ? "rotated" : "started"
3885
3933
  });
3886
- const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
3887
- const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
3888
- const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
3889
- const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
3890
- config: params.params.config,
3891
- disableTools: params.params.disableTools,
3892
- nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
3893
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3894
- webSearchAllowed: params.webSearchAllowed
3895
- })).threadConfig);
3896
- const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
3897
- const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
3898
- const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
3899
- agentId: params.agentId ?? params.params.agentId,
3900
- agentDir: params.params.agentDir,
3901
- allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
3902
- toolOverrides: params.params.toolOverrides,
3903
- onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
3904
- serverName,
3905
- error: formatErrorMessage(error)
3906
- })
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
3907
3947
  });
3908
- const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
3909
- client: params.client,
3910
- codexHome: params.appServer.start.env?.CODEX_HOME,
3911
- cwd: params.cwd,
3912
- home: params.appServer.start.env?.HOME,
3913
- signal: params.signal,
3914
- userProfile: params.appServer.start.env?.USERPROFILE
3915
- }));
3916
- const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
3917
- version: 1,
3918
- disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
3919
- }) : void 0;
3920
- const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
3921
- const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
3922
- const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
3923
- const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
3924
- const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
3925
- const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
3926
- const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
3927
- const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
3928
- if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
3929
- const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
3930
- if (restrictedToolSurface || imageGenerationDenied) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
3931
- restrictedToolSurface,
3932
- additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0
3933
- }, params.signal));
3934
- const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
3935
- version: 1,
3936
- baseInstructions: "",
3937
- config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
3938
- }) : void 0;
3939
- return {
3940
- contextEngineBinding,
3941
- dynamicToolsContainDeferred,
3942
- dynamicToolsFingerprint,
3943
- environmentSelectionFingerprint,
3944
- hostSystemAgentActive,
3945
- legacyDynamicToolsFingerprint,
3946
- legacyUserMcpServersFingerprint,
3947
- lifecycleTiming,
3948
- nativeSkillIsolation,
3949
- nativeSkillIsolationFingerprint,
3950
- networkProxyConfigFingerprint,
3951
- ringZeroActive,
3952
- ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
3953
- ringZeroConfigFingerprint,
3954
- restrictedToolSurface,
3955
- restrictedToolSurfaceInheritedMcpServerNames,
3956
- userMcpServersConfigPatch,
3957
- userMcpServersFingerprint,
3958
- webSearchThreadConfigFingerprint
3959
- };
3960
3948
  }
3961
3949
  //#endregion
3962
3950
  //#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
@@ -3973,66 +3961,150 @@ function throwIfCodexThreadLifecycleAborted(signal) {
3973
3961
  async function releaseCodexConsumedLiveThread(options) {
3974
3962
  if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
3975
3963
  threadId: options.threadId,
3976
- timeoutMs: 5e3
3964
+ timeoutMs: 5e3,
3965
+ assertCurrent: options.assertCurrent
3977
3966
  }))) return;
3967
+ return await abandonCodexLiveThreadRelease(options, options.cause);
3968
+ }
3969
+ async function abandonCodexLiveThreadRelease(options, cause) {
3970
+ options.assertCurrent?.();
3978
3971
  await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
3979
- throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, options.cause !== void 0 ? { cause: options.cause } : void 0);
3972
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, cause !== void 0 ? { cause } : void 0);
3980
3973
  }
3981
- /** Transfers retained-slot ownership before unconditionally releasing it. */
3974
+ /** Releases through the retained owner, preserving its guarded callback and rollback. */
3982
3975
  async function releaseCodexRetainedLiveThread(options) {
3983
- if (await consumeCodexAppServerLiveThread(options.client, options.threadId)) await releaseCodexConsumedLiveThread(options);
3976
+ try {
3977
+ return await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => releaseCodexAppServerLiveThread(options.client, options.threadId, options.assertCurrent));
3978
+ } catch (error) {
3979
+ if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
3980
+ return await abandonCodexLiveThreadRelease(options, error);
3981
+ }
3982
+ }
3983
+ /** Release follows the physical owner across connection rotation, never a copied thread id. */
3984
+ async function releaseCodexBoundLiveThread(options) {
3985
+ const changedClient = options.ownerClientId && options.ownerClientId !== options.clientId;
3986
+ const previous = changedClient ? retainSharedCodexAppServerClientByInstanceId(options.ownerClientId) : void 0;
3987
+ if (changedClient && !previous) return false;
3988
+ try {
3989
+ const client = previous?.client ?? options.client;
3990
+ const assertPrevious = previous && options.assertCurrent ? captureCodexAppServerClientLifetime(client, "connection") : void 0;
3991
+ if (isCodexAppServerLiveThreadClaimed(client, options.threadId)) throw new Error(`Codex thread ${options.threadId} is claimed by active work; stop it first.`);
3992
+ return await releaseCodexRetainedLiveThread({
3993
+ ...options,
3994
+ client,
3995
+ abandonClient: previous ? void 0 : options.abandonClient,
3996
+ assertCurrent: options.assertCurrent ? () => {
3997
+ options.assertCurrent?.();
3998
+ assertPrevious?.();
3999
+ } : void 0
4000
+ });
4001
+ } finally {
4002
+ previous?.release();
4003
+ }
3984
4004
  }
3985
4005
  /** Reuses one safely owned, fully matching subscription on its original client. */
3986
4006
  async function tryReuseCodexLiveThread(options) {
3987
- const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, releaseConsumedThread, ringZeroActive, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
3988
- if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive) return {};
3989
- const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
3990
- action: "resume",
3991
- binding
3992
- }) ?? {
3993
- configPatch: params.finalConfigPatch,
3994
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
3995
- };
3996
- const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0;
3997
- const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
3998
- const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
3999
- const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
4000
- threadId: binding.threadId,
4001
- cwd: params.cwd,
4002
- authProfileId: resumeAuthProfileId,
4003
- model: startModelSelection.model,
4004
- modelProvider: startModelProvider,
4005
- preserveNativeModel: false,
4006
- appServer: params.appServer,
4007
- dynamicTools: params.dynamicTools,
4008
- developerInstructions: params.developerInstructions,
4009
- config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
4010
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
4011
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
4012
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
4013
- webSearchAllowed: params.webSearchAllowed,
4014
- hostSystemAgentActive,
4015
- restrictedToolSurfaceInheritedMcpServerNames,
4016
- shellEnvironment: params.shellEnvironment,
4017
- disableLoginShell: params.disableLoginShell
4018
- }));
4019
- const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
4020
- ...resumeParams,
4021
- model: binding.model ?? resumeParams.model ?? null,
4022
- requestedModel: resumeParams.model ?? null,
4023
- modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
4024
- requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
4025
- }, resumeAuthProfileId, dynamicToolsFingerprint);
4026
- const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint);
4027
- if (!retainedThread) {
4028
- const incompatibleOwnership = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
4029
- if (incompatibleOwnership) await incompatibleOwnership.release(binding.threadId);
4030
- return { prebuiltFinalConfigPatch };
4007
+ const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
4008
+ const incognito = isIncognitoSessionKey(params.params.sessionKey);
4009
+ if (incognito && (binding.preserveNativeModel || binding.connectionScope === "supervision")) {
4010
+ if (binding.clientId === clientId && binding.clientId && ((await options.buildLoadedPluginThreadConfig(binding))?.fingerprint ?? binding.pluginAppsFingerprint) === binding.pluginAppsFingerprint) {
4011
+ params.buildFinalConfigPatch?.({
4012
+ action: "resume",
4013
+ binding
4014
+ });
4015
+ throwIfAborted();
4016
+ return {
4017
+ kind: "ready",
4018
+ binding: {
4019
+ ...binding,
4020
+ lifecycle: { action: "resumed" }
4021
+ }
4022
+ };
4023
+ }
4024
+ return { kind: "rotate" };
4031
4025
  }
4026
+ if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive && !incognito) return { kind: "resume" };
4027
+ const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
4028
+ if (!retainedThread) return { kind: "resume" };
4029
+ const assertWarmOwner = () => {
4030
+ throwIfAborted();
4031
+ if (!isCodexAppServerClientRuntimeLive(params.client)) throw params.client.getCloseError() ?? /* @__PURE__ */ new Error("codex app-server client is closed");
4032
+ try {
4033
+ retainedThread.assertCurrent();
4034
+ params.params.hostCapabilities.assertActive();
4035
+ } catch (cause) {
4036
+ 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
+ }
4038
+ };
4039
+ let ownershipTransferred = false;
4040
+ let preserveSubscription = false;
4032
4041
  try {
4042
+ assertWarmOwner();
4043
+ const pluginThreadConfig = await options.buildLoadedPluginThreadConfig(binding);
4044
+ assertWarmOwner();
4045
+ if (pluginThreadConfig && pluginThreadConfig.fingerprint !== binding.pluginAppsFingerprint) return { kind: "rotate" };
4046
+ const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
4047
+ action: "resume",
4048
+ binding
4049
+ }) ?? {
4050
+ configPatch: params.finalConfigPatch,
4051
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
4052
+ };
4053
+ const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0);
4054
+ const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
4055
+ const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
4056
+ const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
4057
+ threadId: binding.threadId,
4058
+ cwd: params.cwd,
4059
+ authProfileId: resumeAuthProfileId,
4060
+ model: startModelSelection.model,
4061
+ modelProvider: startModelProvider,
4062
+ preserveNativeModel: false,
4063
+ appServer: params.appServer,
4064
+ dynamicTools: params.dynamicTools,
4065
+ developerInstructions: params.developerInstructions,
4066
+ config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
4067
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
4068
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
4069
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
4070
+ webSearchAllowed: params.webSearchAllowed,
4071
+ hostSystemAgentActive,
4072
+ restrictedToolSurfaceInheritedMcpServerNames,
4073
+ shellEnvironment: params.shellEnvironment,
4074
+ disableLoginShell: params.disableLoginShell
4075
+ }));
4076
+ const liveThreadConfigFingerprint = incognito ? retainedThread.configFingerprint : fingerprintCodexThreadConfig({
4077
+ ...resumeParams,
4078
+ model: binding.model ?? resumeParams.model ?? null,
4079
+ requestedModel: resumeParams.model ?? null,
4080
+ modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
4081
+ requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
4082
+ }, resumeAuthProfileId, dynamicToolsFingerprint);
4083
+ if (incognito && retainedThread.ephemeralPolicy !== resumeParams.developerInstructions) {
4084
+ preserveSubscription = true;
4085
+ throw new CodexIncognitoPolicyChangeError();
4086
+ }
4087
+ if (!incognito && retainedThread.configFingerprint !== liveThreadConfigFingerprint) {
4088
+ preserveSubscription = true;
4089
+ return {
4090
+ kind: "resume",
4091
+ prebuiltFinalConfigPatch
4092
+ };
4093
+ }
4094
+ await attestCodexThreadToolSurface({
4095
+ client: params.client,
4096
+ threadId: binding.threadId,
4097
+ appIds: pluginThreadConfig?.provisionalAppIds ?? [],
4098
+ signal: params.signal,
4099
+ threadConfig: resumeParams.config,
4100
+ restrictedToolSurface,
4101
+ lifecycleTiming,
4102
+ assertCurrent: assertWarmOwner
4103
+ });
4104
+ assertWarmOwner();
4033
4105
  const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
4034
4106
  const model = startModelSelection.model;
4035
- if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
4107
+ if (!(incognito || await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
4036
4108
  kind: "patch",
4037
4109
  threadId: binding.threadId,
4038
4110
  patch: {
@@ -4041,8 +4113,8 @@ async function tryReuseCodexLiveThread(options) {
4041
4113
  nativeHookRelayGeneration,
4042
4114
  environmentSelectionFingerprint
4043
4115
  }
4044
- }))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
4045
- throwIfAborted();
4116
+ }, assertWarmOwner)))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
4117
+ assertWarmOwner();
4046
4118
  lifecycleTiming.mark("thread-ready");
4047
4119
  lifecycleTiming.logSummary({
4048
4120
  runId: params.params.runId,
@@ -4051,25 +4123,188 @@ async function tryReuseCodexLiveThread(options) {
4051
4123
  threadId: binding.threadId,
4052
4124
  action: "resumed"
4053
4125
  });
4126
+ ownershipTransferred = true;
4054
4127
  return {
4128
+ kind: "ready",
4055
4129
  binding: {
4056
4130
  ...binding,
4057
- cwd: params.cwd,
4058
- model,
4059
- nativeHookRelayGeneration,
4060
- environmentSelectionFingerprint,
4131
+ ...!incognito ? {
4132
+ cwd: params.cwd,
4133
+ model,
4134
+ nativeHookRelayGeneration,
4135
+ environmentSelectionFingerprint
4136
+ } : {},
4061
4137
  liveThreadConfigFingerprint,
4138
+ liveThreadEphemeralPolicy: retainedThread.ephemeralPolicy,
4062
4139
  liveThreadOwnership: retainedThread,
4063
- ...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
4140
+ ...!incognito && retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
4064
4141
  lifecycle: { action: "resumed" }
4065
- },
4066
- prebuiltFinalConfigPatch
4142
+ }
4143
+ };
4144
+ } finally {
4145
+ if (!ownershipTransferred) {
4146
+ let failure;
4147
+ try {
4148
+ if (preserveSubscription) {
4149
+ if (!await retainCodexAppServerBindingSubscription(params.client, binding.threadId, retainedThread)) failure = { cause: /* @__PURE__ */ new Error("Codex live thread ownership could not be returned to its session") };
4150
+ } else await retainedThread.release(binding.threadId);
4151
+ } catch (cause) {
4152
+ failure = { cause };
4153
+ }
4154
+ if (failure) await abandonCodexLiveThreadRelease({
4155
+ client: params.client,
4156
+ abandonClient: params.abandonClient,
4157
+ lifecycleTiming,
4158
+ threadId: binding.threadId
4159
+ }, failure.cause);
4160
+ }
4161
+ }
4162
+ }
4163
+ //#endregion
4164
+ //#region extensions/codex/src/app-server/thread-lifecycle-adoption.ts
4165
+ /** Passive refusal must precede releasing or acquiring any native subscription. */
4166
+ async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context) {
4167
+ const { thread } = await context.lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
4168
+ threadId,
4169
+ includeTurns: false
4170
+ }, { signal: params.signal }));
4171
+ context.throwIfAborted();
4172
+ assertCodexThreadAcceptsDirectInput(thread);
4173
+ if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
4174
+ if (thread.id !== threadId) throw new Error("Codex returned another thread during adoption status read");
4175
+ return thread;
4176
+ }
4177
+ /** All bound preparation follows attach's native-queue-before-binding-lease order. */
4178
+ async function withCodexThreadLifecycleBinding(params, run) {
4179
+ const identity = sessionBindingIdentity({
4180
+ sessionId: params.params.sessionId,
4181
+ sessionKey: params.params.sessionKey,
4182
+ agentId: params.agentId ?? params.params.agentId,
4183
+ config: params.params.config
4184
+ });
4185
+ const snapshot = await params.bindingStore.read(identity);
4186
+ const runWithLease = () => params.bindingStore.withLease(identity, async () => {
4187
+ const binding = await params.bindingStore.read(identity);
4188
+ 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);
4192
+ });
4193
+ return snapshot?.pendingResumeConfiguration ? await withExclusiveCodexAppServerThread({
4194
+ bindingStore: params.bindingStore,
4195
+ identity,
4196
+ threadId: snapshot.threadId,
4197
+ run: runWithLease
4198
+ }) : snapshot ? await withCodexAppServerThreadMutation(snapshot.threadId, runWithLease) : await runWithLease();
4199
+ }
4200
+ /** Completes manual attachment only under the native queue and exact binding lease. */
4201
+ async function resumePendingCodexThread(params, context) {
4202
+ const { binding, contextEngineBinding, lifecycleTiming, restrictedToolSurface } = context;
4203
+ if (isIncognitoSessionKey(params.params.sessionKey) || context.transientRestriction || !restrictedToolSurface && binding.nativeToolPolicyRestricted === true || (contextEngineBinding ? !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding) : binding.contextEngine !== void 0) || shouldRotateCodexGpt56MultiAgentBinding({
4204
+ bindingModel: binding.model,
4205
+ requestedModel: params.params.modelId
4206
+ })) 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
+ const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
4208
+ 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
+ });
4219
+ });
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
+ }
4232
+ }
4233
+ /** Manual attachment is intent, never evidence that loaded native overrides took effect. */
4234
+ async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
4235
+ 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
+ const agentDir = resolveCodexThreadAgentDir(params);
4237
+ const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
4238
+ if (params.appServer.start.transport !== "stdio" || params.appServer.start.homeScope === "user" || path.resolve(localHome) !== resolveCodexAppServerHomeDir(agentDir) || binding.connectionScope === "supervision" || binding.preserveNativeModel === true) throw fail("configuration adoption requires an OpenClaw-owned local Codex home");
4239
+ if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw fail("the thread is claimed by active work; stop that run before resuming");
4240
+ const assertClient = captureCodexAppServerClientLifetime(params.client, "native-process");
4241
+ const assertCurrent = () => {
4242
+ params.params.hostCapabilities.assertActive();
4243
+ params.signal?.throwIfAborted();
4244
+ assertClient();
4245
+ if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
4246
+ };
4247
+ assertCurrent();
4248
+ const { thread } = await params.client.request("thread/read", {
4249
+ threadId: binding.threadId,
4250
+ includeTurns: false
4251
+ }, { signal: params.signal });
4252
+ assertCurrent();
4253
+ const statusType = thread.status?.type;
4254
+ if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
4255
+ assertCodexThreadAcceptsDirectInput(thread);
4256
+ const observation = observeCodexThreadConfiguration(params, thread, assertCurrent);
4257
+ const dispose = observation.dispose;
4258
+ try {
4259
+ const rolloutPath = thread.path ?? binding.rolloutPath;
4260
+ const metadata = rolloutPath ? await readCodexSessionMeta(path.join(localHome, "sessions"), rolloutPath, binding.threadId) : void 0;
4261
+ if (!metadata) throw fail("its native tool catalog could not be read from the selected Codex home");
4262
+ const recordedTools = metadata.dynamic_tools ?? [];
4263
+ if (!Array.isArray(recordedTools) || codexDynamicToolsFingerprint(recordedTools) !== dynamicToolsFingerprint) throw fail("its immutable native tool catalog does not match the current OpenClaw tools");
4264
+ assertCurrent();
4265
+ await releaseSubscription(assertCurrent);
4266
+ assertCurrent();
4267
+ return {
4268
+ assertConfigured: observation.assertConfigured,
4269
+ assertCurrent,
4270
+ dispose
4067
4271
  };
4068
4272
  } catch (error) {
4069
- await releaseConsumedThread(binding.threadId, error);
4273
+ dispose();
4070
4274
  throw error;
4071
4275
  }
4072
4276
  }
4277
+ /** Observe teardown before release; a successful resume alone can acknowledge ignored overrides. */
4278
+ async function prepareCodexThreadResume(params, binding, context) {
4279
+ const assertClient = captureCodexAppServerClientLifetime(params.client, binding.connectionScope === "supervision" ? "connection" : "native-process");
4280
+ const assertCurrent = () => {
4281
+ params.params.hostCapabilities.assertActive();
4282
+ params.signal?.throwIfAborted();
4283
+ assertClient();
4284
+ if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
4285
+ };
4286
+ assertCurrent();
4287
+ const thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context);
4288
+ assertCurrent();
4289
+ assertCodexSupervisionThreadLineage(binding, thread);
4290
+ return {
4291
+ ...observeCodexThreadConfiguration(params, thread, assertCurrent),
4292
+ assertCurrent
4293
+ };
4294
+ }
4295
+ function observeCodexThreadConfiguration(params, thread, assertCurrent) {
4296
+ if (thread.status?.type !== "idle" && thread.status?.type !== "notLoaded") throw new CodexAdoptedThreadActiveError();
4297
+ let unloaded = thread.status.type === "notLoaded";
4298
+ return {
4299
+ dispose: params.client.addNotificationHandler((notification) => {
4300
+ if (notification.method === "thread/status/changed" && isJsonObject(notification.params) && notification.params.threadId === thread.id && isJsonObject(notification.params.status) && notification.params.status.type === "notLoaded") unloaded = true;
4301
+ }),
4302
+ assertConfigured: () => {
4303
+ assertCurrent();
4304
+ if (!unloaded) throw new Error("Codex did not confirm unloading its previous configuration. The thread is preserved; stop competing native work and reconnect before retrying.");
4305
+ }
4306
+ };
4307
+ }
4073
4308
  //#endregion
4074
4309
  //#region extensions/codex/src/app-server/thread-supervision.ts
4075
4310
  async function materializePendingSupervisionBranch(params) {
@@ -4094,6 +4329,32 @@ async function materializePendingSupervisionBranch(params) {
4094
4329
  });
4095
4330
  let bindingCommitted = false;
4096
4331
  let provisionalCleanupSafe = true;
4332
+ let cleanupExpected = pending;
4333
+ const trackPendingSupervisionArtifacts = async (cleanupThreadIds) => {
4334
+ const expected = pending;
4335
+ pending = withPendingSupervisionCleanup(pending, cleanupThreadIds);
4336
+ let updated;
4337
+ try {
4338
+ updated = await params.bindingStore.mutate(params.bindingIdentity, {
4339
+ kind: "patch-pending-supervision-branch",
4340
+ expected,
4341
+ pending
4342
+ });
4343
+ } catch (error) {
4344
+ try {
4345
+ const current = await params.bindingStore.read(params.bindingIdentity);
4346
+ if (matchesPendingSupervisionState(current, pending)) cleanupExpected = pending;
4347
+ else if (matchesPendingSupervisionState(current, expected)) cleanupExpected = expected;
4348
+ else throw new CodexThreadBindingConflictError(pending.sourceThreadId, "verifying supervised Codex cleanup tracking");
4349
+ } catch (verificationError) {
4350
+ provisionalCleanupSafe = false;
4351
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup tracking could not be verified: ${cleanupThreadIds.join(", ")}`, { cause: new AggregateError([error, verificationError], void 0, { cause: error }) });
4352
+ }
4353
+ throw error;
4354
+ }
4355
+ cleanupExpected = updated ? pending : void 0;
4356
+ if (!updated) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
4357
+ };
4097
4358
  try {
4098
4359
  const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
4099
4360
  const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
@@ -4109,10 +4370,17 @@ async function materializePendingSupervisionBranch(params) {
4109
4370
  sourceThreadId: pending.sourceThreadId,
4110
4371
  role: "model probe"
4111
4372
  });
4112
- pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId]);
4373
+ let probeResponse;
4374
+ try {
4375
+ params.throwIfAborted();
4376
+ probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
4377
+ if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
4378
+ } finally {
4379
+ await unsubscribeCodexAppServerLiveThread(params.client, probeThreadId, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS).catch((cause) => {
4380
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex model probe subscription could not be released: ${probeThreadId}`, { cause });
4381
+ });
4382
+ }
4113
4383
  params.throwIfAborted();
4114
- const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
4115
- if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
4116
4384
  const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
4117
4385
  const nativeModelProvider = requireNativeSupervisionModelProvider({
4118
4386
  responseModelProvider: probeResponse.modelProvider,
@@ -4158,7 +4426,7 @@ async function materializePendingSupervisionBranch(params) {
4158
4426
  otherThreadId: probeThreadId,
4159
4427
  role: "canonical branch"
4160
4428
  });
4161
- pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId, finalThreadId]);
4429
+ await trackPendingSupervisionArtifacts([finalThreadId]);
4162
4430
  params.throwIfAborted();
4163
4431
  assertExactSupervisionModelSelection(assertCodexThreadStartResponse(rawStartResponse), {
4164
4432
  model: nativeModel,
@@ -4178,11 +4446,11 @@ async function materializePendingSupervisionBranch(params) {
4178
4446
  client: params.client,
4179
4447
  threadId: finalThreadId,
4180
4448
  ephemeral: startParams.ephemeral === true
4181
- }) || !await archiveSupervisionArtifact(params.client, probeThreadId)) {
4449
+ })) {
4182
4450
  provisionalCleanupSafe = false;
4183
4451
  throw new CodexAppServerUnsafeSubscriptionError("Codex supervised plugin app attestation cleanup failed", { cause: error });
4184
4452
  }
4185
- pending = await trackPendingSupervisionArtifacts(params, pending, []);
4453
+ await trackPendingSupervisionArtifacts([]);
4186
4454
  throw error;
4187
4455
  }
4188
4456
  if (history.responseItems.length > 0) {
@@ -4192,8 +4460,6 @@ async function materializePendingSupervisionBranch(params) {
4192
4460
  }, { signal: params.signal }));
4193
4461
  params.throwIfAborted();
4194
4462
  }
4195
- if (!await archiveSupervisionArtifact(params.client, probeThreadId)) throw new Error(`Failed to archive temporary Codex model probe: ${probeThreadId}`);
4196
- pending = await trackPendingSupervisionArtifacts(params, pending, [finalThreadId]);
4197
4463
  const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
4198
4464
  const bindingModelProvider = params.normalizeBindingModelProvider(params.attempt.authProfileId, nativeModelProvider);
4199
4465
  let committed = false;
@@ -4255,31 +4521,28 @@ async function materializePendingSupervisionBranch(params) {
4255
4521
  };
4256
4522
  } catch (error) {
4257
4523
  if (bindingCommitted) throw error;
4258
- if (error instanceof CodexThreadBindingConflictAfterCleanupError) throw error;
4259
4524
  if (!provisionalCleanupSafe) {
4260
4525
  await params.abandonClient();
4261
4526
  throw error;
4262
4527
  }
4263
4528
  const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
4529
+ const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
4264
4530
  let cleanupStateError;
4265
- if (cleanup.remaining.length !== (pending.cleanupThreadIds?.length ?? 0)) {
4266
- const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
4267
- try {
4268
- if (await params.bindingStore.mutate(params.bindingIdentity, {
4269
- kind: "patch-pending-supervision-branch",
4270
- expected: pending,
4271
- pending: nextPending
4272
- })) pending = nextPending;
4273
- } catch (stateError) {
4274
- cleanupStateError = stateError;
4275
- }
4531
+ if (cleanupExpected && !isDeepStrictEqual(cleanupExpected, nextPending)) try {
4532
+ await params.bindingStore.mutate(params.bindingIdentity, {
4533
+ kind: "patch-pending-supervision-branch",
4534
+ expected: cleanupExpected,
4535
+ pending: nextPending
4536
+ });
4537
+ } catch (stateError) {
4538
+ cleanupStateError = stateError;
4276
4539
  }
4277
4540
  const unsafeCleanup = cleanup.remaining.length > 0 || isCodexAppServerUnsafeSubscriptionError(error);
4278
4541
  if (unsafeCleanup) await params.abandonClient();
4279
4542
  if (cleanupStateError) {
4280
- const cause = new AggregateError([error, cleanupStateError]);
4543
+ const cause = new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
4281
4544
  if (unsafeCleanup) throw new CodexAppServerUnsafeSubscriptionError("Codex supervised branch cleanup state could not be recorded", { cause });
4282
- throw new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
4545
+ throw cause;
4283
4546
  }
4284
4547
  if (cleanup.remaining.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup remains pending: ${cleanup.remaining.join(", ")}`, { cause: error });
4285
4548
  throw error;
@@ -4307,7 +4570,7 @@ function buildPendingSupervisionProbeForkParams(params, pending) {
4307
4570
  ...params.appServer.serviceTier !== void 0 ? { serviceTier: params.appServer.serviceTier } : {},
4308
4571
  config: runtimeConfig,
4309
4572
  developerInstructions: params.developerInstructions ?? buildDeveloperInstructions(params.attempt, { dynamicTools: params.dynamicTools }),
4310
- ephemeral: isIncognitoSessionKey(params.attempt.sessionKey),
4573
+ ephemeral: true,
4311
4574
  threadSource: "appServer",
4312
4575
  excludeTurns: true
4313
4576
  };
@@ -4349,10 +4612,8 @@ function requireDistinctSupervisionThreadId(params) {
4349
4612
  return threadId;
4350
4613
  }
4351
4614
  function readSupervisionResponseThreadId(value) {
4352
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
4353
- const thread = value.thread;
4354
- if (!thread || typeof thread !== "object" || Array.isArray(thread)) return;
4355
- return thread.id;
4615
+ const thread = isRecord(value) ? value.thread : void 0;
4616
+ return isRecord(thread) ? thread.id : void 0;
4356
4617
  }
4357
4618
  async function recoverPendingSupervisionArtifacts(params, pending) {
4358
4619
  if (!pending.cleanupThreadIds?.length) return pending;
@@ -4375,20 +4636,6 @@ async function recoverPendingSupervisionArtifacts(params, pending) {
4375
4636
  })) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
4376
4637
  return next;
4377
4638
  }
4378
- async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
4379
- const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
4380
- if (!await params.bindingStore.mutate(params.bindingIdentity, {
4381
- kind: "patch-pending-supervision-branch",
4382
- expected: pending,
4383
- pending: next
4384
- })) {
4385
- const cleanupFailed = [];
4386
- for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
4387
- if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
4388
- throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
4389
- }
4390
- return next;
4391
- }
4392
4639
  function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
4393
4640
  return {
4394
4641
  sourceThreadId: pending.sourceThreadId,
@@ -4425,15 +4672,23 @@ async function archiveSupervisionArtifact(client, threadId) {
4425
4672
  async function startOrResumeThread(params) {
4426
4673
  const incognito = isIncognitoSessionKey(params.params.sessionKey);
4427
4674
  const clientId = resolveCodexAppServerClientInstanceId(params.client);
4428
- const bindingIdentity = sessionBindingIdentity({
4429
- sessionId: params.params.sessionId,
4430
- sessionKey: params.params.sessionKey,
4431
- agentId: params.agentId ?? params.params.agentId,
4432
- config: params.params.config
4433
- });
4434
- return await params.bindingStore.withLease(bindingIdentity, async () => {
4435
- const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = await prepareCodexThreadLifecyclePreflight(params);
4436
- let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
4675
+ return await withCodexThreadLifecycleBinding(params, async (bindingIdentity, currentBinding) => {
4676
+ let binding = currentBinding;
4677
+ if (hasCodexNativeToolCatalog(binding)) {
4678
+ const nativeCatalog = await loadCodexNativeToolCatalog({
4679
+ client: params.client,
4680
+ binding,
4681
+ appServer: params.appServer,
4682
+ agentDir: resolveCodexThreadAgentDir(params),
4683
+ assertCurrent: () => {
4684
+ params.signal?.throwIfAborted();
4685
+ params.params.hostCapabilities.assertActive();
4686
+ }
4687
+ });
4688
+ if (!isDeepStrictEqual(params.dynamicTools, nativeCatalog)) throw new Error("Canonical Codex declarations changed after tool preparation; retry the turn on its preserved native thread.");
4689
+ }
4690
+ const preflight = await prepareCodexThreadLifecyclePreflight(params);
4691
+ const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = preflight;
4437
4692
  let replacementPredecessor;
4438
4693
  const initialBoundThreadId = binding?.threadId;
4439
4694
  const initialBoundClientId = binding?.clientId;
@@ -4445,28 +4700,15 @@ async function startOrResumeThread(params) {
4445
4700
  config: params.params.config
4446
4701
  });
4447
4702
  const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
4448
- const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId) => {
4449
- if (ownerClientId && ownerClientId !== clientId) {
4450
- const previousClient = retainSharedCodexAppServerClientByInstanceId(ownerClientId);
4451
- if (!previousClient) return;
4452
- try {
4453
- await releaseCodexRetainedLiveThread({
4454
- client: previousClient.client,
4455
- lifecycleTiming,
4456
- threadId
4457
- });
4458
- } finally {
4459
- previousClient.release();
4460
- }
4461
- return;
4462
- }
4463
- await releaseCodexRetainedLiveThread({
4464
- client: params.client,
4465
- abandonClient: params.abandonClient,
4466
- lifecycleTiming,
4467
- threadId
4468
- });
4469
- };
4703
+ const releaseRetainedThread = (threadId, ownerClientId = initialBoundClientId, assertCurrent) => releaseCodexBoundLiveThread({
4704
+ client: params.client,
4705
+ clientId,
4706
+ ownerClientId,
4707
+ abandonClient: params.abandonClient,
4708
+ lifecycleTiming,
4709
+ threadId,
4710
+ assertCurrent
4711
+ });
4470
4712
  if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
4471
4713
  if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
4472
4714
  bindingStore: params.bindingStore,
@@ -4546,6 +4788,36 @@ async function startOrResumeThread(params) {
4546
4788
  })) throw new CodexThreadBindingConflictError(current.threadId, operation);
4547
4789
  binding = void 0;
4548
4790
  };
4791
+ const resolveRequestContext = () => {
4792
+ const startModelSelection = resolveCodexAppServerThreadModelSelection({
4793
+ provider: params.params.provider,
4794
+ model: params.runtimeModelId ?? params.params.modelId,
4795
+ binding,
4796
+ authProfileId: params.params.authProfileId,
4797
+ authProfileStore: params.params.authProfileStore,
4798
+ agentDir: params.params.agentDir,
4799
+ config: params.params.config
4800
+ });
4801
+ return {
4802
+ ...preflight,
4803
+ bindingIdentity,
4804
+ startModelSelection,
4805
+ startModelProvider: startModelSelection.modelProvider,
4806
+ normalizeBindingModelProvider,
4807
+ throwIfAborted
4808
+ };
4809
+ };
4810
+ const transientDelegationRestriction = params.params.delegationCapability === "report_only";
4811
+ const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
4812
+ const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
4813
+ const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
4814
+ if (binding?.pendingResumeConfiguration) return await resumePendingCodexThread(params, {
4815
+ ...resolveRequestContext(),
4816
+ binding,
4817
+ clearCurrentBinding,
4818
+ releaseRetainedThread: (threadId, assertCurrent) => releaseRetainedThread(threadId, initialBoundClientId, assertCurrent),
4819
+ transientRestriction: transientDelegationRestriction || transientNativeToolRestriction || transientWebSearchRestriction
4820
+ });
4549
4821
  if (binding?.threadId && !restrictedToolSurface && binding.nativeToolPolicyRestricted === true) await clearCurrentBinding("rotating a host-policy-restricted thread binding");
4550
4822
  if (binding?.threadId && binding.nativeSkillIsolationFingerprint !== nativeSkillIsolationFingerprint) {
4551
4823
  embeddedAgentLog.debug("codex app-server native skill isolation changed; starting a new thread", { threadId: binding.threadId });
@@ -4579,24 +4851,30 @@ async function startOrResumeThread(params) {
4579
4851
  await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
4580
4852
  binding = void 0;
4581
4853
  }
4582
- const startModelSelection = resolveCodexAppServerThreadModelSelection({
4583
- provider: params.params.provider,
4584
- model: params.runtimeModelId ?? params.params.modelId,
4585
- binding,
4586
- authProfileId: params.params.authProfileId,
4587
- authProfileStore: params.params.authProfileStore,
4588
- agentDir: params.params.agentDir,
4589
- config: params.params.config
4590
- });
4591
- const startModelProvider = startModelSelection.modelProvider;
4592
- const transientDelegationRestriction = params.params.delegationCapability === "report_only";
4854
+ const requestContext = resolveRequestContext();
4593
4855
  let preserveExistingBinding = transientDelegationRestriction || !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
4594
4856
  let rotatedContextEngineBinding = false;
4595
4857
  let prebuiltPluginThreadConfig;
4858
+ const buildLoadedPluginThreadConfig = async (current) => {
4859
+ if (!params.pluginThreadConfig?.requiresCurrentPolicyCheck && !shouldRecheckRecoverablePluginBinding({
4860
+ binding: current,
4861
+ pluginThreadConfig: params.pluginThreadConfig
4862
+ })) return;
4863
+ try {
4864
+ prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: current.threadId }));
4865
+ } catch (error) {
4866
+ throwIfAborted();
4867
+ if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
4868
+ embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
4869
+ error,
4870
+ threadId: current.threadId
4871
+ });
4872
+ return;
4873
+ }
4874
+ throwIfAborted();
4875
+ return prebuiltPluginThreadConfig;
4876
+ };
4596
4877
  const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
4597
- const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
4598
- const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
4599
- const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
4600
4878
  const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
4601
4879
  const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
4602
4880
  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) {
@@ -4674,28 +4952,13 @@ async function startOrResumeThread(params) {
4674
4952
  binding = void 0;
4675
4953
  }
4676
4954
  if (binding?.threadId) {
4677
- let pluginBindingStale = isCodexPluginThreadBindingStale({
4955
+ if (isCodexPluginThreadBindingStale({
4678
4956
  codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
4679
4957
  bindingFingerprint: binding.pluginAppsFingerprint,
4680
4958
  bindingInputFingerprint: binding.pluginAppsInputFingerprint,
4681
4959
  currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
4682
4960
  hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
4683
- });
4684
- if (!pluginBindingStale && (params.pluginThreadConfig?.requiresCurrentPolicyCheck || shouldRecheckRecoverablePluginBinding({
4685
- binding,
4686
- pluginThreadConfig: params.pluginThreadConfig
4687
- }))) try {
4688
- const bindingThreadId = binding.threadId;
4689
- prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: bindingThreadId }));
4690
- pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
4691
- } catch (error) {
4692
- if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
4693
- embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
4694
- error,
4695
- threadId: binding.threadId
4696
- });
4697
- }
4698
- if (pluginBindingStale) {
4961
+ })) {
4699
4962
  embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
4700
4963
  await clearCurrentBinding("rotating a stale thread binding");
4701
4964
  binding = void 0;
@@ -4708,120 +4971,55 @@ async function startOrResumeThread(params) {
4708
4971
  binding = void 0;
4709
4972
  }
4710
4973
  }
4711
- if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
4712
- assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
4713
- preserveExistingBinding = shouldStartTransientNoToolThread({
4714
- previous: binding.dynamicToolsFingerprint,
4715
- nextHasDynamicTools: params.dynamicTools.length > 0
4716
- });
4717
- if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
4718
- else {
4719
- embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
4720
- await clearCurrentBinding("rotating a stale thread binding");
4721
- }
4722
- } else if (incognito) {
4723
- if (binding.clientId && binding.clientId === clientId) {
4724
- params.buildFinalConfigPatch?.({
4725
- action: "resume",
4726
- binding
4974
+ if (binding?.threadId) {
4975
+ if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
4976
+ assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
4977
+ preserveExistingBinding = shouldStartTransientNoToolThread({
4978
+ previous: binding.dynamicToolsFingerprint,
4979
+ nextHasDynamicTools: params.dynamicTools.length > 0
4727
4980
  });
4728
- throwIfAborted();
4729
- lifecycleTiming.mark("thread-ready");
4730
- lifecycleTiming.logSummary({
4731
- runId: params.params.runId,
4732
- sessionId: params.params.sessionId,
4733
- sessionKey: params.params.sessionKey,
4734
- threadId: binding.threadId,
4735
- action: "resumed"
4981
+ if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
4982
+ else {
4983
+ embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
4984
+ await clearCurrentBinding("rotating a stale thread binding");
4985
+ }
4986
+ } else {
4987
+ const warmReuse = await tryReuseCodexLiveThread({
4988
+ ...requestContext,
4989
+ params,
4990
+ binding,
4991
+ clientId,
4992
+ buildLoadedPluginThreadConfig
4736
4993
  });
4737
- return {
4738
- ...binding,
4739
- lifecycle: { action: "resumed" }
4740
- };
4994
+ if (warmReuse.kind === "ready") return warmReuse.binding;
4995
+ if (incognito || warmReuse.kind === "rotate") {
4996
+ throwIfAborted();
4997
+ await clearCurrentBinding(incognito ? "rotating an unavailable ephemeral thread binding" : "rotating a stale plugin app binding");
4998
+ } 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
+ }
5017
+ }
4741
5018
  }
4742
- await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
4743
- binding = void 0;
4744
- } else {
4745
- const warmReuse = await tryReuseCodexLiveThread({
4746
- params,
4747
- binding,
4748
- bindingIdentity,
4749
- clientId,
4750
- dynamicToolsFingerprint,
4751
- environmentSelectionFingerprint,
4752
- hostSystemAgentActive,
4753
- lifecycleTiming,
4754
- nativeSkillIsolation,
4755
- releaseConsumedThread: (threadId, cause) => releaseCodexConsumedLiveThread({
4756
- client: params.client,
4757
- abandonClient: params.abandonClient,
4758
- lifecycleTiming,
4759
- threadId,
4760
- cause
4761
- }),
4762
- ringZeroActive,
4763
- restrictedToolSurfaceInheritedMcpServerNames,
4764
- startModelProvider,
4765
- startModelSelection,
4766
- throwIfAborted,
4767
- userMcpServersConfigPatch
4768
- });
4769
- if (warmReuse.binding) return warmReuse.binding;
4770
- await releaseRetainedThread(binding.threadId);
4771
- const resumed = await resumeExistingCodexThread(params, {
4772
- binding,
4773
- bindingIdentity,
4774
- startModelSelection,
4775
- startModelProvider,
4776
- userMcpServersConfigPatch,
4777
- dynamicToolsFingerprint,
4778
- dynamicToolsContainDeferred,
4779
- webSearchThreadConfigFingerprint,
4780
- nativeSkillIsolationFingerprint,
4781
- userMcpServersFingerprint,
4782
- ringZeroConfigFingerprint,
4783
- ringZeroClientInstanceId,
4784
- networkProxyConfigFingerprint,
4785
- contextEngineBinding,
4786
- environmentSelectionFingerprint,
4787
- hostSystemAgentActive,
4788
- ringZeroActive,
4789
- restrictedToolSurface,
4790
- restrictedToolSurfaceInheritedMcpServerNames,
4791
- nativeSkillIsolation,
4792
- lifecycleTiming,
4793
- normalizeBindingModelProvider,
4794
- throwIfAborted,
4795
- clearCurrentBinding,
4796
- prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
4797
- prebuiltPluginThreadConfig
4798
- });
4799
- if (resumed) return resumed;
4800
5019
  }
4801
5020
  if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
4802
5021
  const started = await startFreshCodexThread(params, {
4803
- bindingIdentity,
4804
- startModelSelection,
4805
- startModelProvider,
4806
- userMcpServersConfigPatch,
4807
- dynamicToolsFingerprint,
4808
- dynamicToolsContainDeferred,
4809
- webSearchThreadConfigFingerprint,
4810
- nativeSkillIsolationFingerprint,
4811
- userMcpServersFingerprint,
4812
- ringZeroConfigFingerprint,
4813
- ringZeroClientInstanceId,
4814
- networkProxyConfigFingerprint,
4815
- contextEngineBinding,
4816
- environmentSelectionFingerprint,
4817
- hostSystemAgentActive,
4818
- ringZeroActive,
4819
- restrictedToolSurface,
4820
- restrictedToolSurfaceInheritedMcpServerNames,
4821
- nativeSkillIsolation,
4822
- lifecycleTiming,
4823
- normalizeBindingModelProvider,
4824
- throwIfAborted,
5022
+ ...requestContext,
4825
5023
  prebuiltPluginThreadConfig,
4826
5024
  preserveExistingBinding,
4827
5025
  rotatedContextEngineBinding,
@@ -4832,6 +5030,54 @@ async function startOrResumeThread(params) {
4832
5030
  });
4833
5031
  }
4834
5032
  //#endregion
5033
+ //#region extensions/codex/src/app-server/reasoning-effort.ts
5034
+ const CODEX_REASONING_EFFORTS = [
5035
+ "minimal",
5036
+ "low",
5037
+ "medium",
5038
+ "high",
5039
+ "xhigh",
5040
+ "max"
5041
+ ];
5042
+ const LEGACY_PRO_REASONING_EFFORTS = [
5043
+ "medium",
5044
+ "high",
5045
+ "xhigh"
5046
+ ];
5047
+ const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
5048
+ const MODERN_GPT_5_MODEL_ID_RE = /^gpt-5\.(?:[3-9]|[1-9]\d)(?:$|-)/u;
5049
+ /** Read reasoning metadata after the Codex app-server route has been selected. */
5050
+ function readCodexSupportedReasoningEfforts(compat) {
5051
+ if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
5052
+ const efforts = compat.supportedReasoningEfforts;
5053
+ if (!Array.isArray(efforts)) return;
5054
+ return efforts.filter((effort) => typeof effort === "string");
5055
+ }
5056
+ function resolveSupportedReasoningEffort(params) {
5057
+ const declared = new Set(params.supportedReasoningEfforts.map((effort) => effort.trim().toLowerCase()));
5058
+ const supported = CODEX_REASONING_EFFORTS.filter((effort) => declared.has(effort));
5059
+ if (supported.includes(params.requested)) return params.requested;
5060
+ const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
5061
+ return supported.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? supported.at(-1);
5062
+ }
5063
+ function resolveCodexAppServerReasoningEffort(params) {
5064
+ if (params.thinkLevel === "ultra") return "ultra";
5065
+ if (params.thinkLevel === "off") return params.supportedReasoningEfforts?.includes("none") ? "none" : null;
5066
+ if (params.thinkLevel === "adaptive") return null;
5067
+ if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
5068
+ requested: params.thinkLevel,
5069
+ supportedReasoningEfforts: params.supportedReasoningEfforts
5070
+ }) ?? null;
5071
+ const modelId = params.modelId.trim().toLowerCase();
5072
+ if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
5073
+ requested: params.thinkLevel,
5074
+ supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
5075
+ }) ?? null;
5076
+ if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
5077
+ if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
5078
+ return null;
5079
+ }
5080
+ //#endregion
4835
5081
  //#region extensions/codex/src/app-server/user-input.ts
4836
5082
  /** Builds ordered Codex user input for both new turns and same-turn steering. */
4837
5083
  function buildCodexUserInput(text, images) {
@@ -4884,12 +5130,29 @@ function buildTurnStartParams(params, options) {
4884
5130
  agentDir: params.agentDir,
4885
5131
  config: params.config
4886
5132
  });
5133
+ const collaborationMode = modelSelection ? buildTurnCollaborationMode(params, {
5134
+ model: modelSelection.model,
5135
+ turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
5136
+ skillsCollaborationInstructions: options.skillsCollaborationInstructions,
5137
+ memoryCollaborationInstructions: options.memoryCollaborationInstructions
5138
+ }) : void 0;
4887
5139
  const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
4888
5140
  const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
4889
- const additionalContext = currentSenderContext ? { openclaw_current_sender: {
4890
- kind: "untrusted",
4891
- value: currentSenderContext
4892
- } } : void 0;
5141
+ let additionalContext = buildCodexTemporalAdditionalContext(params, { sessionStatusAvailable: options.sessionStatusAvailable === true });
5142
+ if (currentSenderContext) additionalContext = {
5143
+ ...additionalContext,
5144
+ openclaw_current_sender: {
5145
+ kind: "untrusted",
5146
+ value: currentSenderContext
5147
+ }
5148
+ };
5149
+ if (params.permissionChange?.notice) additionalContext = {
5150
+ ...additionalContext,
5151
+ openclaw_permission_change: {
5152
+ kind: "application",
5153
+ value: params.permissionChange.notice
5154
+ }
5155
+ };
4893
5156
  return {
4894
5157
  threadId: options.threadId,
4895
5158
  input: [...buildCodexUserInput(options.promptText ?? params.prompt, params.images), ...options.explicitSkillInputs ?? []],
@@ -4904,23 +5167,33 @@ function buildTurnStartParams(params, options) {
4904
5167
  personality: CODEX_NATIVE_PERSONALITY_NONE
4905
5168
  } : {},
4906
5169
  ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : options.clearInheritedServiceTier ? { serviceTier: null } : {},
4907
- ...modelSelection ? { effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)) } : {},
4908
- ...options.environmentSelection ? { environments: options.environmentSelection } : {},
4909
- ...modelSelection ? { collaborationMode: buildTurnCollaborationMode(params, {
4910
- model: modelSelection.model,
4911
- turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
4912
- skillsCollaborationInstructions: options.skillsCollaborationInstructions,
4913
- memoryCollaborationInstructions: options.memoryCollaborationInstructions
4914
- }) } : {}
5170
+ ...collaborationMode ? {
5171
+ effort: collaborationMode.settings.reasoning_effort,
5172
+ collaborationMode
5173
+ } : {},
5174
+ ...options.environmentSelection ? { environments: options.environmentSelection } : {}
4915
5175
  };
4916
5176
  }
5177
+ function buildCodexTemporalAdditionalContext(params, options) {
5178
+ return { openclaw_temporal_context: {
5179
+ kind: "application",
5180
+ value: buildTemporalContextText({
5181
+ configuredTimezone: params.config?.agents?.defaults?.userTimezone,
5182
+ sessionStatusAvailable: options.sessionStatusAvailable
5183
+ })
5184
+ } };
5185
+ }
4917
5186
  function buildTurnCollaborationMode(params, options = {}) {
4918
5187
  const model = options.model ?? params.modelId;
4919
5188
  return {
4920
5189
  mode: "default",
4921
5190
  settings: {
4922
5191
  model,
4923
- reasoning_effort: resolveReasoningEffort(params.thinkLevel, model, readCodexSupportedReasoningEfforts(params.model?.compat)),
5192
+ reasoning_effort: resolveCodexAppServerReasoningEffort({
5193
+ thinkLevel: params.thinkLevel,
5194
+ modelId: model,
5195
+ supportedReasoningEfforts: readCodexSupportedReasoningEfforts(params.model?.compat)
5196
+ }),
4924
5197
  developer_instructions: buildTurnScopedCollaborationInstructions(params, options)
4925
5198
  }
4926
5199
  };
@@ -4958,4 +5231,4 @@ function joinPresentSections(...sections) {
4958
5231
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
4959
5232
  }
4960
5233
  //#endregion
4961
- export { isForcedPrivateQaCodexRuntime as $, attestCodexRestrictedToolSurfaceMcpServersDisabled as A, resolveCodexAppServerRequestModelSelection as B, toCodexDynamicToolProgressResponse as C, refreshCodexPluginRuntimeState as Ct, withDynamicToolExecutionState as D, resolveRecoverableCodexPluginConfigKeys as Dt, createFailedDynamicToolResponse as E, pluginReadParams as Et, resolveCodexWebSearchPlan as F, CODEX_MANAGED_THREAD_MAX_ENTRIES as G, resolveCodexBindingModelProviderFallback as H, buildDeveloperInstructions as I, areCodexDynamicToolFingerprintsCompatible as J, CODEX_MANAGED_THREAD_NAMESPACE as K, buildCodexProjectDocThreadConfig as L, buildCodexRuntimeThreadConfig as M, readCodexInheritedMcpServerNames as N, withDynamicToolTranscriptDetails as O, buildCodexNativeWebSearchThreadConfig as P, filterCodexDynamicToolsForDisabledNativeSurface as Q, CODEX_NATIVE_PERSONALITY_NONE as R, shouldReleaseTurnAfterTerminalDynamicTool as S, ensureCodexPluginActivation as St, resolveCodexToolAbortTerminalReason as T, isOpenAiCuratedMarketplaceName as Tt, resolveReasoningEffort as U, resolveCodexAppServerThreadModelSelection as V, readCodexSupportedReasoningEfforts as W, codexLegacyDynamicToolsFingerprint as X, codexDynamicToolsFingerprint as Y, filterCodexDynamicTools as Z, isDynamicToolTerminalDiagnosticEvent as _, buildCodexPluginThreadConfigTimeoutFallback as _t, isCodexAppServerProfilerEnabled as a, buildContextEngineBinding as at, resolveTerminalDynamicToolBatchAction as b, mergeCodexThreadConfigs as bt, buildCodexNativeHookRelayConfig as c, fitCodexProjectedContextForTurnStart as ct, emitCodexNativePreToolUseFailureDiagnostic as d, resolveCodexContextEngineProjectionMaxChars as dt, isMessageOnlyCodexSourceReply as et, resolveCodexNativeHookRelayEvents as f, resolveCodexContextEngineProjectionReserveTokens as ft, hasPendingDynamicToolTerminalDiagnostic as g, buildCodexPluginThreadConfigInputFingerprint as gt, handleDynamicToolCallWithTimeout as h, buildCodexPluginThreadConfig as ht, startOrResumeThread as i, resolveCodexDynamicToolsLoadingForRuntime as it, buildCodexRingZeroThreadConfigPatch as j, assertCodexManagedRequirementsDoNotOverrideToolPolicy as k, buildCodexNativeHookRelayDisabledConfig as l, neutralizeCodexExplicitMentionSigils as lt, scheduleCodexNativeHookRelayUnregister as m, buildCodexPluginAppsConfigPatchFromPolicyContext as mt, buildTurnStartParams as n, normalizeCodexDynamicToolName as nt, CODEX_NATIVE_HOOK_RELAY_EVENTS as o, isContextEngineBindingCompatible as ot, resolveCodexNativeHookRelayTtlMs as p, resolveCodexContinuityProjectionMaxChars as pt, createCodexManagedThreadStore as q, buildCodexUserInput as r, resolveCodexDynamicToolsLoading as rt, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as s, buildCodexContinuityCalibration as st, buildTurnCollaborationMode as t, isSystemAgentOnlyCodexDynamicToolAllowlist as tt, createCodexNativeHookRelay as u, projectContextEngineAssemblyForCodex as ut, isMatchingDynamicToolTerminalDiagnostic as v, buildDisabledAppsConfigPatch as vt, toCodexDynamicToolProtocolResponse as w, isOpenAiCuratedMarketplace as wt, shouldBlockTerminalReleaseForNonTerminalDynamicToolResult as x, shouldBuildCodexPluginThreadConfig as xt, resolveDynamicToolCallTimeoutMs as y, buildPluginAppPolicyContext as yt, resolveCodexAppServerModelProvider as z };
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 };