@openclaw/codex 2026.8.2 → 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/api.js +3 -3
  2. package/dist/{attempt-client-cleanup-CpVfwctT.js → attempt-client-cleanup-DYHcRQAa.js} +6 -43
  3. package/dist/attempt-context-DmQUNWwT.js +634 -0
  4. package/dist/{binding-connection-CF7BmrDk.js → binding-connection-E6uDsPlx.js} +2 -2
  5. package/dist/{bounded-turn-DRC3hcjm.js → bounded-turn-wzJrwKKe.js} +8 -9
  6. package/dist/{capabilities-D6m_TdVy.js → capabilities-dIjNVCYH.js} +1 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters--UToCokS.js → command-formatters-CLHhEB1Q.js} +3 -3
  9. package/dist/{command-handlers-CLFx2Ypa.js → command-handlers-CTGb2csu.js} +54 -55
  10. package/dist/{compact-5xqW1oCh.js → compact-_t0HQrUS.js} +33 -25
  11. package/dist/{config-CcYL77O6.js → config-BhmfC1s6.js} +12 -8
  12. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  13. package/dist/doctor-contract-api.js +31 -4
  14. package/dist/{dynamic-tools-BmeXjwNZ.js → dynamic-tools-BU9tmlYm.js} +448 -457
  15. package/dist/{effective-mcp-catalog-XXQKgK1U.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
  16. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
  17. package/dist/{event-projector-usage-B2r5vhWH.js → event-projector-usage-C5mcrGjp.js} +9 -137
  18. package/dist/{harness-BgeB7LnD.js → harness-DXVZ5II5.js} +13 -13
  19. package/dist/harness.js +1 -1
  20. package/dist/index.js +323 -313
  21. package/dist/{isolated-completion-CxTus3vZ.js → isolated-completion-kg9kv4iH.js} +4 -4
  22. package/dist/{launch-args-sXOAg0SX.js → launch-args-RXQwn8zV.js} +7 -1
  23. package/dist/{managed-binary-ZkGodW19.js → managed-binary-4-Vjz5GH.js} +14 -9
  24. package/dist/media-understanding-provider.js +1 -1
  25. package/dist/{model-catalog-DULBcniB.js → model-catalog-CUM68Hkl.js} +11 -11
  26. package/dist/{models-DaWfETQk.js → models-sYnEc0gd.js} +3 -3
  27. package/dist/{native-subagent-monitor-vXYUZH37.js → native-subagent-monitor-CnH147jH.js} +3 -3
  28. package/dist/{node-exec-server.runtime-DirKVZ5h.js → node-exec-server.runtime-CstNHCmC.js} +2 -2
  29. package/dist/{plugin-approval-roundtrip-CCWQGlAI.js → plugin-approval-roundtrip-CNe-Xrjx.js} +29 -3
  30. package/dist/{plugin-marketplace-discovery-BXjIFEiw.js → plugin-marketplace-discovery-CfFlKqDl.js} +6 -6
  31. package/dist/{plugin-metadata-cache-DQQm4gWT.js → plugin-metadata-cache-BFrPhb2c.js} +5 -5
  32. package/dist/provider-capabilities-DW5p4k9w.js +101 -0
  33. package/dist/{rate-limits-BqHEHXQJ.js → rate-limits-CawQJfUS.js} +1 -1
  34. package/dist/{request-BWxweEdK.js → request-DYAI5FQE.js} +18 -6
  35. package/dist/{run-attempt-BVLE4Gbi.js → run-attempt-D1hJO4fX.js} +483 -1865
  36. package/dist/{runtime-artifact-D9pYp4zL.js → runtime-artifact-C3jVimFn.js} +19 -12
  37. package/dist/{session-binding-D0zinCP1.js → session-binding-BHuTCRZQ.js} +8 -10
  38. package/dist/{session-catalog-eMhVm0Ql.js → session-catalog-yyvTWFOw.js} +138 -116
  39. package/dist/{session-cli-B2lyjJA0.js → session-cli-CmFEXMAM.js} +1 -1
  40. package/dist/session-initialization-BeWzjGiR.js +74 -0
  41. package/dist/{session-permission-policy-ScnAyG90.js → session-permission-policy-DeWVTCMj.js} +2 -2
  42. package/dist/{session-retirement-CtUtUTr9.js → session-retirement-BujDjXVF.js} +16 -9
  43. package/dist/{session-upstream-marker-gqrYIRHk.js → session-upstream-marker-C351-5Qw.js} +18 -2
  44. package/dist/{settled-turn-context-Bk3IUwod.js → settled-turn-context-D3SNWtUn.js} +6 -28
  45. package/dist/{settled-turn-finalizer-BXLN9PfJ.js → settled-turn-finalizer-BVJx4yoc.js} +5 -5
  46. package/dist/{shared-client-B1tT5U6r.js → shared-client-DxcfF9KX.js} +2748 -101
  47. package/dist/{side-question-CZ-mCw-t.js → side-question-BTZAII4l.js} +97 -63
  48. package/dist/{thread-lifecycle-CGGKP720.js → thread-lifecycle-DAcEJ_Om.js} +2039 -1487
  49. package/dist/{thread-ownership-RkaoZCmQ.js → thread-ownership-zGokd8i1.js} +10 -9
  50. package/dist/thread-policy-g4KbJPzp.js +54 -0
  51. package/dist/{thread-resume-BFt1-ZDa.js → thread-resume-C7pyVKeW.js} +15 -7
  52. package/dist/{transcript-mirror-1ep4XjzF.js → transcript-mirror-DTUxFWa_.js} +47 -10
  53. package/dist/{transport-stdio-NT_Dpd9V.js → transport-stdio-CyPldczm.js} +3 -1
  54. package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
  55. package/dist/{usage-DUhd58gI.js → usage-Bj-rrzkR.js} +3 -3
  56. package/dist/{web-search-provider.runtime-BEmlE7ov.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
  57. package/openclaw.plugin.json +0 -30
  58. package/package.json +5 -5
  59. package/dist/protocol-validators-Bg5rpNxk.js +0 -2542
  60. package/dist/session-catalog-home-id-Dt2GiG5o.js +0 -112
  61. package/dist/upstream-session-fork-Dc8BFExw.js +0 -312
@@ -1,19 +1,19 @@
1
- import { E as resolveCodexPluginsPolicy, F as resolveCodexAppServerLocalHomeDir, M as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, P as resolveCodexAppServerHomeDir, f as assertCodexModelBackedReviewerEffectiveConfig, j as CODEX_PLUGINS_MARKETPLACE_NAME, n as codexSandboxPolicyForTurn } from "./config-CcYL77O6.js";
2
- import { $ as CodexAppServerRpcError, D as CodexThreadStartRequestError, E as CodexThreadBindingConflictError, H as getCodexAppServerClientInstanceId, Ht as isJsonObject, J as isCodexAppServerOverloadError, Q as resolveCodexAppServerClientInstanceId, St as releaseCodexAppServerLiveThread, T as CodexRestrictedToolSurfaceAttestationError, Vt as flattenCodexDynamicToolFunctions, _ as retainSharedCodexAppServerClientByInstanceId, bt as isCodexAppServerLiveThreadClaimed, dt as resolveCodexToolAbortTerminalReason, gt as consumeCodexAppServerLiveThread, n as captureExclusiveSharedCodexAppServerClient, w as CodexAdoptedThreadActiveError, wt as unsubscribeCodexAppServerLiveThread, yt as isCodexAppServerClientRuntimeLive } from "./shared-client-B1tT5U6r.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-D0zinCP1.js";
4
- import { a as assertCodexThreadForkResponse, r as assertCodexThreadAcceptsDirectInput, s as assertCodexThreadStartResponse, t as CodexThreadDirectInputError } from "./protocol-validators-Bg5rpNxk.js";
5
- import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, f as unsubscribeCodexThreadBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-CpVfwctT.js";
6
- import { c as serializeCodexAppInventoryError, n as buildCodexAppServerConnectionFingerprint, s as defaultCodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-BFt1-ZDa.js";
7
- import { c as withExclusiveCodexAppServerThread, l as isIncognitoSessionKey } from "./thread-ownership-RkaoZCmQ.js";
8
- import { i as readCodexSessionMeta, n as codexCatalogHomeId } from "./session-catalog-home-id-Dt2GiG5o.js";
9
- import { f as projectBoundedCodexThreadHistory } from "./transcript-mirror-1ep4XjzF.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";
10
9
  import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
11
10
  import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
12
11
  import fs from "node:fs/promises";
13
12
  import path from "node:path";
14
13
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
15
14
  import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
16
- import { SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
15
+ import { AgentHarnessPreflightError, SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, buildTemporalContextText, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
16
+ import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
17
17
  import * as crypto$1 from "node:crypto";
18
18
  import crypto, { createHash } from "node:crypto";
19
19
  import os from "node:os";
@@ -26,13 +26,12 @@ import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-util
26
26
  import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
27
27
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
28
28
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
29
- import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
30
29
  import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
31
- import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
32
- import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
33
30
  import { buildCodexUserMcpServersThreadConfigPatchForRun } from "openclaw/plugin-sdk/codex-mcp-projection";
34
31
  import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
35
32
  import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-flags";
33
+ import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
34
+ import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
36
35
  //#region extensions/codex/src/app-server/plugin-inventory.ts
37
36
  /**
38
37
  * Reads Codex plugin marketplace state and app inventory to decide which
@@ -281,25 +280,20 @@ function resolveOwnedApps(params) {
281
280
  accessible: info.isAccessible,
282
281
  enabled: info.isEnabled,
283
282
  needsAuth: !info.isAccessible
284
- }, resolveOwnedAppReadOnlyToolConfigKeys(info));
283
+ }, resolveOwnedAppApprovalOverrideKeys(info));
285
284
  }).toSorted((left, right) => left.id.localeCompare(right.id));
286
285
  }
287
- /** Returns the config keys that Codex metadata proves belong to read-only tools. */
288
- function resolveOwnedAppReadOnlyToolConfigKeys(app) {
286
+ /** Returns current tool keys whose overrides could bypass the requested reviewer. */
287
+ function resolveOwnedAppApprovalOverrideKeys(app) {
288
+ if (!app.toolSummaries) return {};
289
289
  const appName = app.name.trim();
290
290
  const appNameLower = appName.toLowerCase();
291
- const tools = app.toolSummaries ?? [];
292
- const writableToolConfigKeys = new Set(tools.filter((tool) => !tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
293
- appName,
294
- appNameLower,
295
- tool
296
- })));
297
- const keys = tools.filter((tool) => tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
291
+ const keys = app.toolSummaries.filter((tool) => !tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
298
292
  appName,
299
293
  appNameLower,
300
294
  tool
301
- })).filter((key) => !writableToolConfigKeys.has(key));
302
- return keys.length > 0 ? { readOnlyToolConfigKeys: Array.from(new Set(keys)).toSorted() } : {};
295
+ }));
296
+ return { approvalOverrideToolConfigKeys: Array.from(new Set(keys)).toSorted() };
303
297
  }
304
298
  function resolveAppToolConfigKeys(params) {
305
299
  const keys = [params.tool.name];
@@ -566,7 +560,7 @@ function toCodexPluginOwnedAccountApp(app) {
566
560
  accessible: app.isAccessible,
567
561
  enabled: app.isEnabled,
568
562
  needsAuth: !app.isAccessible,
569
- ...resolveOwnedAppReadOnlyToolConfigKeys(app)
563
+ ...resolveOwnedAppApprovalOverrideKeys(app)
570
564
  };
571
565
  }
572
566
  function resolveCodexThreadConfigAppsForRecord(params) {
@@ -623,7 +617,7 @@ function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
623
617
  * Builds Codex thread config patches that expose only policy-approved apps
624
618
  * for native Codex turns.
625
619
  */
626
- const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 3;
620
+ const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 4;
627
621
  const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
628
622
  /** Returns true when plugin config exists and thread config may need app patches. */
629
623
  function shouldBuildCodexPluginThreadConfig(pluginConfig) {
@@ -996,10 +990,11 @@ function readPersistedAppToolApprovalOverrideNames(config, app) {
996
990
  const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
997
991
  const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
998
992
  if (!isJsonObject(tools)) return [];
999
- return Object.entries(tools).filter(([toolName]) => !app.readOnlyToolConfigKeys?.includes(toolName)).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();
1000
995
  }
1001
996
  function hasPersistedToolApprovalOverride(value) {
1002
- return isJsonObject(value) && value.approval_mode !== void 0;
997
+ return isJsonObject(value) && value.approval_mode !== void 0 && value.approval_mode !== null;
1003
998
  }
1004
999
  function quoteConfigKeyPathSegment(segment) {
1005
1000
  return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
@@ -1043,11 +1038,11 @@ function mergeJsonObjects(left, right) {
1043
1038
  return merged;
1044
1039
  }
1045
1040
  function fingerprintJson(value) {
1046
- return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
1041
+ return crypto.createHash("sha256").update(stableStringify$1(value)).digest("hex");
1047
1042
  }
1048
- function stableStringify(value) {
1049
- if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
1050
- 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(",")}}`;
1051
1046
  return JSON.stringify(value);
1052
1047
  }
1053
1048
  //#endregion
@@ -1828,6 +1823,65 @@ function applyCodexNativeSkillIsolation(config, isolation) {
1828
1823
  };
1829
1824
  }
1830
1825
  //#endregion
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
+ }
1884
+ //#endregion
1831
1885
  //#region extensions/codex/src/app-server/managed-thread-store.ts
1832
1886
  const CODEX_MANAGED_THREAD_NAMESPACE = "app-server-managed-threads";
1833
1887
  const CODEX_MANAGED_THREAD_MAX_ENTRIES = 2e4;
@@ -1890,11 +1944,59 @@ function createCodexManagedThreadStore(state) {
1890
1944
  };
1891
1945
  }
1892
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
1893
1983
  //#region extensions/codex/src/app-server/plugin-thread-attestation.ts
1894
1984
  /**
1895
1985
  * Confirms admitted plugin and account apps against their actual Codex thread before
1896
1986
  * OpenClaw commits a binding or starts a turn.
1897
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
+ }
1898
2000
  var CodexPluginThreadAppAttestationError = class extends Error {
1899
2001
  constructor(message, options) {
1900
2002
  super(message, options);
@@ -1945,211 +2047,505 @@ async function discardUnattestedCodexPluginThread(params) {
1945
2047
  }
1946
2048
  }
1947
2049
  //#endregion
1948
- //#region extensions/codex/src/app-server/thread-lifecycle-result.ts
1949
- /** Materializes the public lifecycle result after a fresh thread is durably committed. */
1950
- function buildStartedCodexThreadBinding(input) {
1951
- const { context, params, response, startParams } = input;
1952
- return {
1953
- threadId: response.thread.id,
1954
- ...input.clientId ? { clientId: input.clientId } : {},
1955
- cwd: params.cwd,
1956
- ...input.rolloutPath ? { rolloutPath: input.rolloutPath } : {},
1957
- authProfileId: params.params.authProfileId,
1958
- agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
1959
- model: response.model ?? startParams.model ?? params.params.modelId,
1960
- modelProvider: response.modelProvider ?? input.startModelProvider ?? input.modelProvider,
1961
- dynamicToolsFingerprint: context.dynamicToolsFingerprint,
1962
- dynamicToolsContainDeferred: context.dynamicToolsContainDeferred,
1963
- nativeSkillIsolationFingerprint: context.nativeSkillIsolationFingerprint,
1964
- userMcpServersFingerprint: context.userMcpServersFingerprint,
1965
- mcpServersFingerprint: input.nextMcpServersFingerprint,
1966
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
1967
- ringZeroConfigFingerprint: context.ringZeroConfigFingerprint,
1968
- ringZeroClientInstanceId: context.ringZeroClientInstanceId,
1969
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
1970
- networkProxyConfigFingerprint: context.networkProxyConfigFingerprint,
1971
- nativeHookRelayGeneration: input.finalConfigPatch.nativeHookRelayGeneration,
1972
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
1973
- pluginAppsFingerprint: input.pluginThreadConfig?.fingerprint,
1974
- pluginAppsInputFingerprint: input.pluginThreadConfig?.inputFingerprint,
1975
- pluginAppPolicyContext: input.pluginThreadConfig?.policyContext,
1976
- contextEngine: context.contextEngineBinding,
1977
- environmentSelectionFingerprint: context.environmentSelectionFingerprint,
1978
- ...!context.preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
1979
- ...startParams,
1980
- model: response.model ?? startParams.model ?? null,
1981
- requestedModel: startParams.model ?? null,
1982
- modelProvider: input.bindingModelProvider ?? null,
1983
- requestedModelProvider: startParams.modelProvider ?? input.bindingModelProvider ?? null
1984
- }, params.params.authProfileId, context.dynamicToolsFingerprint) } : {},
1985
- lifecycle: {
1986
- action: "started",
1987
- ...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();
1988
2066
  }
1989
- };
1990
- }
2067
+ },
2068
+ clear() {
2069
+ for (const entry of pending) clearTimeout(entry.timeout);
2070
+ pending.clear();
2071
+ }
2072
+ };
1991
2073
  //#endregion
1992
- //#region extensions/codex/src/app-server/reasoning-effort.ts
1993
- const CODEX_REASONING_EFFORTS = [
1994
- "minimal",
1995
- "low",
1996
- "medium",
1997
- "high",
1998
- "xhigh",
1999
- "max",
2000
- "ultra"
2001
- ];
2002
- const LEGACY_PRO_REASONING_EFFORTS = [
2003
- "medium",
2004
- "high",
2005
- "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"
2006
2085
  ];
2007
- const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
2008
- const MODERN_GPT_5_MODEL_ID_RE = /^gpt-5\.(?:[3-9]|[1-9]\d)(?:$|-)/u;
2009
- /** Read reasoning metadata after the Codex app-server route has been selected. */
2010
- function readCodexSupportedReasoningEfforts(compat) {
2011
- if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
2012
- const efforts = compat.supportedReasoningEfforts;
2013
- if (!Array.isArray(efforts)) return;
2014
- 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;
2015
2129
  }
2016
- function resolveSupportedReasoningEffort(params) {
2017
- const declared = new Set(params.supportedReasoningEfforts.map((effort) => effort.trim().toLowerCase()));
2018
- const supported = CODEX_REASONING_EFFORTS.filter((effort) => declared.has(effort));
2019
- if (supported.includes(params.requested)) return params.requested;
2020
- const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
2021
- const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
2022
- 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();
2023
2147
  }
2024
- /** Resolve a turn effort from the selected model's provider-owned metadata. */
2025
- function resolveCodexAppServerReasoningEffort(params) {
2026
- if (params.thinkLevel === "off") return params.supportedReasoningEfforts?.includes("none") ? "none" : null;
2027
- if (params.thinkLevel === "adaptive") return null;
2028
- if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
2029
- requested: params.thinkLevel,
2030
- supportedReasoningEfforts: params.supportedReasoningEfforts
2031
- }) ?? null;
2032
- const modelId = params.modelId.trim().toLowerCase();
2033
- if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
2034
- requested: params.thinkLevel,
2035
- supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
2036
- }) ?? null;
2037
- if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
2038
- if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
2039
- 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);
2040
2152
  }
2041
- //#endregion
2042
- //#region extensions/codex/src/app-server/thread-model-selection.ts
2043
- const CODEX_NATIVE_PERSONALITY_NONE = "none";
2044
- function resolveCodexBindingModelProviderFallback(params) {
2045
- const provider = params.provider?.trim().toLowerCase();
2046
- if (provider && provider !== "codex") return;
2047
- const currentModel = params.currentModel?.trim();
2048
- const bindingModel = params.bindingModel?.trim();
2049
- if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
2050
- 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
+ });
2051
2168
  }
2052
- function resolveCodexAppServerThreadModelSelection(params) {
2053
- const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
2054
- const explicitModelProvider = resolveCodexAppServerModelProvider({
2055
- provider: params.provider,
2056
- authProfileId,
2057
- authProfileStore: params.authProfileStore,
2058
- agentDir: params.agentDir,
2059
- config: params.config
2060
- });
2061
- const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
2062
- provider: params.provider,
2063
- currentModel: params.model,
2064
- bindingModel: params.binding.model,
2065
- bindingModelProvider: params.binding.modelProvider
2066
- }) : void 0;
2067
- return resolveCodexAppServerRequestModelSelection({
2068
- model: params.model,
2069
- modelProvider: explicitModelProvider ?? bindingModelProvider,
2070
- authProfileId,
2071
- authProfileStore: params.authProfileStore,
2072
- agentDir: params.agentDir,
2073
- config: params.config
2074
- });
2075
- }
2076
- function resolveCodexAppServerRequestModelSelection(params) {
2077
- const model = params.model.trim();
2078
- const modelProvider = params.modelProvider?.trim();
2079
- if (modelProvider) return {
2080
- model,
2081
- 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();
2082
2180
  };
2083
- const slashIndex = model.indexOf("/");
2084
- if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
2085
- const inferredModelProvider = resolveCodexAppServerModelProvider({
2086
- provider: model.slice(0, slashIndex),
2087
- authProfileId: params.authProfileId,
2088
- authProfileStore: params.authProfileStore,
2089
- agentDir: params.agentDir,
2090
- 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
+ }
2091
2240
  });
2241
+ const unregister = () => {
2242
+ foregroundClosed = true;
2243
+ rejectPendingAdmissions("native hook relay foreground closed");
2244
+ relay.unregister();
2245
+ };
2092
2246
  return {
2093
- model: model.slice(slashIndex + 1).trim(),
2094
- ...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
+ }
2095
2278
  };
2096
2279
  }
2097
- function hasProviderQualifiedModelRef(model) {
2098
- const trimmed = model?.trim();
2099
- const slashIndex = trimmed?.indexOf("/") ?? -1;
2100
- 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;
2101
2283
  }
2102
- function resolveCodexAppServerModelProvider(params) {
2103
- const normalized = params.provider.trim();
2104
- const normalizedLower = normalized.toLowerCase();
2105
- if (!normalized || normalizedLower === "codex") return;
2106
- if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
2107
- 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;
2108
2288
  }
2109
- function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
2110
- return resolveCodexAppServerReasoningEffort({
2111
- thinkLevel,
2112
- modelId,
2113
- supportedReasoningEfforts
2114
- });
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));
2115
2294
  }
2116
- //#endregion
2117
- //#region extensions/codex/src/app-server/project-doc-thread-config.ts
2118
- const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES = 131072;
2119
- function buildCodexProjectDocThreadConfig(config) {
2120
- const defaults = { project_doc_max_bytes: CODEX_NATIVE_PROJECT_DOC_MAX_BYTES };
2121
- 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)}`;
2122
2304
  }
2123
- //#endregion
2124
- //#region extensions/codex/src/app-server/thread-prompt.ts
2125
- function buildDeveloperInstructions(params, options = {}) {
2126
- const deferredToolNames = /* @__PURE__ */ new Set();
2127
- let secretsToolName;
2128
- let hasSkillWorkshop = false;
2129
- let hasSessionsSpawn = false;
2130
- let hasSessionsYield = false;
2131
- let hasSubagentsList = false;
2132
- let hasSessionsSend = false;
2133
- let hasSeenDirectNamespace = false;
2134
- let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
2135
- for (const spec of options.dynamicTools ?? []) {
2136
- const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
2137
- if (isDirectNamespace) hasSeenDirectNamespace = true;
2138
- for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
2139
- const name = tool.name.trim();
2140
- if (tool.deferLoading === true && name) deferredToolNames.add(name);
2141
- if (name === "secrets" && params.disableTools !== true) secretsToolName ??= spec.type === "namespace" ? `${spec.name}.${name}` : name;
2142
- hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
2143
- hasSessionsSpawn ||= name === "sessions_spawn";
2144
- hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
2145
- hasSubagentsList ||= name === "subagents";
2146
- hasSessionsSend ||= name === "sessions_send";
2147
- 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;
2148
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;
2149
2357
  }
2150
- const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
2151
- surface: "codex_app_server",
2152
- includeLegacyGlobalGuidance: false
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
2153
2549
  }).join("\n");
2154
2550
  const delegationGuidanceAvailable = params.disableTools !== true && params.delegationCapability !== "report_only" && !isMessageOnlyCodexSourceReply(params);
2155
2551
  const nativeDelegationAvailable = delegationGuidanceAvailable && !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) && !shouldDisableCodexToolSearchForModel(params.modelId);
@@ -2176,7 +2572,12 @@ function buildDeveloperInstructions(params, options = {}) {
2176
2572
  }).join("\n") : void 0,
2177
2573
  buildHarnessVisibleReplyGuidance({
2178
2574
  sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
2179
- messageToolAvailable
2575
+ messageToolAvailable,
2576
+ uiPresentation: params.disableTools !== true && params.promptMode !== "minimal" && params.promptMode !== "none" ? {
2577
+ showWidgetToolName,
2578
+ dashboardToolName,
2579
+ portalToolName
2580
+ } : void 0
2180
2581
  }),
2181
2582
  buildCredentialSafetyPrompt(secretsToolName),
2182
2583
  nativeCommandGuidance,
@@ -2372,8 +2773,31 @@ const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
2372
2773
  "project",
2373
2774
  "sessionFlags"
2374
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
+ }
2375
2800
  function buildThreadStartParams(params, options) {
2376
- const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
2377
2801
  const resolvedModelProvider = resolveCodexAppServerModelProvider({
2378
2802
  provider: params.provider,
2379
2803
  authProfileId: params.authProfileId,
@@ -2392,29 +2816,11 @@ function buildThreadStartParams(params, options) {
2392
2816
  return {
2393
2817
  model: modelSelection.model,
2394
2818
  ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
2395
- cwd: options.cwd,
2396
- ...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
2397
- approvalPolicy: options.appServer.approvalPolicy,
2398
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2399
- ...codexThreadSandboxOrPermissions(options.appServer),
2400
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2819
+ ...buildCodexThreadConfiguration(params, options),
2820
+ ...(options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) ? { baseInstructions: "" } : {},
2401
2821
  personality: CODEX_NATIVE_PERSONALITY_NONE,
2402
2822
  serviceName: "OpenClaw",
2403
- ...ringZeroActive ? { baseInstructions: "" } : {},
2404
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2405
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2406
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2407
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2408
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
2409
- webSearchAllowed: options.webSearchAllowed,
2410
- appServer: options.appServer,
2411
- hostSystemAgentActive: options.hostSystemAgentActive,
2412
- restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
2413
- shellEnvironment: options.shellEnvironment,
2414
- disableLoginShell: options.disableLoginShell
2415
- }),
2416
2823
  ...resolveCodexThreadEnvironmentSelection(options),
2417
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
2418
2824
  dynamicTools: [...options.dynamicTools],
2419
2825
  experimentalRawEvents: true,
2420
2826
  ...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
@@ -2437,8 +2843,6 @@ function buildThreadResumeParams(params, options) {
2437
2843
  });
2438
2844
  return {
2439
2845
  threadId: options.threadId,
2440
- ...options.cwd ? { cwd: options.cwd } : {},
2441
- ...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
2442
2846
  excludeTurns: true,
2443
2847
  initialTurnsPage: {
2444
2848
  limit: 1,
@@ -2449,24 +2853,8 @@ function buildThreadResumeParams(params, options) {
2449
2853
  model: modelSelection.model,
2450
2854
  ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
2451
2855
  } : {},
2452
- approvalPolicy: options.appServer.approvalPolicy,
2453
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2454
- ...codexThreadSandboxOrPermissions(options.appServer),
2455
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2456
- personality: CODEX_NATIVE_PERSONALITY_NONE,
2457
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2458
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2459
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2460
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2461
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
2462
- webSearchAllowed: options.webSearchAllowed,
2463
- appServer: options.appServer,
2464
- hostSystemAgentActive: options.hostSystemAgentActive,
2465
- restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
2466
- shellEnvironment: options.shellEnvironment,
2467
- disableLoginShell: options.disableLoginShell
2468
- }),
2469
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
2856
+ ...buildCodexThreadConfiguration(params, options),
2857
+ personality: CODEX_NATIVE_PERSONALITY_NONE
2470
2858
  };
2471
2859
  }
2472
2860
  function buildCodexRuntimeThreadConfig(config, options = {}) {
@@ -2516,7 +2904,7 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2516
2904
  }).threadConfig;
2517
2905
  const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
2518
2906
  return applyCodexManagedShellEnvironment({
2519
- ...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,
2520
2908
  ...params.bootstrapContextMode === "lightweight" ? CODEX_NO_PROJECT_DOCS_CONFIG : {}
2521
2909
  }, options.shellEnvironment, options.disableLoginShell);
2522
2910
  }
@@ -2527,10 +2915,14 @@ function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isH
2527
2915
  ...CODEX_NO_PROJECT_DOCS_CONFIG
2528
2916
  };
2529
2917
  }
2530
- function buildRestrictedToolConfigPatch(inheritedMcpServerNames) {
2918
+ function buildRestrictedToolConfigPatch(inheritedMcpServerNames, scheduledAppAuthorityActive = false) {
2531
2919
  const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
2532
2920
  return {
2533
2921
  ...CODEX_RING_ZERO_THREAD_CONFIG,
2922
+ ...scheduledAppAuthorityActive ? {
2923
+ "features.apps": true,
2924
+ "orchestrator.mcp.enabled": true
2925
+ } : {},
2534
2926
  ...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
2535
2927
  };
2536
2928
  }
@@ -2552,58 +2944,53 @@ async function readCodexInheritedMcpServerNames(client, cwd, signal) {
2552
2944
  return Object.keys(configuredServers).toSorted();
2553
2945
  }
2554
2946
  async function assertCodexManagedRequirementsDoNotOverrideToolPolicy(client, options, signal) {
2555
- const response = await client.request("configRequirements/read", void 0, { signal });
2556
- if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
2557
- if (response.requirements === null) return;
2558
- 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;
2559
2953
  if (options.restrictedToolSurface) for (const key of [
2560
2954
  "hooks",
2561
2955
  "managedHooks",
2562
2956
  "managed_hooks"
2563
2957
  ]) {
2564
- const hooks = response.requirements[key];
2958
+ const hooks = requirements[key];
2565
2959
  if (hooks === void 0 || hooks === null) continue;
2566
2960
  if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
2567
- 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");
2568
2962
  }
2569
2963
  const additionalDeniedFeatures = new Set(options.additionalDeniedFeatures);
2570
2964
  for (const key of ["featureRequirements", "feature_requirements"]) {
2571
- const requirements = response.requirements[key];
2572
- if (requirements === void 0 || requirements === null) continue;
2573
- if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
2574
- 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)) {
2575
2969
  if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
2576
2970
  const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
2577
2971
  const deniedByToolPolicy = options.restrictedToolSurface && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature) || additionalDeniedFeatures.has(canonicalFeature);
2972
+ if (canonicalFeature === "hooks" && managedHooksAllowed) continue;
2578
2973
  if (enabled && deniedByToolPolicy) throw new Error(`Codex tool policy cannot override required feature ${feature}`);
2579
2974
  }
2580
2975
  }
2581
2976
  }
2582
- async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal) {
2583
- const configuredServers = threadConfig?.mcp_servers;
2584
- if (configuredServers !== void 0 && !isJsonObject(configuredServers)) throw new Error("Codex restricted-tool-surface thread config has invalid mcp_servers");
2585
- const expectedDisabledServerNames = /* @__PURE__ */ new Set();
2586
- for (const [name, serverConfig] of Object.entries(configuredServers ?? {})) {
2587
- if (!isJsonObject(serverConfig) || serverConfig.enabled !== false) throw new Error(`Codex restricted-tool-surface MCP server ${name} is not disabled`);
2588
- expectedDisabledServerNames.add(name);
2589
- }
2590
- const response = await client.request("mcpServerStatus/list", {
2591
- threadId,
2592
- detail: "toolsAndAuthOnly"
2593
- }, { signal });
2594
- if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
2595
- const observedDisabledServerNames = /* @__PURE__ */ new Set();
2596
- for (const status of response.data) {
2597
- if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
2598
- if (!expectedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
2599
- if (observedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
2600
- observedDisabledServerNames.add(status.name);
2601
- if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
2602
- if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
2603
- if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
2604
- }
2605
- for (const expectedName of expectedDisabledServerNames) if (!observedDisabledServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing server ${expectedName}`);
2606
- 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");
2984
+ }
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;
2607
2994
  }
2608
2995
  function hasNonEmptyJsonValue(value) {
2609
2996
  if (value === null || value === false || value === "") return false;
@@ -2624,1179 +3011,1298 @@ function resolveCodexThreadEnvironmentSelection(options) {
2624
3011
  return {};
2625
3012
  }
2626
3013
  //#endregion
2627
- //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
2628
- function resolveCodexThreadAgentDir(params) {
2629
- const agentId = resolveSessionAgentIdsStrict({
2630
- config: params.params.config,
2631
- sessionKey: params.params.sessionKey,
2632
- agentId: params.agentId ?? params.params.agentId
2633
- }).sessionAgentId;
2634
- return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
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
+ };
2635
3047
  }
2636
- function resolveCodexThreadRolloutPath(thread) {
2637
- const rolloutPath = thread.path?.trim();
2638
- if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
2639
- 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
+ };
2640
3103
  }
2641
- async function resumeExistingCodexThread(params, context) {
2642
- 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;
2643
- let resumeReservation;
2644
- let resumeResponseAccepted = false;
2645
- let checkingLoadedPluginThreadConfig = false;
2646
- const abandonClient = params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client));
2647
- try {
2648
- const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
2649
- const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
2650
- action: "resume",
2651
- binding: resumeBinding
2652
- }) ?? {
2653
- configPatch: params.finalConfigPatch,
2654
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
2655
- };
2656
- const pluginThreadConfig = context.prebuiltPluginThreadConfig ?? (params.pluginThreadConfig?.requiresCurrentPolicyCheck ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0);
2657
- const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
2658
- const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
2659
- const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
2660
- threadId: resumeBinding.threadId,
2661
- cwd: params.cwd,
2662
- authProfileId,
2663
- model: startModelSelection.model,
2664
- modelProvider: startModelProvider,
2665
- preserveNativeModel: resumeBinding.preserveNativeModel === true,
2666
- appServer: params.appServer,
2667
- dynamicTools: params.dynamicTools,
2668
- developerInstructions: params.developerInstructions,
2669
- config: resumeConfig,
2670
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2671
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2672
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2673
- webSearchAllowed: params.webSearchAllowed,
2674
- hostSystemAgentActive,
2675
- restrictedToolSurfaceInheritedMcpServerNames,
2676
- shellEnvironment: params.shellEnvironment,
2677
- disableLoginShell: params.disableLoginShell
2678
- }));
2679
- const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
2680
- throwIfAborted();
2681
- resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
2682
- const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
2683
- client: params.client,
2684
- abandonClient,
2685
- request: resumeParams,
2686
- signal: params.signal,
2687
- assertCurrent: context.assertResumeOwnership,
2688
- isPrewriteOwnershipError: (error) => error instanceof CodexAdoptedThreadActiveError
2689
- }));
2690
- resumeResponseAccepted = true;
2691
- assertCodexThreadAcceptsDirectInput(response.thread);
2692
- context.assertResumeConfiguration?.();
2693
- checkingLoadedPluginThreadConfig = true;
2694
- const loadedPluginThreadConfig = await context.buildLoadedPluginThreadConfig?.(resumeBinding);
2695
- if (loadedPluginThreadConfig && loadedPluginThreadConfig.fingerprint !== (pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint)) {
2696
- checkingLoadedPluginThreadConfig = false;
2697
- throw new Error("Codex thread app policy changed; a fresh thread configuration is required");
2698
- }
2699
- await attestCodexPluginThreadApps({
2700
- client: params.client,
2701
- threadId: response.thread.id,
2702
- appIds: loadedPluginThreadConfig?.provisionalAppIds ?? pluginThreadConfig?.provisionalAppIds ?? [],
2703
- signal: params.signal
2704
- });
2705
- checkingLoadedPluginThreadConfig = false;
2706
- if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
2707
- await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
2708
- } catch (error) {
2709
- context.assertResumeOwnership?.();
2710
- await abandonClient();
2711
- throw new CodexRestrictedToolSurfaceAttestationError(error);
2712
- }
2713
- throwIfAborted();
2714
- const boundAuthProfileId = authProfileId;
2715
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
2716
- const resumePatch = {
2717
- clientId: resolveCodexAppServerClientInstanceId(params.client),
2718
- pendingResumeConfiguration: void 0,
2719
- cwd: params.cwd,
2720
- rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
2721
- authProfileId: boundAuthProfileId,
2722
- model: response.model ?? resumeParams.model ?? params.params.modelId,
2723
- preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
2724
- modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
2725
- dynamicToolsFingerprint,
2726
- dynamicToolsContainDeferred,
2727
- webSearchThreadConfigFingerprint,
2728
- nativeSkillIsolationFingerprint,
2729
- userMcpServersFingerprint,
2730
- mcpServersFingerprint: nextMcpServersFingerprint,
2731
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
2732
- ringZeroConfigFingerprint,
2733
- ringZeroClientInstanceId,
2734
- nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
2735
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2736
- networkProxyConfigFingerprint,
2737
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
2738
- appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
2739
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
2740
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
2741
- pluginAppPolicyContext: pluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
2742
- contextEngine: contextEngineBinding,
2743
- environmentSelectionFingerprint
2744
- };
2745
- if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2746
- kind: "patch",
2747
- threadId: resumeBinding.threadId,
2748
- patch: resumePatch
2749
- }, context.assertResumeConfiguration))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
2750
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2751
- sessionId: params.params.sessionId,
2752
- sessionKey: params.params.sessionKey,
2753
- threadId: response.thread.id,
2754
- engineId: contextEngineBinding.engineId,
2755
- epoch: contextEngineBinding.projection?.epoch,
2756
- fingerprint: contextEngineBinding.projection?.fingerprint,
2757
- action: "resumed"
2758
- });
2759
- lifecycleTiming.mark("thread-ready");
2760
- lifecycleTiming.logSummary({
2761
- runId: params.params.runId,
2762
- sessionId: params.params.sessionId,
2763
- sessionKey: params.params.sessionKey,
2764
- threadId: response.thread.id,
2765
- 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 } : {}
2766
3114
  });
2767
- const activeTurnIds = readActiveCodexTurnIdsFromResume(response);
2768
- return {
2769
- ...resumeBinding,
2770
- threadId: response.thread.id,
2771
- ...resumePatch,
2772
- liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
2773
- ...resumeParams,
2774
- model: resumeBinding.preserveNativeModel === true ? null : response.model ?? resumeParams.model ?? null,
2775
- requestedModel: resumeBinding.preserveNativeModel === true ? null : resumeParams.model ?? null,
2776
- modelProvider: resumeBinding.preserveNativeModel === true ? null : resumePatch.modelProvider ?? null,
2777
- requestedModelProvider: resumeBinding.preserveNativeModel === true ? null : resumeParams.modelProvider ?? resumePatch.modelProvider ?? null
2778
- }, authProfileId, dynamicToolsFingerprint),
2779
- lifecycle: {
2780
- action: "resumed",
2781
- ...activeTurnIds.length ? { activeTurnIds } : {}
2782
- }
2783
- };
2784
- } catch (error) {
2785
- resumeReservation?.release();
2786
- if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
2787
- if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
2788
- if (!resumeBinding.pendingResumeConfiguration) await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
2789
- throw error;
2790
- }
2791
- if (resumeResponseAccepted) {
2792
- if (!await unsubscribeCodexThreadBestEffort(params.client, {
2793
- threadId: resumeBinding.threadId,
2794
- timeoutMs: 5e3,
2795
- assertCurrent: context.assertResumeOwnership
2796
- }).catch(() => false)) {
2797
- try {
2798
- await abandonClient();
2799
- } catch (abandonError) {
2800
- throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume client could not be retired", { cause: abandonError });
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);
2801
3131
  }
2802
- throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
2803
3132
  }
2804
3133
  }
2805
- if (checkingLoadedPluginThreadConfig || resumeBinding.pendingResumeConfiguration || error instanceof CodexThreadDirectInputError || params.signal?.aborted) throw error;
2806
- embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
2807
- 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);
2808
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
+ };
2809
3153
  }
2810
- async function startFreshCodexThread(params, context) {
2811
- const clientId = resolveCodexAppServerClientInstanceId(params.client);
2812
- 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;
2813
- const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2814
- const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2815
- configPatch: params.finalConfigPatch,
2816
- 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
2817
3167
  };
2818
- const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
2819
- const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
2820
- cwd: params.cwd,
2821
- dynamicTools: params.dynamicTools,
2822
- appServer: params.appServer,
2823
- developerInstructions: params.developerInstructions,
2824
- config,
2825
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2826
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2827
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2828
- webSearchAllowed: params.webSearchAllowed,
2829
- environmentSelection: params.environmentSelection,
2830
- model: startModelSelection.model,
2831
- modelProvider: startModelProvider,
2832
- hostSystemAgentActive,
2833
- restrictedToolSurfaceInheritedMcpServerNames,
2834
- shellEnvironment: params.shellEnvironment,
2835
- disableLoginShell: params.disableLoginShell
2836
- }));
2837
- const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
2838
- const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
2839
- try {
2840
- return await params.client.request("thread/start", startParams, { signal: params.signal });
2841
- } catch (error) {
2842
- if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
2843
- throw error;
2844
- }
2845
- });
2846
- const response = assertCodexThreadStartResponse(threadStartResponse);
2847
- const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
2848
- if (provisionalAppIds?.length) try {
2849
- await lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
2850
- client: params.client,
2851
- threadId: response.thread.id,
2852
- 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,
2853
3182
  signal: params.signal
2854
- }));
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
+ });
2855
3215
  } catch (error) {
2856
- if (!await discardUnattestedCodexPluginThread({
2857
- client: params.client,
2858
- threadId: response.thread.id,
2859
- ephemeral: startParams.ephemeral === true
2860
- })) {
2861
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2862
- 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
2863
3236
  }
2864
- 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";
2865
3243
  }
2866
- const rolloutPath = resolveCodexThreadRolloutPath(response.thread);
2867
- if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
2868
- await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, startParams.config, params.signal));
2869
- } catch (error) {
2870
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2871
- 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.`);
2872
3285
  }
2873
- try {
2874
- throwIfAborted();
2875
- } catch (error) {
2876
- if (replacementPredecessor) {
2877
- if (!await discardUnattestedCodexPluginThread({
2878
- client: params.client,
2879
- threadId: response.thread.id,
2880
- ephemeral: startParams.ephemeral === true
2881
- })) {
2882
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2883
- throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after an aborted binding replacement", { cause: error });
2884
- }
2885
- }
2886
- 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
+ };
2887
3297
  }
2888
- const modelProvider = resolveCodexAppServerModelProvider({
2889
- provider: params.params.provider,
2890
- authProfileId: params.params.authProfileId,
2891
- authProfileStore: params.params.authProfileStore,
2892
- agentDir: params.params.agentDir,
2893
- config: params.params.config
2894
- });
2895
- const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
2896
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2897
- if (!preserveExistingBinding) {
2898
- const nextBinding = {
2899
- threadId: response.thread.id,
2900
- ...clientId ? { clientId } : {},
2901
- cwd: params.cwd,
2902
- ...rolloutPath ? { rolloutPath } : {},
2903
- authProfileId: params.params.authProfileId,
2904
- agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
2905
- model: response.model ?? startParams.model ?? params.params.modelId,
2906
- modelProvider: bindingModelProvider,
2907
- dynamicToolsFingerprint,
2908
- dynamicToolsContainDeferred,
2909
- webSearchThreadConfigFingerprint,
2910
- nativeSkillIsolationFingerprint,
2911
- userMcpServersFingerprint,
2912
- mcpServersFingerprint: nextMcpServersFingerprint,
2913
- configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
2914
- ringZeroConfigFingerprint,
2915
- ringZeroClientInstanceId,
2916
- nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
2917
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2918
- networkProxyConfigFingerprint,
2919
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2920
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2921
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2922
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2923
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2924
- contextEngine: contextEngineBinding,
2925
- environmentSelectionFingerprint
2926
- };
2927
- const cleanupUncommittedSuccessor = async (cause) => {
2928
- if (!await discardUnattestedCodexPluginThread({
2929
- client: params.client,
2930
- threadId: response.thread.id,
2931
- ephemeral: startParams.ephemeral === true
2932
- })) {
2933
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2934
- throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
2935
- }
2936
- };
2937
- const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
2938
- await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
2939
- sourceHomeId: managedSourceHomeId,
2940
- threadId: response.thread.id,
2941
- ...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
+ }];
2942
3316
  }));
2943
- let committed;
2944
- try {
2945
- committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
2946
- kind: "replace-thread",
2947
- expectedThreadId: replacementPredecessor.threadId,
2948
- binding: nextBinding
2949
- } : {
2950
- kind: "set",
2951
- if: { kind: "absent" },
2952
- binding: nextBinding
2953
- }));
2954
- } catch (error) {
2955
- if (replacementPredecessor) await cleanupUncommittedSuccessor(error);
2956
- throw error;
2957
- }
2958
- if (!committed) {
2959
- if (replacementPredecessor) await cleanupUncommittedSuccessor();
2960
- throw new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread");
2961
- }
2962
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2963
- sessionId: params.params.sessionId,
2964
- sessionKey: params.params.sessionKey,
2965
- threadId: response.thread.id,
2966
- engineId: contextEngineBinding.engineId,
2967
- epoch: contextEngineBinding.projection?.epoch,
2968
- fingerprint: contextEngineBinding.projection?.fingerprint,
2969
- action: rotatedContextEngineBinding ? "rotated" : "started"
2970
- });
2971
3317
  }
2972
- lifecycleTiming.mark("thread-ready");
2973
- lifecycleTiming.logSummary({
2974
- runId: params.params.runId,
2975
- sessionId: params.params.sessionId,
2976
- sessionKey: params.params.sessionKey,
2977
- threadId: response.thread.id,
2978
- action: rotatedContextEngineBinding ? "rotated" : "started"
2979
- });
2980
- return buildStartedCodexThreadBinding({
2981
- bindingModelProvider,
2982
- clientId,
2983
- context,
2984
- finalConfigPatch,
2985
- nextMcpServersFingerprint,
2986
- params,
2987
- pluginThreadConfig,
2988
- response,
2989
- rolloutPath,
2990
- startModelProvider: requestModelProvider ?? startModelProvider,
2991
- startParams,
2992
- modelProvider
2993
- });
2994
- }
2995
- //#endregion
2996
- //#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
2997
- /** Preserves the caller's abort reason across thread ownership transitions. */
2998
- function throwIfCodexThreadLifecycleAborted(signal) {
2999
- if (!signal?.aborted) return;
3000
- const reason = signal.reason;
3001
- if (reason instanceof Error) throw reason;
3002
- const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
3003
- error.name = "AbortError";
3004
- throw error;
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
3332
+ };
3005
3333
  }
3006
- /** Releases consumed subscription ownership or retires an unsafe client. */
3007
- async function releaseCodexConsumedLiveThread(options) {
3008
- if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
3009
- threadId: options.threadId,
3010
- timeoutMs: 5e3,
3011
- assertCurrent: options.assertCurrent
3012
- }))) return;
3013
- return await abandonCodexLiveThreadRelease(options, options.cause);
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;
3346
+ }
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");
3014
3364
  }
3015
- async function abandonCodexLiveThreadRelease(options, cause) {
3016
- options.assertCurrent?.();
3017
- await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
3018
- throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, cause !== void 0 ? { cause } : void 0);
3365
+ //#endregion
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);
3019
3373
  }
3020
- /** Releases through the retained owner, preserving its guarded callback and rollback. */
3021
- async function releaseCodexRetainedLiveThread(options) {
3022
- try {
3023
- return await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => releaseCodexAppServerLiveThread(options.client, options.threadId, options.assertCurrent));
3024
- } catch (error) {
3025
- if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
3026
- return await abandonCodexLiveThreadRelease(options, error);
3027
- }
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}`;
3028
3377
  }
3029
- /** Reuses one safely owned, fully matching subscription on its original client. */
3030
- async function tryReuseCodexLiveThread(options) {
3031
- const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, ringZeroActive, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
3032
- if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive) return { kind: "resume" };
3033
- const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
3034
- if (!retainedThread) return { kind: "resume" };
3035
- const assertCurrentClient = () => {
3036
- throwIfAborted();
3037
- if (!isCodexAppServerClientRuntimeLive(params.client)) throw params.client.getCloseError() ?? /* @__PURE__ */ new Error("codex app-server client is closed");
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() {}
3038
3389
  };
3039
- let ownershipTransferred = false;
3040
- try {
3041
- const pluginThreadConfig = await options.buildLoadedPluginThreadConfig(binding);
3042
- assertCurrentClient();
3043
- if (pluginThreadConfig && pluginThreadConfig.fingerprint !== binding.pluginAppsFingerprint) return { kind: "rotate" };
3044
- const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
3045
- action: "resume",
3046
- binding
3047
- }) ?? {
3048
- configPatch: params.finalConfigPatch,
3049
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
3050
- };
3051
- const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0);
3052
- const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
3053
- const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
3054
- const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
3055
- threadId: binding.threadId,
3056
- cwd: params.cwd,
3057
- authProfileId: resumeAuthProfileId,
3058
- model: startModelSelection.model,
3059
- modelProvider: startModelProvider,
3060
- preserveNativeModel: false,
3061
- appServer: params.appServer,
3062
- dynamicTools: params.dynamicTools,
3063
- developerInstructions: params.developerInstructions,
3064
- config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
3065
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
3066
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3067
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
3068
- webSearchAllowed: params.webSearchAllowed,
3069
- hostSystemAgentActive,
3070
- restrictedToolSurfaceInheritedMcpServerNames,
3071
- shellEnvironment: params.shellEnvironment,
3072
- disableLoginShell: params.disableLoginShell
3073
- }));
3074
- const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
3075
- ...resumeParams,
3076
- model: binding.model ?? resumeParams.model ?? null,
3077
- requestedModel: resumeParams.model ?? null,
3078
- modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
3079
- requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
3080
- }, resumeAuthProfileId, dynamicToolsFingerprint);
3081
- if (retainedThread.configFingerprint !== liveThreadConfigFingerprint) return {
3082
- kind: "resume",
3083
- prebuiltFinalConfigPatch
3084
- };
3085
- await attestCodexPluginThreadApps({
3086
- client: params.client,
3087
- threadId: binding.threadId,
3088
- appIds: pluginThreadConfig?.provisionalAppIds ?? [],
3089
- signal: params.signal
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)
3090
3401
  });
3091
- assertCurrentClient();
3092
- const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
3093
- const model = startModelSelection.model;
3094
- if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
3095
- kind: "patch",
3096
- threadId: binding.threadId,
3097
- patch: {
3098
- cwd: params.cwd,
3099
- model,
3100
- nativeHookRelayGeneration,
3101
- environmentSelectionFingerprint
3402
+ };
3403
+ const snapshot = () => ({
3404
+ totalMs: toMs(now() - startedAt),
3405
+ spans: spans.slice()
3406
+ });
3407
+ return {
3408
+ async measure(name, run) {
3409
+ const spanStartedAt = now();
3410
+ try {
3411
+ return await run();
3412
+ } finally {
3413
+ record(name, spanStartedAt);
3102
3414
  }
3103
- }, assertCurrentClient))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
3104
- assertCurrentClient();
3105
- lifecycleTiming.mark("thread-ready");
3106
- lifecycleTiming.logSummary({
3107
- runId: params.params.runId,
3108
- sessionId: params.params.sessionId,
3109
- sessionKey: params.params.sessionKey,
3110
- threadId: binding.threadId,
3111
- action: "resumed"
3112
- });
3113
- ownershipTransferred = true;
3114
- return {
3115
- kind: "ready",
3116
- binding: {
3117
- ...binding,
3118
- cwd: params.cwd,
3119
- model,
3120
- nativeHookRelayGeneration,
3121
- environmentSelectionFingerprint,
3122
- liveThreadConfigFingerprint,
3123
- liveThreadOwnership: retainedThread,
3124
- ...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
3125
- lifecycle: { action: "resumed" }
3415
+ },
3416
+ measureSync(name, run) {
3417
+ const spanStartedAt = now();
3418
+ try {
3419
+ return run();
3420
+ } finally {
3421
+ record(name, spanStartedAt);
3126
3422
  }
3127
- };
3128
- } finally {
3129
- if (!ownershipTransferred) try {
3130
- await retainedThread.release(binding.threadId);
3131
- } catch (error) {
3132
- await abandonCodexLiveThreadRelease({
3133
- client: params.client,
3134
- abandonClient: params.abandonClient,
3135
- lifecycleTiming,
3136
- threadId: binding.threadId
3137
- }, error);
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);
3138
3451
  }
3139
- }
3452
+ };
3140
3453
  }
3141
3454
  //#endregion
3142
- //#region extensions/codex/src/app-server/thread-lifecycle-adoption.ts
3143
- /** Passive refusal must precede releasing or acquiring any native subscription. */
3144
- async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context) {
3145
- const { thread } = await context.lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
3146
- threadId,
3147
- includeTurns: false
3148
- }, { signal: params.signal }));
3149
- context.throwIfAborted();
3150
- assertCodexThreadAcceptsDirectInput(thread);
3151
- if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
3152
- }
3153
- /** Preserve attach's native-queue-before-binding-lease order when consuming pending intent. */
3154
- async function withCodexThreadLifecycleBinding(params, run) {
3155
- const identity = sessionBindingIdentity({
3156
- sessionId: params.params.sessionId,
3455
+ //#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
3456
+ function resolveCodexThreadAgentDir(params) {
3457
+ const agentId = resolveSessionAgentIdsStrict({
3458
+ config: params.params.config,
3157
3459
  sessionKey: params.params.sessionKey,
3158
- agentId: params.agentId ?? params.params.agentId,
3159
- config: params.params.config
3160
- });
3161
- const snapshot = await params.bindingStore.read(identity);
3162
- const pendingThreadId = snapshot?.pendingResumeConfiguration ? snapshot.threadId : void 0;
3163
- const runWithLease = () => params.bindingStore.withLease(identity, async () => {
3164
- const binding = await params.bindingStore.read(identity);
3165
- if (pendingThreadId && (binding?.threadId !== pendingThreadId || !binding.pendingResumeConfiguration)) throw new CodexThreadBindingConflictError(pendingThreadId, "acquiring a pending resume configuration");
3166
- if (!pendingThreadId && binding?.pendingResumeConfiguration) throw new CodexThreadBindingConflictError(binding.threadId, "acquiring a pending resume configuration");
3167
- return await run(identity, binding);
3168
- });
3169
- return pendingThreadId ? await withExclusiveCodexAppServerThread({
3170
- bindingStore: params.bindingStore,
3171
- identity,
3172
- threadId: pendingThreadId,
3173
- run: runWithLease
3174
- }) : await runWithLease();
3460
+ agentId: params.agentId ?? params.params.agentId
3461
+ }).sessionAgentId;
3462
+ return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
3175
3463
  }
3176
- /** Completes manual attachment only under the native queue and exact binding lease. */
3177
- async function resumePendingCodexThread(params, context) {
3178
- const { binding, contextEngineBinding, lifecycleTiming, restrictedToolSurface } = context;
3179
- if (isIncognitoSessionKey(params.params.sessionKey) || context.transientRestriction || !restrictedToolSurface && binding.nativeToolPolicyRestricted === true || (contextEngineBinding ? !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding) : binding.contextEngine !== void 0) || shouldRotateCodexGpt56MultiAgentBinding({
3180
- bindingModel: binding.model,
3181
- requestedModel: params.params.modelId
3182
- })) 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.`);
3183
- const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
3184
- const clientId = resolveCodexAppServerClientInstanceId(params.client);
3185
- const configuration = await preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint, async (assertCurrent) => {
3186
- const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
3187
- assertCurrent();
3188
- if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
3189
- client: params.client,
3190
- abandonClient: params.abandonClient,
3191
- lifecycleTiming,
3192
- threadId: binding.threadId,
3193
- assertCurrent
3194
- });
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
3195
3470
  });
3196
- try {
3197
- const resumed = await resumeExistingCodexThread(params, {
3198
- ...context,
3199
- prebuiltPluginThreadConfig,
3200
- assertResumeConfiguration: configuration.assertConfigured,
3201
- assertResumeOwnership: configuration.assertCurrent
3202
- });
3203
- if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
3204
- return resumed;
3205
- } finally {
3206
- configuration.dispose();
3207
- }
3208
- }
3209
- /** Manual attachment is intent, never evidence that loaded native overrides took effect. */
3210
- async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
3211
- 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.`);
3212
- const agentDir = resolveCodexThreadAgentDir(params);
3213
- const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
3214
- 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");
3215
- if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw fail("the thread is claimed by active work; stop that run before resuming");
3216
- const assertCurrent = captureExclusiveSharedCodexAppServerClient(params.client);
3217
- const { thread } = await params.client.request("thread/read", {
3218
- threadId: binding.threadId,
3219
- includeTurns: false
3220
- }, { signal: params.signal });
3221
- assertCurrent();
3222
- const statusType = thread.status?.type;
3223
- if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
3224
- assertCodexThreadAcceptsDirectInput(thread);
3225
- let unloaded = statusType === "notLoaded";
3226
- const dispose = params.client.addNotificationHandler((notification) => {
3227
- if (notification.method === "thread/status/changed" && isJsonObject(notification.params) && notification.params.threadId === binding.threadId && isJsonObject(notification.params.status) && notification.params.status.type === "notLoaded") unloaded = true;
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)
3228
3475
  });
3229
- try {
3230
- const rolloutPath = thread.path ?? binding.rolloutPath;
3231
- const metadata = rolloutPath ? await readCodexSessionMeta(path.join(localHome, "sessions"), rolloutPath, binding.threadId) : void 0;
3232
- if (!metadata) throw fail("its native tool catalog could not be read from the selected Codex home");
3233
- const recordedTools = metadata.dynamic_tools ?? [];
3234
- if (!Array.isArray(recordedTools) || codexDynamicToolsFingerprint(recordedTools) !== dynamicToolsFingerprint) throw fail("its immutable native tool catalog does not match the current OpenClaw tools");
3235
- assertCurrent();
3236
- await releaseSubscription(assertCurrent);
3237
- assertCurrent();
3238
- return {
3239
- assertConfigured: () => {
3240
- assertCurrent();
3241
- if (!unloaded) throw fail("Codex did not confirm unloading its previous configuration");
3242
- },
3243
- assertCurrent,
3244
- dispose
3245
- };
3246
- } catch (error) {
3247
- dispose();
3248
- throw error;
3249
- }
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
+ })
3498
+ });
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;
3533
+ return {
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
3553
+ };
3250
3554
  }
3251
3555
  //#endregion
3252
- //#region extensions/codex/src/app-server/native-hook-relay-state.ts
3253
- const pending = /* @__PURE__ */ new Set();
3254
- /** Owns delayed hook-relay cleanup across runtime scheduling and test teardown. */
3255
- const nativeHookRelayUnregisterQueue = {
3256
- add(entry) {
3257
- pending.add(entry);
3258
- },
3259
- delete(entry) {
3260
- return pending.delete(entry);
3261
- },
3262
- flush() {
3263
- while (pending.size > 0) {
3264
- const entry = pending.values().next().value;
3265
- if (!entry) return;
3266
- clearTimeout(entry.timeout);
3267
- entry.unregister();
3268
- }
3269
- },
3270
- clear() {
3271
- for (const entry of pending) clearTimeout(entry.timeout);
3272
- pending.clear();
3273
- }
3274
- };
3275
- //#endregion
3276
- //#region extensions/codex/src/app-server/native-hook-relay.ts
3277
- /**
3278
- * Bridges Codex native hook callbacks into OpenClaw's native hook relay so
3279
- * app-server tool events can still run OpenClaw policy and diagnostics.
3280
- */
3281
- /** Codex hook events that can be registered through OpenClaw's native relay. */
3282
- const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
3283
- "pre_tool_use",
3284
- "post_tool_use",
3285
- "permission_request",
3286
- "before_agent_finalize"
3287
- ];
3288
- const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
3289
- const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 18e5;
3290
- /** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
3291
- const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 3e5;
3292
- const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
3293
- const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
3294
- const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
3295
- const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
3296
- const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
3297
- const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
3298
- const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
3299
- const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
3300
- exec: [
3301
- "Bash",
3302
- "exec",
3303
- "exec_command"
3304
- ],
3305
- apply_patch: [
3306
- "apply_patch",
3307
- "Write",
3308
- "Edit"
3309
- ],
3310
- spawn_agent: ["spawn_agent", "Agent"]
3311
- };
3312
- /** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
3313
- async function assertCodexNativeHookRelayAllowed(client, signal) {
3314
- let attestation = nativeHookPolicyByClient.get(client);
3315
- if (!attestation) {
3316
- attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
3317
- if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
3318
- const requirements = response.requirements;
3319
- if (requirements === null) return;
3320
- if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
3321
- const managedOnly = requirements.allowManagedHooksOnly;
3322
- if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
3323
- if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
3324
- });
3325
- nativeHookPolicyByClient.set(client, attestation);
3326
- attestation.catch(() => {
3327
- if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
3328
- });
3329
- }
3330
- await attestation;
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;
3331
3561
  }
3332
- /** Defers relay unregister so late native hook subprocesses can still resolve. */
3333
- function scheduleCodexNativeHookRelayUnregister(params) {
3334
- let pending;
3335
- const unregister = () => {
3336
- if (!pending) return;
3337
- const current = pending;
3338
- pending = void 0;
3339
- if (!nativeHookRelayUnregisterQueue.delete(current)) return;
3340
- params.relay.unregister();
3341
- };
3342
- const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
3343
- pending = {
3344
- timeout,
3345
- unregister
3562
+ /** Materializes the public lifecycle result after a fresh thread is durably committed. */
3563
+ function buildStartedCodexThreadBinding(input) {
3564
+ const { context, params, response, startParams } = input;
3565
+ return {
3566
+ threadId: response.thread.id,
3567
+ ...input.clientId ? { clientId: input.clientId } : {},
3568
+ cwd: params.cwd,
3569
+ ...input.rolloutPath ? { rolloutPath: input.rolloutPath } : {},
3570
+ authProfileId: params.params.authProfileId,
3571
+ agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
3572
+ model: response.model ?? startParams.model ?? params.params.modelId,
3573
+ modelProvider: response.modelProvider ?? input.startModelProvider ?? input.modelProvider,
3574
+ dynamicToolsFingerprint: context.dynamicToolsFingerprint,
3575
+ dynamicToolsContainDeferred: context.dynamicToolsContainDeferred,
3576
+ nativeSkillIsolationFingerprint: context.nativeSkillIsolationFingerprint,
3577
+ userMcpServersFingerprint: context.userMcpServersFingerprint,
3578
+ mcpServersFingerprint: input.nextMcpServersFingerprint,
3579
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
3580
+ ringZeroConfigFingerprint: context.ringZeroConfigFingerprint,
3581
+ ringZeroClientInstanceId: context.ringZeroClientInstanceId,
3582
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
3583
+ networkProxyConfigFingerprint: context.networkProxyConfigFingerprint,
3584
+ nativeHookRelayGeneration: input.finalConfigPatch.nativeHookRelayGeneration,
3585
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
3586
+ pluginAppsFingerprint: input.pluginThreadConfig?.fingerprint,
3587
+ pluginAppsInputFingerprint: input.pluginThreadConfig?.inputFingerprint,
3588
+ pluginAppPolicyContext: input.pluginThreadConfig?.policyContext,
3589
+ contextEngine: context.contextEngineBinding,
3590
+ environmentSelectionFingerprint: context.environmentSelectionFingerprint,
3591
+ ...startParams.ephemeral ? { liveThreadEphemeralPolicy: startParams.developerInstructions } : {},
3592
+ ...!context.preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
3593
+ ...startParams,
3594
+ model: response.model ?? startParams.model ?? null,
3595
+ requestedModel: startParams.model ?? null,
3596
+ modelProvider: input.bindingModelProvider ?? null,
3597
+ requestedModelProvider: startParams.modelProvider ?? input.bindingModelProvider ?? null
3598
+ }, params.params.authProfileId, context.dynamicToolsFingerprint) } : {},
3599
+ lifecycle: {
3600
+ action: "started",
3601
+ ...context.rotatedContextEngineBinding ? { rotatedContextEngineBinding: true } : {}
3602
+ }
3346
3603
  };
3347
- nativeHookRelayUnregisterQueue.add(pending);
3348
- timeout.unref();
3349
3604
  }
3350
- /** Computes the delayed unregister window from Codex's hook timeout. */
3351
- function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
3352
- const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
3353
- return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
3354
- }
3355
- /** Records a native pre-tool failure that Codex does not project as a tool item. */
3356
- function emitCodexNativePreToolUseFailureDiagnostic(params) {
3357
- emitTrustedDiagnosticEvent({
3358
- type: "tool.execution.error",
3359
- ...params.agentId ? { agentId: params.agentId } : {},
3360
- sessionId: params.sessionId,
3361
- ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3362
- runId: params.runId,
3363
- toolName: params.failure.toolName,
3364
- toolCallId: params.failure.toolCallId,
3365
- durationMs: params.failure.durationMs,
3366
- errorCategory: "before_tool_call",
3367
- terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
3368
- ...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
3369
- });
3370
- }
3371
- /** Registers an OpenClaw native hook relay for a Codex app-server turn. */
3372
- function createCodexNativeHookRelay(params) {
3373
- if (params.options?.enabled === false) return;
3374
- const directChildClaims = /* @__PURE__ */ new Map();
3375
- const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
3376
- let foregroundClosed = false;
3377
- let successfulYieldRetentionAuthorized = false;
3378
- const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
3379
- const rejectPendingAdmissions = (reason) => {
3380
- for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
3381
- pendingDirectChildAdmissions.clear();
3605
+ //#endregion
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();
3382
3618
  };
3383
- const relay = registerRetainedNativeHookRelayForBundledRuntime({
3384
- provider: "codex",
3385
- relayId: buildCodexNativeHookRelayId({
3386
- agentId: params.agentId,
3387
- sessionId: params.sessionId,
3388
- sessionKey: params.sessionKey
3389
- }),
3390
- ...params.generation ? { generation: params.generation } : {},
3391
- ...params.generationMismatchGraceMs ? { generationMismatchGraceMs: params.generationMismatchGraceMs } : {},
3392
- ...params.agentId ? { agentId: params.agentId } : {},
3393
- sessionId: params.sessionId,
3394
- ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3395
- ...params.config ? { config: params.config } : {},
3396
- runId: params.runId,
3397
- ...params.channelId ? { channelId: params.channelId } : {},
3398
- ...params.requester ? { requester: params.requester } : {},
3399
- ...params.approvalContext ? { approvalContext: params.approvalContext } : {},
3400
- allowedEvents: params.events,
3401
- preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
3402
- ttlMs: resolveCodexNativeHookRelayTtlMs({
3403
- explicitTtlMs: params.options?.ttlMs,
3404
- attemptTimeoutMs: params.attemptTimeoutMs,
3405
- startupTimeoutMs: params.startupTimeoutMs,
3406
- turnStartTimeoutMs: params.turnStartTimeoutMs
3407
- }),
3408
- signal: params.signal,
3409
- runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
3410
- assertActive: params.hostCapabilities.assertActive,
3411
- retention: {
3412
- readClaim: readCodexNativeChildThreadId,
3413
- shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
3414
- allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
3415
- awaitForegroundAdmission: (childThreadId) => {
3416
- if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
3417
- const existingClaim = directChildClaims.get(childThreadId);
3418
- if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
3419
- const existingPending = pendingDirectChildAdmissions.get(childThreadId);
3420
- if (existingPending) return existingPending.promise.then((claim) => assertClaim(childThreadId, claim));
3421
- if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
3422
- const { promise, resolve, reject } = createDeferred();
3423
- pendingDirectChildAdmissions.set(childThreadId, {
3424
- promise,
3425
- resolve,
3426
- reject
3427
- });
3428
- return promise.then((claim) => assertClaim(childThreadId, claim));
3429
- },
3430
- onDispose: () => {
3431
- foregroundClosed = true;
3432
- rejectPendingAdmissions("native hook relay registration closed");
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
3680
+ });
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 } : {}
3433
3759
  }
3434
- },
3435
- onPreToolUseFailure: params.onPreToolUseFailure,
3436
- command: {
3437
- nice: 10,
3438
- timeoutMs: params.options?.gatewayTimeoutMs
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
+ }
3778
+ }
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;
3439
3821
  }
3440
3822
  });
3441
- const unregister = () => {
3442
- foregroundClosed = true;
3443
- rejectPendingAdmissions("native hook relay foreground closed");
3444
- relay.unregister();
3823
+ const response = assertCodexThreadStartResponse(threadStartResponse);
3824
+ const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
3825
+ const assertCurrent = () => {
3826
+ throwIfAborted();
3827
+ params.params.hostCapabilities.assertActive();
3445
3828
  };
3446
- return {
3447
- ...relay,
3448
- unregister,
3449
- authorizeRetentionAfterSuccessfulYield: () => {
3450
- successfulYieldRetentionAuthorized = true;
3451
- },
3452
- hasClaimedDirectChild: () => directChildClaims.size > 0,
3453
- rejectPendingDirectChild: (threadIdInput, reason) => {
3454
- const threadId = threadIdInput.trim();
3455
- const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
3456
- if (!pending) return;
3457
- pendingDirectChildAdmissions.delete(threadId);
3458
- pending.reject(new Error(reason));
3459
- },
3460
- claimDirectChild: (threadIdInput) => {
3461
- const threadId = threadIdInput.trim();
3462
- if (!threadId) return () => void 0;
3463
- if (directChildClaims.get(threadId)) return () => void 0;
3464
- const claim = Symbol(threadId);
3465
- directChildClaims.set(threadId, claim);
3466
- const pending = pendingDirectChildAdmissions.get(threadId);
3467
- pendingDirectChildAdmissions.delete(threadId);
3468
- pending?.resolve(claim);
3469
- let released = false;
3470
- return () => {
3471
- if (released) return;
3472
- released = true;
3473
- if (directChildClaims.get(threadId) !== claim) return;
3474
- directChildClaims.delete(threadId);
3475
- if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
3476
- };
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 });
3477
3837
  }
3838
+ throw cause;
3478
3839
  };
3479
- }
3480
- function readCodexNativeChildThreadId(rawPayload) {
3481
- if (!isJsonObject(rawPayload) || typeof rawPayload.agent_id !== "string") return;
3482
- return rawPayload.agent_id.trim() || void 0;
3483
- }
3484
- /** Selects the native hook events Codex should install for the current approval mode. */
3485
- function resolveCodexNativeHookRelayEvents(params) {
3486
- if (params.configuredEvents?.length) return params.configuredEvents;
3487
- return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
3488
- }
3489
- /** Derives the native hook relay TTL from the turn budget unless explicitly configured. */
3490
- function resolveCodexNativeHookRelayTtlMs(params) {
3491
- if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
3492
- const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
3493
- return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
3494
- }
3495
- /** Builds a stable relay id scoped to the agent and session identity. */
3496
- function buildCodexNativeHookRelayId(params) {
3497
- const hash = createHash("sha256");
3498
- hash.update("openclaw:codex:native-hook-relay:v1");
3499
- hash.update("\0");
3500
- hash.update(params.agentId?.trim() || "");
3501
- hash.update("\0");
3502
- hash.update(params.sessionKey?.trim() || params.sessionId);
3503
- return `codex-${hash.digest("hex").slice(0, 40)}`;
3504
- }
3505
- const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
3506
- pre_tool_use: "PreToolUse",
3507
- post_tool_use: "PostToolUse",
3508
- permission_request: "PermissionRequest",
3509
- before_agent_finalize: "Stop"
3510
- };
3511
- const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
3512
- pre_tool_use: "pre_tool_use",
3513
- post_tool_use: "post_tool_use",
3514
- permission_request: "permission_request",
3515
- before_agent_finalize: "stop"
3516
- };
3517
- const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
3518
- /** Builds the Codex config overlay that installs trusted command hooks for relay events. */
3519
- function buildCodexNativeHookRelayConfig(params) {
3520
- const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
3521
- const selectedEvents = new Set(events);
3522
- const config = { "features.hooks": true };
3523
- const hookState = {};
3524
- for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
3525
- const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
3526
- const selected = selectedEvents.has(event);
3527
- const shouldRelay = params.relay.shouldRelayEvent(event);
3528
- if (!selected || !shouldRelay) {
3529
- if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
3530
- 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 };
3531
- continue;
3532
- }
3533
- const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
3534
- const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
3535
- const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
3536
- config[`hooks.${codexEvent}`] = [{
3537
- ...matcher ? { matcher } : {},
3538
- hooks: [{
3539
- type: "command",
3540
- command,
3541
- timeout,
3542
- async: false,
3543
- statusMessage: "OpenClaw native hook relay"
3544
- }]
3545
- }];
3546
- const state = {
3547
- enabled: true,
3548
- trusted_hash: codexCommandHookTrustedHash({
3549
- event,
3550
- command,
3551
- matcher,
3552
- timeout,
3553
- statusMessage: "OpenClaw native hook relay"
3554
- })
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
3862
+ });
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
3555
3894
  };
3556
- for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
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
+ });
3557
3925
  }
3558
- config["hooks.state"] = hookState;
3559
- return config;
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"
3933
+ });
3934
+ return buildStartedCodexThreadBinding({
3935
+ bindingModelProvider,
3936
+ clientId,
3937
+ context,
3938
+ finalConfigPatch,
3939
+ nextMcpServersFingerprint,
3940
+ params,
3941
+ pluginThreadConfig,
3942
+ response,
3943
+ rolloutPath,
3944
+ startModelProvider: requestModelProvider ?? startModelProvider,
3945
+ startParams,
3946
+ modelProvider
3947
+ });
3560
3948
  }
3561
- /** Builds a Codex config overlay that disables native hooks and clears hook arrays. */
3562
- function buildCodexNativeHookRelayDisabledConfig() {
3563
- return {
3564
- "features.hooks": false,
3565
- "hooks.PreToolUse": [],
3566
- "hooks.PostToolUse": [],
3567
- "hooks.PermissionRequest": [],
3568
- "hooks.Stop": []
3569
- };
3949
+ //#endregion
3950
+ //#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
3951
+ /** Preserves the caller's abort reason across thread ownership transitions. */
3952
+ function throwIfCodexThreadLifecycleAborted(signal) {
3953
+ if (!signal?.aborted) return;
3954
+ const reason = signal.reason;
3955
+ if (reason instanceof Error) throw reason;
3956
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
3957
+ error.name = "AbortError";
3958
+ throw error;
3570
3959
  }
3571
- function normalizeHookTimeoutSec(value) {
3572
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC;
3960
+ /** Releases consumed subscription ownership or retires an unsafe client. */
3961
+ async function releaseCodexConsumedLiveThread(options) {
3962
+ if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
3963
+ threadId: options.threadId,
3964
+ timeoutMs: 5e3,
3965
+ assertCurrent: options.assertCurrent
3966
+ }))) return;
3967
+ return await abandonCodexLiveThreadRelease(options, options.cause);
3968
+ }
3969
+ async function abandonCodexLiveThreadRelease(options, cause) {
3970
+ options.assertCurrent?.();
3971
+ await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
3972
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, cause !== void 0 ? { cause } : void 0);
3573
3973
  }
3574
- function resolveCodexNativeHookRelayCommandTimeoutMs(hookTimeoutSec) {
3575
- const parentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 5e3;
3576
- 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)));
3577
- return Math.max(1, parentTimeoutMs - parentMarginMs);
3974
+ /** Releases through the retained owner, preserving its guarded callback and rollback. */
3975
+ async function releaseCodexRetainedLiveThread(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
+ }
3578
3982
  }
3579
- function buildCodexNativeToolMatcher(toolNames) {
3580
- if (toolNames === void 0) return;
3581
- if (toolNames.length === 0) throw new TypeError("Codex native hook matcher requires at least one tool name");
3582
- const nativeNames = /* @__PURE__ */ new Set();
3583
- let hasCustomToolName = false;
3584
- for (const toolName of toolNames) {
3585
- const canonicalToolName = toolName.trim();
3586
- if (!canonicalToolName || canonicalToolName === "*") throw new TypeError("Codex native hook matcher requires canonical OpenClaw tool ids");
3587
- const nativeAliases = CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID[canonicalToolName];
3588
- if (!nativeAliases) hasCustomToolName = true;
3589
- for (const nativeName of nativeAliases ?? [canonicalToolName]) nativeNames.add(nativeName);
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();
3590
4003
  }
3591
- const sortedNames = Array.from(nativeNames).toSorted();
3592
- if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
3593
- return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
3594
4004
  }
3595
- function codexCommandHookTrustedHash(params) {
3596
- const identity = {
3597
- event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
3598
- ...params.matcher ? { matcher: params.matcher } : {},
3599
- hooks: [{
3600
- async: false,
3601
- command: params.command,
3602
- statusMessage: params.statusMessage,
3603
- timeout: params.timeout,
3604
- type: "command"
3605
- }]
4005
+ /** Reuses one safely owned, fully matching subscription on its original client. */
4006
+ async function tryReuseCodexLiveThread(options) {
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" };
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
+ }
3606
4038
  };
3607
- return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
3608
- }
3609
- function sortJsonValue(value) {
3610
- if (!value || typeof value !== "object") return value;
3611
- if (Array.isArray(value)) return value.map(sortJsonValue);
3612
- const sorted = {};
3613
- for (const [key, entry] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) sorted[key] = sortJsonValue(entry);
3614
- return sorted;
4039
+ let ownershipTransferred = false;
4040
+ let preserveSubscription = false;
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();
4105
+ const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
4106
+ const model = startModelSelection.model;
4107
+ if (!(incognito || await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
4108
+ kind: "patch",
4109
+ threadId: binding.threadId,
4110
+ patch: {
4111
+ cwd: params.cwd,
4112
+ model,
4113
+ nativeHookRelayGeneration,
4114
+ environmentSelectionFingerprint
4115
+ }
4116
+ }, assertWarmOwner)))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
4117
+ assertWarmOwner();
4118
+ lifecycleTiming.mark("thread-ready");
4119
+ lifecycleTiming.logSummary({
4120
+ runId: params.params.runId,
4121
+ sessionId: params.params.sessionId,
4122
+ sessionKey: params.params.sessionKey,
4123
+ threadId: binding.threadId,
4124
+ action: "resumed"
4125
+ });
4126
+ ownershipTransferred = true;
4127
+ return {
4128
+ kind: "ready",
4129
+ binding: {
4130
+ ...binding,
4131
+ ...!incognito ? {
4132
+ cwd: params.cwd,
4133
+ model,
4134
+ nativeHookRelayGeneration,
4135
+ environmentSelectionFingerprint
4136
+ } : {},
4137
+ liveThreadConfigFingerprint,
4138
+ liveThreadEphemeralPolicy: retainedThread.ephemeralPolicy,
4139
+ liveThreadOwnership: retainedThread,
4140
+ ...!incognito && retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
4141
+ lifecycle: { action: "resumed" }
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
+ }
3615
4162
  }
3616
4163
  //#endregion
3617
- //#region extensions/codex/src/app-server/profiler-flag.ts
3618
- const PROFILER_FLAGS = ["profiler", "codex.profiler"];
3619
- /** Checks the generic and Codex-specific profiler diagnostic flags. */
3620
- function isCodexAppServerProfilerEnabled(config, env = process.env) {
3621
- return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
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;
3622
4176
  }
3623
- //#endregion
3624
- //#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
3625
- const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
3626
- const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
3627
- function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
3628
- const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
3629
- const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
3630
- return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
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();
3631
4199
  }
3632
- function formatCodexThreadLifecycleTimingSummary(params) {
3633
- const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
3634
- 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}`;
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
4271
+ };
4272
+ } catch (error) {
4273
+ dispose();
4274
+ throw error;
4275
+ }
3635
4276
  }
3636
- function createCodexThreadLifecycleTimingTracker(options = {}) {
3637
- const log = options.log ?? embeddedAgentLog;
3638
- if (!options.enabled && log.isEnabled?.("trace") !== true) return {
3639
- async measure(_name, run) {
3640
- return await run();
3641
- },
3642
- measureSync(_name, run) {
3643
- return run();
3644
- },
3645
- mark() {},
3646
- logSummary() {}
3647
- };
3648
- const now = options.now ?? Date.now;
3649
- const startedAt = now();
3650
- let didLog = false;
3651
- const spans = [];
3652
- const toMs = (value) => Math.max(0, Math.round(value));
3653
- const record = (name, spanStartedAt) => {
3654
- const currentAt = now();
3655
- spans.push({
3656
- name,
3657
- durationMs: toMs(currentAt - spanStartedAt),
3658
- elapsedMs: toMs(currentAt - startedAt)
3659
- });
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();
3660
4285
  };
3661
- const snapshot = () => ({
3662
- totalMs: toMs(now() - startedAt),
3663
- spans: spans.slice()
3664
- });
4286
+ assertCurrent();
4287
+ const thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context);
4288
+ assertCurrent();
4289
+ assertCodexSupervisionThreadLineage(binding, thread);
3665
4290
  return {
3666
- async measure(name, run) {
3667
- const spanStartedAt = now();
3668
- try {
3669
- return await run();
3670
- } finally {
3671
- record(name, spanStartedAt);
3672
- }
3673
- },
3674
- measureSync(name, run) {
3675
- const spanStartedAt = now();
3676
- try {
3677
- return run();
3678
- } finally {
3679
- record(name, spanStartedAt);
3680
- }
3681
- },
3682
- mark(name) {
3683
- record(name, now());
3684
- },
3685
- logSummary(params) {
3686
- if (didLog) return;
3687
- const summary = snapshot();
3688
- const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
3689
- if (!shouldWarn && !log.isEnabled?.("trace")) return;
3690
- didLog = true;
3691
- const message = formatCodexThreadLifecycleTimingSummary({
3692
- runId: params.runId,
3693
- sessionId: params.sessionId,
3694
- sessionKey: params.sessionKey,
3695
- action: params.action,
3696
- summary
3697
- });
3698
- const meta = {
3699
- runId: params.runId,
3700
- sessionId: params.sessionId,
3701
- sessionKey: params.sessionKey,
3702
- action: params.action,
3703
- threadId: params.threadId,
3704
- totalMs: summary.totalMs,
3705
- spans: summary.spans
3706
- };
3707
- if (shouldWarn) log.warn(message, meta);
3708
- else log.trace(message, meta);
3709
- }
4291
+ ...observeCodexThreadConfiguration(params, thread, assertCurrent),
4292
+ assertCurrent
3710
4293
  };
3711
4294
  }
3712
- //#endregion
3713
- //#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
3714
- async function prepareCodexThreadLifecyclePreflight(params) {
3715
- await assertCodexModelBackedReviewerEffectiveConfig({
3716
- client: params.client,
3717
- approvalsReviewer: params.appServer.approvalsReviewer,
3718
- cwd: params.cwd,
3719
- signal: params.signal
3720
- });
3721
- if (params.nativeHookRelayRequired) await assertCodexNativeHookRelayAllowed(params.client, params.signal);
3722
- const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
3723
- ...params.timing,
3724
- enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
3725
- });
3726
- const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
3727
- const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
3728
- const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
3729
- const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
3730
- config: params.params.config,
3731
- disableTools: params.params.disableTools,
3732
- nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
3733
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3734
- webSearchAllowed: params.webSearchAllowed
3735
- })).threadConfig);
3736
- const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
3737
- const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
3738
- const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRun({
3739
- run: params.params,
3740
- cwd: params.cwd,
3741
- agentId: params.agentId ?? params.params.agentId,
3742
- allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
3743
- warn: (message) => embeddedAgentLog.warn(message),
3744
- onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
3745
- serverName,
3746
- error: formatErrorMessage(error)
3747
- })
3748
- });
3749
- const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
3750
- client: params.client,
3751
- codexHome: params.appServer.start.env?.CODEX_HOME,
3752
- cwd: params.cwd,
3753
- home: params.appServer.start.env?.HOME,
3754
- signal: params.signal,
3755
- userProfile: params.appServer.start.env?.USERPROFILE
3756
- }));
3757
- const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
3758
- version: 1,
3759
- disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
3760
- }) : void 0;
3761
- const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
3762
- const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
3763
- const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
3764
- const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
3765
- const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
3766
- const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
3767
- const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
3768
- const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
3769
- if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
3770
- const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
3771
- if (restrictedToolSurface || imageGenerationDenied) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
3772
- restrictedToolSurface,
3773
- additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0
3774
- }, params.signal));
3775
- const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
3776
- version: 1,
3777
- baseInstructions: "",
3778
- config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
3779
- }) : void 0;
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";
3780
4298
  return {
3781
- contextEngineBinding,
3782
- dynamicToolsContainDeferred,
3783
- dynamicToolsFingerprint,
3784
- environmentSelectionFingerprint,
3785
- hostSystemAgentActive,
3786
- legacyDynamicToolsFingerprint,
3787
- legacyUserMcpServersFingerprint,
3788
- lifecycleTiming,
3789
- nativeSkillIsolation,
3790
- nativeSkillIsolationFingerprint,
3791
- networkProxyConfigFingerprint,
3792
- ringZeroActive,
3793
- ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
3794
- ringZeroConfigFingerprint,
3795
- restrictedToolSurface,
3796
- restrictedToolSurfaceInheritedMcpServerNames,
3797
- userMcpServersConfigPatch,
3798
- userMcpServersFingerprint,
3799
- webSearchThreadConfigFingerprint
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
+ }
3800
4306
  };
3801
4307
  }
3802
4308
  //#endregion
@@ -4168,6 +4674,19 @@ async function startOrResumeThread(params) {
4168
4674
  const clientId = resolveCodexAppServerClientInstanceId(params.client);
4169
4675
  return await withCodexThreadLifecycleBinding(params, async (bindingIdentity, currentBinding) => {
4170
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
+ }
4171
4690
  const preflight = await prepareCodexThreadLifecyclePreflight(params);
4172
4691
  const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = preflight;
4173
4692
  let replacementPredecessor;
@@ -4181,35 +4700,15 @@ async function startOrResumeThread(params) {
4181
4700
  config: params.params.config
4182
4701
  });
4183
4702
  const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
4184
- const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId, assertCurrent) => {
4185
- if (ownerClientId && ownerClientId !== clientId) {
4186
- const previousClient = retainSharedCodexAppServerClientByInstanceId(ownerClientId);
4187
- if (!previousClient) return false;
4188
- try {
4189
- const assertPrevious = assertCurrent ? captureExclusiveSharedCodexAppServerClient(previousClient.client) : void 0;
4190
- if (isCodexAppServerLiveThreadClaimed(previousClient.client, threadId)) throw new Error(`Codex thread ${threadId} is claimed by active work; stop it first.`);
4191
- return await releaseCodexRetainedLiveThread({
4192
- client: previousClient.client,
4193
- lifecycleTiming,
4194
- threadId,
4195
- assertCurrent: assertCurrent ? () => {
4196
- assertCurrent();
4197
- assertPrevious?.();
4198
- } : void 0
4199
- });
4200
- } finally {
4201
- previousClient.release();
4202
- }
4203
- }
4204
- if (isCodexAppServerLiveThreadClaimed(params.client, threadId)) throw new Error(`Codex thread ${threadId} is claimed by active work; stop it first.`);
4205
- return await releaseCodexRetainedLiveThread({
4206
- client: params.client,
4207
- abandonClient: params.abandonClient,
4208
- lifecycleTiming,
4209
- threadId,
4210
- assertCurrent
4211
- });
4212
- };
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
+ });
4213
4712
  if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
4214
4713
  if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
4215
4714
  bindingStore: params.bindingStore,
@@ -4484,28 +4983,6 @@ async function startOrResumeThread(params) {
4484
4983
  embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
4485
4984
  await clearCurrentBinding("rotating a stale thread binding");
4486
4985
  }
4487
- } else if (incognito) {
4488
- if (binding.clientId && binding.clientId === clientId && ((await buildLoadedPluginThreadConfig(binding))?.fingerprint ?? binding.pluginAppsFingerprint) === binding.pluginAppsFingerprint) {
4489
- params.buildFinalConfigPatch?.({
4490
- action: "resume",
4491
- binding
4492
- });
4493
- throwIfAborted();
4494
- lifecycleTiming.mark("thread-ready");
4495
- lifecycleTiming.logSummary({
4496
- runId: params.params.runId,
4497
- sessionId: params.params.sessionId,
4498
- sessionKey: params.params.sessionKey,
4499
- threadId: binding.threadId,
4500
- action: "resumed"
4501
- });
4502
- return {
4503
- ...binding,
4504
- lifecycle: { action: "resumed" }
4505
- };
4506
- }
4507
- await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
4508
- binding = void 0;
4509
4986
  } else {
4510
4987
  const warmReuse = await tryReuseCodexLiveThread({
4511
4988
  ...requestContext,
@@ -4515,21 +4992,28 @@ async function startOrResumeThread(params) {
4515
4992
  buildLoadedPluginThreadConfig
4516
4993
  });
4517
4994
  if (warmReuse.kind === "ready") return warmReuse.binding;
4518
- if (warmReuse.kind === "rotate") {
4995
+ if (incognito || warmReuse.kind === "rotate") {
4519
4996
  throwIfAborted();
4520
- await clearCurrentBinding("rotating a stale plugin app binding");
4997
+ await clearCurrentBinding(incognito ? "rotating an unavailable ephemeral thread binding" : "rotating a stale plugin app binding");
4521
4998
  } else {
4522
- if (binding.preserveNativeModel === true) await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, requestContext);
4523
- await releaseRetainedThread(binding.threadId);
4524
- const resumed = await resumeExistingCodexThread(params, {
4525
- ...requestContext,
4526
- binding,
4527
- clearCurrentBinding,
4528
- prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
4529
- prebuiltPluginThreadConfig,
4530
- buildLoadedPluginThreadConfig
4531
- });
4532
- if (resumed) return resumed;
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
+ }
4533
5017
  }
4534
5018
  }
4535
5019
  }
@@ -4546,6 +5030,54 @@ async function startOrResumeThread(params) {
4546
5030
  });
4547
5031
  }
4548
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
4549
5081
  //#region extensions/codex/src/app-server/user-input.ts
4550
5082
  /** Builds ordered Codex user input for both new turns and same-turn steering. */
4551
5083
  function buildCodexUserInput(text, images) {
@@ -4598,12 +5130,22 @@ function buildTurnStartParams(params, options) {
4598
5130
  agentDir: params.agentDir,
4599
5131
  config: params.config
4600
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;
4601
5139
  const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
4602
5140
  const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
4603
- let additionalContext = currentSenderContext ? { openclaw_current_sender: {
4604
- kind: "untrusted",
4605
- value: currentSenderContext
4606
- } } : 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
+ };
4607
5149
  if (params.permissionChange?.notice) additionalContext = {
4608
5150
  ...additionalContext,
4609
5151
  openclaw_permission_change: {
@@ -4625,23 +5167,33 @@ function buildTurnStartParams(params, options) {
4625
5167
  personality: CODEX_NATIVE_PERSONALITY_NONE
4626
5168
  } : {},
4627
5169
  ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : options.clearInheritedServiceTier ? { serviceTier: null } : {},
4628
- ...modelSelection ? { effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)) } : {},
4629
- ...options.environmentSelection ? { environments: options.environmentSelection } : {},
4630
- ...modelSelection ? { collaborationMode: buildTurnCollaborationMode(params, {
4631
- model: modelSelection.model,
4632
- turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
4633
- skillsCollaborationInstructions: options.skillsCollaborationInstructions,
4634
- memoryCollaborationInstructions: options.memoryCollaborationInstructions
4635
- }) } : {}
5170
+ ...collaborationMode ? {
5171
+ effort: collaborationMode.settings.reasoning_effort,
5172
+ collaborationMode
5173
+ } : {},
5174
+ ...options.environmentSelection ? { environments: options.environmentSelection } : {}
4636
5175
  };
4637
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
+ }
4638
5186
  function buildTurnCollaborationMode(params, options = {}) {
4639
5187
  const model = options.model ?? params.modelId;
4640
5188
  return {
4641
5189
  mode: "default",
4642
5190
  settings: {
4643
5191
  model,
4644
- 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
+ }),
4645
5197
  developer_instructions: buildTurnScopedCollaborationInstructions(params, options)
4646
5198
  }
4647
5199
  };
@@ -4679,4 +5231,4 @@ function joinPresentSections(...sections) {
4679
5231
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
4680
5232
  }
4681
5233
  //#endregion
4682
- export { resolveCodexContinuityProjectionMaxChars as $, readCodexSupportedReasoningEfforts as A, isMessageOnlyCodexSourceReply as B, buildCodexProjectDocThreadConfig as C, resolveCodexAppServerThreadModelSelection as D, resolveCodexAppServerRequestModelSelection as E, codexDynamicToolsFingerprint as F, buildContextEngineBinding as G, normalizeCodexDynamicToolName as H, codexLegacyDynamicToolsFingerprint as I, fitCodexProjectedContextForTurnStart as J, isContextEngineBindingCompatible as K, filterCodexDynamicTools as L, CODEX_MANAGED_THREAD_NAMESPACE as M, createCodexManagedThreadStore as N, resolveCodexBindingModelProviderFallback as O, areCodexDynamicToolFingerprintsCompatible as P, resolveCodexContextEngineProjectionReserveTokens as Q, filterCodexDynamicToolsForDisabledNativeSurface as R, buildDeveloperInstructions as S, resolveCodexAppServerModelProvider as T, resolveCodexDynamicToolsLoading as U, isSystemAgentOnlyCodexDynamicToolAllowlist as V, resolveCodexDynamicToolsLoadingForRuntime as W, projectContextEngineAssemblyForCodex as X, neutralizeCodexExplicitMentionSigils as Y, resolveCodexContextEngineProjectionMaxChars as Z, buildCodexRingZeroThreadConfigPatch as _, isCodexAppServerProfilerEnabled as a, buildPluginAppPolicyContext as at, buildCodexNativeWebSearchThreadConfig as b, buildCodexNativeHookRelayConfig as c, ensureCodexPluginActivation as ct, emitCodexNativePreToolUseFailureDiagnostic as d, isOpenAiCuratedMarketplaceName as dt, buildCodexPluginAppsConfigPatchFromPolicyContext as et, resolveCodexNativeHookRelayEvents as f, pluginReadParams as ft, attestCodexRestrictedToolSurfaceMcpServersDisabled as g, assertCodexManagedRequirementsDoNotOverrideToolPolicy as h, startOrResumeThread as i, buildDisabledAppsConfigPatch as it, CODEX_MANAGED_THREAD_MAX_ENTRIES as j, resolveReasoningEffort as k, buildCodexNativeHookRelayDisabledConfig as l, refreshCodexPluginRuntimeState as lt, scheduleCodexNativeHookRelayUnregister as m, buildTurnStartParams as n, buildCodexPluginThreadConfigInputFingerprint as nt, CODEX_NATIVE_HOOK_RELAY_EVENTS as o, mergeCodexThreadConfigs as ot, resolveCodexNativeHookRelayTtlMs as p, resolveRecoverableCodexPluginConfigKeys as pt, buildCodexContinuityCalibration as q, buildCodexUserInput as r, buildCodexPluginThreadConfigTimeoutFallback as rt, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as s, shouldBuildCodexPluginThreadConfig as st, buildTurnCollaborationMode as t, buildCodexPluginThreadConfig as tt, createCodexNativeHookRelay as u, isOpenAiCuratedMarketplace as ut, buildCodexRuntimeThreadConfig as v, CODEX_NATIVE_PERSONALITY_NONE as w, resolveCodexWebSearchPlan as x, readCodexInheritedMcpServerNames as y, isForcedPrivateQaCodexRuntime 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 };