@openclaw/codex 2026.7.2-beta.1 → 2026.7.2-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/dist/{app-server-policy-DVcxdse_.js → app-server-policy-Scc-Wevo.js} +1 -1
  3. package/dist/{attempt-notifications-DEv9h7iC.js → attempt-notifications-BFmNhqFl.js} +3 -6
  4. package/dist/cli-metadata.js +1 -1
  5. package/dist/{command-formatters-D6ZlGNmb.js → command-formatters-Dq9InZUK.js} +34 -3
  6. package/dist/{command-handlers-Cpl9fUWv.js → command-handlers-IgqmL_kv.js} +10 -13
  7. package/dist/{compact-FnJmiPNA.js → compact-DCTmg3id.js} +4 -5
  8. package/dist/{computer-use-DVzr8OP1.js → computer-use-CPcU8TjG.js} +4 -4
  9. package/dist/{conversation-control-DtQ-E7mr.js → conversation-control-CGkBlfc5.js} +5 -7
  10. package/dist/doctor-contract-api.js +1 -1
  11. package/dist/{dynamic-tools-BMLoaTeG.js → dynamic-tools-C_1tEs34.js} +608 -330
  12. package/dist/harness.js +9 -5
  13. package/dist/index.js +52 -31
  14. package/dist/{media-understanding-provider-D580L0P8.js → media-understanding-provider-D31dOJwb.js} +11 -12
  15. package/dist/media-understanding-provider.js +1 -1
  16. package/dist/{models-DuKzZA6G.js → models-uh26C8QU.js} +5 -9
  17. package/dist/{notification-correlation-DfaCm0mx.js → notification-correlation-KmfV4EkP.js} +3 -3
  18. package/dist/{plugin-app-cache-key-BKNjHMEs.js → plugin-app-cache-key-Ceb-lm2c.js} +2 -2
  19. package/dist/{rate-limits-Dhp04Rqo.js → rate-limits-DyXaYAxU.js} +7 -6
  20. package/dist/{request-DborTWgw.js → request-B_oQsCXy.js} +54 -8
  21. package/dist/{run-attempt-VPVJoYDP.js → run-attempt-MLzoMe_m.js} +499 -295
  22. package/dist/{config-c48K5HP9.js → session-binding-h1mmCGnl.js} +786 -3
  23. package/dist/{session-catalog-7H112Tr_.js → session-catalog-CEvoXWHA.js} +45 -29
  24. package/dist/{session-cli-B28RhCyp.js → session-cli-XsEuWb_C.js} +1 -1
  25. package/dist/{shared-client-D4mFI9al.js → shared-client-JiAnW6pc.js} +255 -81
  26. package/dist/{side-question-CgJBz52s.js → side-question-DqDvIwSU.js} +15 -13
  27. package/dist/{thread-lifecycle-BgLXzjvV.js → thread-lifecycle-CN_pPtPh.js} +1787 -1579
  28. package/dist/{protocol-validators-dZQ-UTOa.js → transcript-mirror-D9rTxx2P.js} +485 -2
  29. package/dist/usage-D5Rohxoc.js +25 -0
  30. package/dist/{web-search-provider.runtime-Bs-eTn5U.js → web-search-provider.runtime-CKVBn3fP.js} +3 -3
  31. package/npm-shrinkwrap.json +30 -30
  32. package/openclaw.plugin.json +10 -5
  33. package/package.json +6 -6
  34. package/dist/capabilities-42Dfn2TV.js +0 -33
  35. package/dist/prompt-overlay.js +0 -15
  36. package/dist/protocol-BMifTfdW.js +0 -15
  37. package/dist/provider-catalog.js +0 -118
  38. package/dist/provider-discovery.js +0 -34
  39. package/dist/provider.js +0 -307
  40. package/dist/session-binding-BMfX1OX-.js +0 -786
  41. package/dist/transcript-mirror-DhLwFIL4.js +0 -485
@@ -1,22 +1,18 @@
1
- import { i as isJsonObject, r as flattenCodexDynamicToolFunctions } from "./protocol-BMifTfdW.js";
2
- import { _ as resolveCodexPluginsPolicy, n as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, o as codexSandboxPolicyForTurn, t as CODEX_PLUGINS_MARKETPLACE_NAME } from "./config-c48K5HP9.js";
3
- import { n as assertCodexThreadResumeResponse, r as assertCodexThreadStartResponse, t as assertCodexThreadForkResponse } from "./protocol-validators-dZQ-UTOa.js";
4
- import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-BKNjHMEs.js";
5
- import { O as CodexAppServerRpcError, P as isCodexAppServerPrewriteRequestCancellationError, d as retireSharedCodexAppServerClientIfCurrent, j as isCodexAppServerConnectionClosedError, k as getCodexAppServerClientInstanceId, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, o as isCodexAppServerStartSelectionChangedError, t as clearSharedCodexAppServerClientIfCurrent } from "./shared-client-D4mFI9al.js";
6
- import { a as isCodexAppServerNativeAuthProfile, c as sessionBindingIdentity, i as hashCodexAppServerBindingFingerprint, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider, s as reclaimCurrentCodexSessionGeneration } from "./session-binding-BMfX1OX-.js";
7
- import { CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "./prompt-overlay.js";
8
- import { isMaxReasoningCodexModel, isModernCodexModel, readCodexSupportedReasoningEfforts, resolveCodexFallbackReasoningEfforts, resolveCodexSupportedReasoningEffort } from "./provider.js";
9
- import { a as projectBoundedCodexThreadHistory } from "./transcript-mirror-DhLwFIL4.js";
1
+ import { G as isCodexAppServerPrewriteRequestCancellationError, H as isCodexAppServerConnectionClosedError, R as CodexAppServerRpcError, X as flattenCodexDynamicToolFunctions, Z as isJsonObject, d as retireSharedCodexAppServerClientIfCurrent, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, o as isCodexAppServerStartSelectionChangedError, t as clearSharedCodexAppServerClientIfCurrent, z as getCodexAppServerClientInstanceId } from "./shared-client-JiAnW6pc.js";
2
+ import { T as resolveCodexPluginsPolicy, a as isCodexAppServerNativeAuthProfile, c as sessionBindingIdentity, d as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, h as codexSandboxPolicyForTurn, i as hashCodexAppServerBindingFingerprint, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider, s as reclaimCurrentCodexSessionGeneration, u as CODEX_PLUGINS_MARKETPLACE_NAME } from "./session-binding-h1mmCGnl.js";
3
+ import { a as projectBoundedCodexThreadHistory, d as assertCodexThreadStartResponse, l as assertCodexThreadForkResponse, u as assertCodexThreadResumeResponse } from "./transcript-mirror-D9rTxx2P.js";
4
+ import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-Ceb-lm2c.js";
10
5
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
11
6
  import * as crypto$1 from "node:crypto";
12
7
  import crypto from "node:crypto";
13
8
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
14
9
  import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
10
+ import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
15
11
  import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
12
+ import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
16
13
  import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
17
- import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
14
+ import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
18
15
  import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
19
- import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
20
16
  //#region extensions/codex/src/app-server/plugin-inventory.ts
21
17
  /**
22
18
  * Reads Codex plugin marketplace state and app inventory to decide which
@@ -35,13 +31,13 @@ async function readCodexPluginInventory(params) {
35
31
  }]
36
32
  };
37
33
  const appInventory = readCachedAppInventory(params);
38
- const curatedListed = await params.request("plugin/list", { cwds: [] });
34
+ const curatedListed = await listCodexPluginMetadata(params, "curated-global", {});
39
35
  const shouldListWorkspacePlugins = policy.pluginPolicies.some((pluginPolicy) => pluginPolicy.enabled && pluginPolicy.marketplaceName === "workspace-directory");
40
36
  let workspaceListResult;
41
37
  if (shouldListWorkspacePlugins) try {
42
38
  workspaceListResult = {
43
39
  kind: "listed",
44
- response: await params.request("plugin/list", {
40
+ response: await listCodexPluginMetadata(params, "workspace-directory", {
45
41
  cwds: [],
46
42
  marketplaceKinds: [CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME]
47
43
  })
@@ -138,6 +134,31 @@ function pluginReadParams(marketplace, pluginName) {
138
134
  pluginName
139
135
  };
140
136
  }
137
+ /** Returns configured plugin keys whose current metadata may still recover. */
138
+ function resolveRecoverableCodexPluginConfigKeys(params) {
139
+ return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
140
+ pluginPolicy,
141
+ metadataCache: params.metadataCache,
142
+ appCacheKey: params.appCacheKey
143
+ })).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
144
+ }
145
+ async function listCodexPluginMetadata(params, queryKind, requestParams) {
146
+ if (!params.metadataCache || !params.appCacheKey || queryKind === "workspace-directory") return await params.request("plugin/list", requestParams);
147
+ return (await params.metadataCache.load({
148
+ appCacheKey: params.appCacheKey,
149
+ queryKind,
150
+ requestParams,
151
+ request: async (method, listedParams) => await params.request(method, listedParams),
152
+ cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
153
+ })).response;
154
+ }
155
+ function isSettledMissingPluginPolicy(params) {
156
+ const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "workspace-directory" : "curated-global";
157
+ const listed = params.metadataCache.read(params.appCacheKey, queryKind)?.response;
158
+ if (!listed) return false;
159
+ if (queryKind === "workspace-directory") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
160
+ return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
161
+ }
141
162
  function readCachedAppInventory(params) {
142
163
  if (!params.appCache || !params.appCacheKey) return;
143
164
  const request = async (method, requestParams) => await params.request(method, requestParams);
@@ -237,6 +258,7 @@ function marketplaceRef(marketplace, name) {
237
258
  ...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
238
259
  };
239
260
  }
261
+ /** True for either supported OpenAI curated marketplace wire name. */
240
262
  function isOpenAiCuratedMarketplace(marketplace) {
241
263
  return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME;
242
264
  }
@@ -245,10 +267,10 @@ function isOpenAiCuratedMarketplace(marketplace) {
245
267
  /** Activates a curated plugin or rejects a workspace plugin that is not already active. */
246
268
  async function ensureCodexPluginActivation(params) {
247
269
  if (params.identity.marketplaceName === "workspace-directory") return activationFailure(params.identity, "disabled", { message: "workspace-directory plugins must be installed and enabled outside OpenClaw before use." });
248
- const listed = await params.request("plugin/list", { cwds: [] });
270
+ const listed = await listCuratedCodexPluginMetadata(params);
249
271
  const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName);
250
272
  if (!resolved) {
251
- if (!listed.marketplaces.some((marketplace) => marketplace.name === "openai-curated")) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
273
+ if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
252
274
  return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
253
275
  }
254
276
  if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
@@ -260,6 +282,7 @@ async function ensureCodexPluginActivation(params) {
260
282
  diagnostics: []
261
283
  };
262
284
  const installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, resolved.marketplace.remoteMarketplaceName && resolved.summary.remotePluginId ? resolved.summary.remotePluginId : params.identity.pluginName));
285
+ if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
263
286
  const refreshDiagnostics = [];
264
287
  let refreshFailed = false;
265
288
  try {
@@ -267,6 +290,7 @@ async function ensureCodexPluginActivation(params) {
267
290
  request: params.request,
268
291
  appCache: params.appCache,
269
292
  appCacheKey: params.appCacheKey,
293
+ metadataCache: params.metadataCache,
270
294
  targetAppIds: params.targetAppIds
271
295
  });
272
296
  refreshDiagnostics.push(...refreshResult.diagnostics);
@@ -288,7 +312,7 @@ async function ensureCodexPluginActivation(params) {
288
312
  /** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
289
313
  async function refreshCodexPluginRuntimeState(params) {
290
314
  const diagnostics = [];
291
- await params.request("plugin/list", { cwds: [] });
315
+ await listCuratedCodexPluginMetadata(params);
292
316
  await params.request("skills/list", {
293
317
  cwds: [],
294
318
  forceReload: true
@@ -315,6 +339,17 @@ async function refreshCodexPluginRuntimeState(params) {
315
339
  }
316
340
  return { diagnostics };
317
341
  }
342
+ async function listCuratedCodexPluginMetadata(params) {
343
+ const requestParams = {};
344
+ if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
345
+ return (await params.metadataCache.load({
346
+ appCacheKey: params.appCacheKey,
347
+ queryKind: "curated-global",
348
+ requestParams,
349
+ request: async (method, listedParams) => await params.request(method, listedParams),
350
+ cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
351
+ })).response;
352
+ }
318
353
  function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
319
354
  return {
320
355
  identity,
@@ -345,6 +380,20 @@ function buildCodexPluginThreadConfigInputFingerprint(params) {
345
380
  appCacheKey: params.appCacheKey ?? null
346
381
  });
347
382
  }
383
+ /** Builds the deny-all app patch used when plugin discovery exceeds its turn budget. */
384
+ function buildCodexPluginThreadConfigTimeoutFallback(params) {
385
+ return {
386
+ ...emptyPluginThreadConfig({
387
+ enabled: true,
388
+ inputFingerprint: buildCodexPluginThreadConfigInputFingerprint(params),
389
+ configPatch: buildDisabledAppsConfigPatch()
390
+ }),
391
+ diagnostics: [{
392
+ code: "plugin_config_timeout",
393
+ message: params.message
394
+ }]
395
+ };
396
+ }
348
397
  /** Builds the Codex apps config patch and policy context for a native thread. */
349
398
  async function buildCodexPluginThreadConfig(params) {
350
399
  const appCache = params.appCache ?? defaultCodexAppInventoryCache;
@@ -364,6 +413,7 @@ async function buildCodexPluginThreadConfig(params) {
364
413
  request: params.request,
365
414
  appCache,
366
415
  appCacheKey: params.appCacheKey,
416
+ metadataCache: params.metadataCache,
367
417
  nowMs: params.nowMs,
368
418
  suppressAppInventoryRefresh: true
369
419
  }) : emptyCodexPluginInventory(policy);
@@ -380,6 +430,7 @@ async function buildCodexPluginThreadConfig(params) {
380
430
  request: params.request,
381
431
  appCache,
382
432
  appCacheKey: params.appCacheKey,
433
+ metadataCache: params.metadataCache,
383
434
  nowMs: params.nowMs
384
435
  });
385
436
  inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
@@ -396,6 +447,7 @@ async function buildCodexPluginThreadConfig(params) {
396
447
  request: params.request,
397
448
  appCache,
398
449
  appCacheKey: params.appCacheKey,
450
+ metadataCache: params.metadataCache,
399
451
  targetAppIds: record.ownedAppIds
400
452
  });
401
453
  activationResults.push(activation);
@@ -419,6 +471,7 @@ async function buildCodexPluginThreadConfig(params) {
419
471
  request: params.request,
420
472
  appCache,
421
473
  appCacheKey: params.appCacheKey,
474
+ metadataCache: params.metadataCache,
422
475
  nowMs: params.nowMs
423
476
  });
424
477
  inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
@@ -438,6 +491,7 @@ async function buildCodexPluginThreadConfig(params) {
438
491
  request: params.request,
439
492
  appCache,
440
493
  appCacheKey: params.appCacheKey,
494
+ metadataCache: params.metadataCache,
441
495
  nowMs: params.nowMs
442
496
  });
443
497
  inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
@@ -755,129 +809,6 @@ function stableStringify(value) {
755
809
  return JSON.stringify(value);
756
810
  }
757
811
  //#endregion
758
- //#region extensions/codex/src/app-server/attempt-client-cleanup.ts
759
- /**
760
- * Best-effort cleanup helpers for Codex app-server startup attempts and turns.
761
- */
762
- /** Timeout for best-effort app-server turn interruption during cleanup. */
763
- const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
764
- /** Timeout for best-effort thread unsubscribe during cleanup. */
765
- const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
766
- /** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
767
- var CodexAppServerUnsafeSubscriptionError = class extends Error {
768
- constructor(message, options) {
769
- super(message, options);
770
- this.name = "CodexAppServerUnsafeSubscriptionError";
771
- }
772
- };
773
- function isCodexAppServerUnsafeSubscriptionError(error) {
774
- return error instanceof CodexAppServerUnsafeSubscriptionError;
775
- }
776
- /** Asserts Codex resumed the exact thread this attempt subscribed to. */
777
- function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
778
- if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
779
- }
780
- async function closeClientAndWaitIfAvailable(client) {
781
- const closeable = client;
782
- if (typeof closeable.closeAndWait === "function") {
783
- await closeable.closeAndWait();
784
- return;
785
- }
786
- closeable.close?.();
787
- }
788
- async function closeCodexStartupClientBestEffort(client) {
789
- if (!client) return;
790
- const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
791
- if (unclaimedSharedClient.closed) {
792
- await closeClientAndWaitIfAvailable(client);
793
- return;
794
- }
795
- if (unclaimedSharedClient.found) {
796
- if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
797
- return;
798
- }
799
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
800
- if (retiredSharedClient) {
801
- if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
802
- return;
803
- }
804
- if (clearSharedCodexAppServerClientIfCurrent(client)) {
805
- await closeClientAndWaitIfAvailable(client);
806
- return;
807
- }
808
- await closeClientAndWaitIfAvailable(client);
809
- }
810
- /** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
811
- function interruptCodexTurnBestEffort(client, params) {
812
- const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
813
- const requestParams = {
814
- threadId: params.threadId,
815
- turnId: params.turnId
816
- };
817
- try {
818
- const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
819
- Promise.resolve(interrupt).catch((error) => {
820
- embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
821
- });
822
- } catch (error) {
823
- embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
824
- }
825
- }
826
- /** Unsubscribes from a thread while swallowing cleanup-only failures. */
827
- async function unsubscribeCodexThreadBestEffort(client, params) {
828
- try {
829
- await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
830
- return true;
831
- } catch (error) {
832
- embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
833
- threadId: params.threadId,
834
- error
835
- });
836
- return false;
837
- }
838
- }
839
- /**
840
- * Retires the shared client after a timed-out turn so later runs do not reuse a
841
- * potentially wedged app-server connection.
842
- */
843
- async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
844
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
845
- const detachedSharedClient = Boolean(retiredSharedClient);
846
- if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
847
- interruptCodexTurnBestEffort(client, {
848
- threadId: params.threadId,
849
- turnId: params.turnId,
850
- timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
851
- });
852
- await unsubscribeCodexThreadBestEffort(client, {
853
- threadId: params.threadId,
854
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
855
- });
856
- }
857
- let closedClient = retiredSharedClient?.closed ?? false;
858
- if (!detachedSharedClient) {
859
- const close = client.close;
860
- if (typeof close === "function") try {
861
- close.call(client);
862
- closedClient = true;
863
- } catch (error) {
864
- embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
865
- threadId: params.threadId,
866
- turnId: params.turnId,
867
- error
868
- });
869
- }
870
- }
871
- embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
872
- threadId: params.threadId,
873
- turnId: params.turnId,
874
- reason: params.reason,
875
- detachedSharedClient,
876
- closedClient,
877
- activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
878
- });
879
- }
880
- //#endregion
881
812
  //#region extensions/codex/src/app-server/context-engine-projection.ts
882
813
  const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
883
814
  const CONTEXT_OPEN = "<conversation_context>";
@@ -1137,961 +1068,1332 @@ function truncateOlderContext(text, maxChars) {
1137
1068
  return `${marker}${sliceUtf16Safe(text, -tailChars).trimStart()}`;
1138
1069
  }
1139
1070
  //#endregion
1140
- //#region extensions/codex/src/app-server/dynamic-tool-profile.ts
1141
- /** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
1142
- const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
1143
- "read",
1144
- "write",
1145
- "edit",
1146
- "apply_patch",
1147
- "exec",
1148
- "process",
1149
- "update_plan",
1150
- "tool_call",
1151
- "tool_describe",
1152
- "tool_search",
1153
- "tool_search_code"
1154
- ];
1155
- const DYNAMIC_TOOL_NAME_ALIASES = {
1156
- bash: "exec",
1157
- "apply-patch": "apply_patch"
1158
- };
1159
- /** Normalizes OpenClaw/Codex tool names before filtering and allowlist checks. */
1160
- function normalizeCodexDynamicToolName(name) {
1161
- const normalized = name.trim().toLowerCase();
1162
- return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
1071
+ //#region extensions/codex/src/app-server/thread-context-engine.ts
1072
+ function buildContextEngineBinding(params, projection) {
1073
+ const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
1074
+ const engineId = contextEngine?.info?.id?.trim();
1075
+ if (!contextEngine || !engineId) return;
1076
+ return {
1077
+ schemaVersion: 1,
1078
+ engineId,
1079
+ policyFingerprint: JSON.stringify({
1080
+ schemaVersion: 1,
1081
+ engineId,
1082
+ engineVersion: contextEngine.info.version,
1083
+ ownsCompaction: contextEngine.info.ownsCompaction === true,
1084
+ turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
1085
+ citationsMode: resolveContextEngineCitationsMode(params.config),
1086
+ contextTokenBudget: params.contextTokenBudget,
1087
+ projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
1088
+ contextTokenBudget: params.contextTokenBudget,
1089
+ reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
1090
+ })
1091
+ }),
1092
+ projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
1093
+ };
1163
1094
  }
1164
- /** True only for the host-scoped OpenClaw run's exact tool contract. */
1165
- function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
1166
- return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
1095
+ function buildContextEngineProjectionBinding(projection) {
1096
+ return {
1097
+ schemaVersion: 1,
1098
+ mode: "thread_bootstrap",
1099
+ epoch: projection.epoch,
1100
+ fingerprint: projection.fingerprint
1101
+ };
1167
1102
  }
1168
- /** Returns true for private QA runs that force the Codex runtime profile. */
1169
- function isForcedPrivateQaCodexRuntime(env = process.env) {
1170
- return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
1103
+ function isContextEngineBindingCompatible(previous, next) {
1104
+ return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
1171
1105
  }
1172
- /** Resolves whether dynamic tools load directly or through Codex tool search. */
1173
- function resolveCodexDynamicToolsLoading(config, env = process.env) {
1174
- return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
1106
+ function areContextEngineProjectionBindingsCompatible(previous, next) {
1107
+ if (!next) return previous === void 0;
1108
+ return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
1175
1109
  }
1176
- function normalizeCodexModelId(modelId) {
1177
- const normalized = modelId?.trim().toLowerCase();
1178
- if (!normalized) return "";
1179
- return normalized.includes("/") ? normalized.split("/").at(-1) : normalized;
1110
+ function resolveContextEngineCitationsMode(config) {
1111
+ const rootConfig = isUnknownRecord(config) ? config : void 0;
1112
+ const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
1113
+ return isJsonConfigValue(citations) ? citations : void 0;
1180
1114
  }
1181
- /** Returns true when model behavior requires direct dynamic-tool registration. */
1182
- function shouldUseDirectCodexDynamicToolsForModel(modelId) {
1183
- return shouldDisableCodexToolSearchForModel(modelId);
1115
+ function isUnknownRecord(value) {
1116
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1184
1117
  }
1185
- /** Returns true for models whose tool-search path is unsupported or inefficient. */
1186
- function shouldDisableCodexToolSearchForModel(modelId) {
1187
- return normalizeCodexModelId(modelId) === "gpt-5.4-nano";
1118
+ function isJsonConfigValue(value) {
1119
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1120
+ if (typeof value === "number") return Number.isFinite(value);
1121
+ if (Array.isArray(value)) return value.every(isJsonConfigValue);
1122
+ return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
1188
1123
  }
1189
- /** Resolves dynamic-tool loading after applying model-specific restrictions. */
1190
- function resolveCodexDynamicToolsLoadingForModel(config, modelId, env = process.env) {
1191
- const loading = resolveCodexDynamicToolsLoading(config, env);
1192
- return loading === "searchable" && shouldUseDirectCodexDynamicToolsForModel(modelId) ? "direct" : loading;
1124
+ //#endregion
1125
+ //#region extensions/codex/src/app-server/thread-fingerprints.ts
1126
+ function codexDynamicToolsFingerprint(dynamicTools) {
1127
+ return fingerprintDynamicTools(dynamicTools);
1193
1128
  }
1194
- /** Resolves dynamic-tool loading for the app-server connection that will execute the turn. */
1195
- function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}, env = process.env) {
1196
- const loading = resolveCodexDynamicToolsLoadingForModel(config, modelId, env);
1197
- return loading === "searchable" && options.connectionClass === "remote" ? "direct" : loading;
1129
+ function codexLegacyDynamicToolsFingerprint(dynamicTools) {
1130
+ return legacyFingerprintDynamicTools(dynamicTools);
1198
1131
  }
1199
- /** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
1200
- function filterCodexDynamicTools(tools, config, env = process.env) {
1201
- const excludes = /* @__PURE__ */ new Set();
1202
- if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) excludes.add(name);
1203
- for (const name of config.codexDynamicToolsExclude ?? []) {
1204
- const trimmed = normalizeCodexDynamicToolName(name);
1205
- if (trimmed) excludes.add(trimmed);
1206
- }
1207
- return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
1132
+ function areCodexDynamicToolFingerprintsCompatible(params) {
1133
+ return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
1208
1134
  }
1209
- //#endregion
1210
- //#region extensions/codex/src/app-server/image-payload-sanitizer.ts
1211
- /**
1212
- * Sanitizes inline image payloads mirrored through Codex history so invalid
1213
- * base64 data becomes readable text instead of poisoning replayed transcripts.
1214
- */
1215
- const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
1216
- /** Validates and normalizes an inline image data URL for Codex history payloads. */
1217
- function sanitizeInlineImageDataUrl$1(imageUrl) {
1218
- return sanitizeInlineImageDataUrl(imageUrl);
1135
+ function fingerprintDynamicTools(dynamicTools) {
1136
+ return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
1219
1137
  }
1220
- /** Builds the replacement text inserted when an inline image payload is invalid. */
1221
- function invalidInlineImageText(label) {
1222
- return `[${label}] ${IMAGE_OMITTED_TEXT}`;
1138
+ function legacyFingerprintDynamicTools(dynamicTools) {
1139
+ return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
1223
1140
  }
1224
- function sanitizeImageContentRecord(record, label) {
1225
- if (record.type === "image" && typeof record.data === "string") {
1226
- const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
1227
- const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
1228
- if (!imageUrl) return {
1229
- type: "text",
1230
- text: invalidInlineImageText(label)
1231
- };
1232
- const commaIndex = imageUrl.indexOf(",");
1233
- const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
1234
- return {
1235
- ...record,
1236
- mimeType: mime,
1237
- data: imageUrl.slice(commaIndex + 1)
1238
- };
1239
- }
1240
- if (record.type === "inputImage" && typeof record.imageUrl === "string") {
1241
- const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
1242
- return imageUrl ? {
1243
- ...record,
1244
- imageUrl
1245
- } : {
1246
- type: "inputText",
1247
- text: invalidInlineImageText(label)
1248
- };
1249
- }
1250
- if (record.type === "input_image" && typeof record.image_url === "string") {
1251
- const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
1252
- return imageUrl ? {
1253
- ...record,
1254
- image_url: imageUrl
1255
- } : {
1256
- type: "input_text",
1257
- text: invalidInlineImageText(label)
1258
- };
1259
- }
1141
+ function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
1142
+ return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
1260
1143
  }
1261
- /** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
1262
- function sanitizeCodexHistoryImagePayloads(value, label) {
1263
- if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
1264
- if (!isRecord(value)) return value;
1265
- const imageRecord = sanitizeImageContentRecord(value, label);
1266
- if (imageRecord) return imageRecord;
1144
+ function fingerprintUserMcpServersConfigPatch(configPatch) {
1145
+ return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
1146
+ }
1147
+ function redactUserMcpServersFingerprintSecrets(value) {
1148
+ if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
1149
+ if (!value || typeof value !== "object") return value;
1267
1150
  const next = {};
1268
- for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
1151
+ for (const [key, entry] of Object.entries(value)) {
1152
+ if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
1153
+ next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
1154
+ continue;
1155
+ }
1156
+ next[key] = redactUserMcpServersFingerprintSecrets(entry);
1157
+ }
1269
1158
  return next;
1270
1159
  }
1271
- //#endregion
1272
- //#region extensions/codex/src/app-server/profiler-flag.ts
1273
- const PROFILER_FLAGS = ["profiler", "codex.profiler"];
1274
- /** Checks the generic and Codex-specific profiler diagnostic flags. */
1275
- function isCodexAppServerProfilerEnabled(config, env = process.env) {
1276
- return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
1160
+ function fingerprintUserMcpServersAuthorizationHeader(value) {
1161
+ return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
1277
1162
  }
1278
- //#endregion
1279
- //#region extensions/codex/src/app-server/thread-resume.ts
1280
- /** Owns Codex thread/resume subscription safety. */
1281
- /** Resumes one thread and retires the physical client when acceptance is indeterminate. */
1282
- async function resumeCodexAppServerThread(params) {
1283
- const threadId = params.request.threadId;
1284
- let response;
1285
- try {
1286
- response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
1287
- ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
1288
- ...params.signal ? { signal: params.signal } : {}
1289
- }));
1290
- assertCodexThreadResumeSubscription(threadId, response.thread.id);
1291
- } catch (error) {
1292
- if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
1293
- if (error instanceof CodexAppServerRpcError) throw error;
1294
- try {
1295
- await params.abandonClient();
1296
- } catch (abandonError) {
1297
- throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
1298
- }
1299
- if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
1300
- throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
1163
+ function fingerprintJsonObject(value) {
1164
+ return JSON.stringify(stabilizeJsonValue(value));
1165
+ }
1166
+ function fingerprintEnvironmentSelection(environments) {
1167
+ return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
1168
+ }
1169
+ function fingerprintDynamicToolSpec(tool) {
1170
+ return stabilizeDynamicToolFingerprintValue(tool);
1171
+ }
1172
+ function stabilizeDynamicToolFingerprintValue(value) {
1173
+ if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
1174
+ if (!isJsonObject(value)) return value;
1175
+ const stable = {};
1176
+ for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
1177
+ if (key === "description") continue;
1178
+ stable[key] = stabilizeDynamicToolFingerprintValue(child);
1301
1179
  }
1302
- return response;
1180
+ return stable;
1303
1181
  }
1304
- //#endregion
1305
- //#region extensions/codex/src/app-server/web-search.ts
1306
- const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG = {
1307
- "features.standalone_web_search": false,
1308
- web_search: "disabled"
1309
- };
1310
- function normalizeOptionalString$1(value) {
1311
- return typeof value === "string" ? value.trim() || void 0 : void 0;
1182
+ function stabilizeJsonValue(value) {
1183
+ if (Array.isArray(value)) return value.map(stabilizeJsonValue);
1184
+ if (!isJsonObject(value)) return value;
1185
+ const stable = {};
1186
+ for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
1187
+ return stable;
1312
1188
  }
1313
- function normalizeUniqueStrings(value) {
1314
- if (!Array.isArray(value)) return;
1315
- const normalized = [...new Set(value.map(normalizeOptionalString$1).filter((entry) => Boolean(entry)))];
1316
- return normalized.length > 0 ? normalized : void 0;
1189
+ function readActiveCodexTurnIds(thread) {
1190
+ return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
1317
1191
  }
1318
- function hasManagedSearchProvider(config) {
1319
- return normalizeOptionalString$1(config?.tools?.web?.search?.provider) !== void 0;
1192
+ const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
1193
+ const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
1194
+ function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
1195
+ return !previous || previous === next || previous === nextLegacy;
1320
1196
  }
1321
- function hasNativeDomainRestrictions(config) {
1322
- return normalizeUniqueStrings(config?.tools?.web?.search?.openaiCodex?.allowedDomains) !== void 0;
1197
+ function areUserMcpServersFingerprintsCompatible(params) {
1198
+ return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
1323
1199
  }
1324
- function buildCodexNativeWebSearchThreadConfig(config) {
1325
- const nativeConfig = config?.tools?.web?.search?.openaiCodex;
1326
- const threadConfig = {
1327
- "features.standalone_web_search": false,
1328
- web_search: nativeConfig?.mode === "live" ? "live" : "cached"
1329
- };
1330
- const allowedDomains = normalizeUniqueStrings(nativeConfig?.allowedDomains);
1331
- if (allowedDomains) threadConfig["tools.web_search.allowed_domains"] = allowedDomains;
1332
- if (nativeConfig?.contextSize) threadConfig["tools.web_search.context_size"] = nativeConfig.contextSize;
1333
- const location = nativeConfig?.userLocation;
1334
- const country = normalizeOptionalString$1(location?.country);
1335
- const region = normalizeOptionalString$1(location?.region);
1336
- const city = normalizeOptionalString$1(location?.city);
1337
- const timezone = normalizeOptionalString$1(location?.timezone);
1338
- if (country) threadConfig["tools.web_search.location.country"] = country;
1339
- if (region) threadConfig["tools.web_search.location.region"] = region;
1340
- if (city) threadConfig["tools.web_search.location.city"] = city;
1341
- if (timezone) threadConfig["tools.web_search.location.timezone"] = timezone;
1342
- return threadConfig;
1200
+ function shouldStartTransientNoToolThread(params) {
1201
+ return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
1343
1202
  }
1344
- function resolveCodexWebSearchPlan(params) {
1345
- if (params.disableTools === true || params.webSearchAllowed === false || params.config?.tools?.web?.search?.enabled === false) return {
1346
- kind: "disabled",
1347
- suppressManagedWebSearch: true,
1348
- threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
1349
- };
1350
- const nativeConfig = params.config?.tools?.web?.search?.openaiCodex;
1351
- const managedSearchExplicit = hasManagedSearchProvider(params.config) || nativeConfig?.enabled === false;
1352
- const nativeProviderSupportsSearch = params.nativeProviderWebSearchSupport === void 0 || params.nativeProviderWebSearchSupport === "supported";
1353
- if (!(params.nativeToolSurfaceEnabled !== false && nativeProviderSupportsSearch && nativeConfig?.enabled !== false && !hasManagedSearchProvider(params.config))) {
1354
- if (!managedSearchExplicit && hasNativeDomainRestrictions(params.config)) return {
1355
- kind: "disabled",
1356
- suppressManagedWebSearch: true,
1357
- threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
1358
- };
1359
- return {
1360
- kind: "managed",
1361
- suppressManagedWebSearch: false,
1362
- threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
1363
- };
1364
- }
1365
- return {
1366
- kind: "native-hosted",
1367
- suppressManagedWebSearch: true,
1368
- threadConfig: buildCodexNativeWebSearchThreadConfig(params.config)
1369
- };
1203
+ function isEmptyDynamicToolsFingerprint(fingerprint) {
1204
+ return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
1205
+ }
1206
+ function compareJsonFingerprint(left, right) {
1207
+ return JSON.stringify(left).localeCompare(JSON.stringify(right));
1370
1208
  }
1371
1209
  //#endregion
1372
- //#region extensions/codex/src/app-server/thread-lifecycle.ts
1373
- var CodexThreadStartRequestError = class extends Error {
1374
- constructor(cause) {
1375
- super(formatErrorMessage(cause), { cause });
1376
- this.name = "CodexThreadStartRequestError";
1210
+ //#region extensions/codex/src/app-server/attempt-client-cleanup.ts
1211
+ /**
1212
+ * Best-effort cleanup helpers for Codex app-server startup attempts and turns.
1213
+ */
1214
+ /** Timeout for best-effort app-server turn interruption during cleanup. */
1215
+ const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
1216
+ /** Timeout for best-effort thread unsubscribe during cleanup. */
1217
+ const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
1218
+ /** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
1219
+ var CodexAppServerUnsafeSubscriptionError = class extends Error {
1220
+ constructor(message, options) {
1221
+ super(message, options);
1222
+ this.name = "CodexAppServerUnsafeSubscriptionError";
1377
1223
  }
1378
1224
  };
1379
- var CodexThreadBindingConflictError = class extends Error {
1380
- constructor(threadId, operation) {
1381
- super(`Codex thread binding changed while ${operation}: ${threadId}`);
1382
- this.name = "CodexThreadBindingConflictError";
1225
+ function isCodexAppServerUnsafeSubscriptionError(error) {
1226
+ return error instanceof CodexAppServerUnsafeSubscriptionError;
1227
+ }
1228
+ /** Asserts Codex resumed the exact thread this attempt subscribed to. */
1229
+ function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
1230
+ if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
1231
+ }
1232
+ async function closeClientAndWaitIfAvailable(client) {
1233
+ const closeable = client;
1234
+ if (typeof closeable.closeAndWait === "function") {
1235
+ await closeable.closeAndWait();
1236
+ return;
1383
1237
  }
1384
- };
1385
- var CodexRingZeroAttestationError = class extends Error {
1386
- constructor(cause) {
1387
- super("Codex ring-zero MCP attestation failed", { cause });
1388
- this.name = "CodexRingZeroAttestationError";
1238
+ closeable.close?.();
1239
+ }
1240
+ async function closeCodexStartupClientBestEffort(client) {
1241
+ if (!client) return;
1242
+ const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
1243
+ if (unclaimedSharedClient.closed) {
1244
+ await closeClientAndWaitIfAvailable(client);
1245
+ return;
1389
1246
  }
1390
- };
1391
- var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
1392
- var CodexAdoptedThreadActiveError = class extends Error {
1393
- constructor() {
1394
- super("Codex session became active in another runner; wait for it to finish before continuing");
1395
- this.name = "CodexAdoptedThreadActiveError";
1247
+ if (unclaimedSharedClient.found) {
1248
+ if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
1249
+ return;
1396
1250
  }
1397
- };
1398
- const CODEX_NATIVE_PERSONALITY_NONE = "none";
1399
- const CODEX_RING_ZERO_BASE_INSTRUCTIONS = "";
1400
- const CODEX_CODE_MODE_THREAD_CONFIG = {
1401
- "features.code_mode": true,
1402
- "features.code_mode_only": false,
1403
- "features.apply_patch_streaming_events": true
1404
- };
1405
- const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
1406
- "features.code_mode": false,
1407
- "features.code_mode_only": false
1408
- };
1409
- const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
1410
- const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
1411
- const CODEX_RING_ZERO_THREAD_CONFIG = {
1412
- "features.apps": false,
1413
- "features.current_time_reminder": false,
1414
- "features.deferred_executor": false,
1415
- "features.enable_fanout": false,
1416
- "features.goals": false,
1417
- "features.hooks": false,
1418
- "features.image_generation": false,
1419
- "features.memories": false,
1420
- "features.multi_agent": false,
1421
- "features.multi_agent_v2": false,
1422
- "features.plugins": false,
1423
- "features.standalone_web_search": false,
1424
- "features.token_budget": false,
1425
- "orchestrator.mcp.enabled": false,
1426
- "orchestrator.skills.enabled": false,
1427
- "tools.experimental_request_user_input.enabled": false,
1428
- hooks: {
1429
- PreToolUse: [],
1430
- PermissionRequest: [],
1431
- PostToolUse: [],
1432
- PreCompact: [],
1433
- PostCompact: [],
1434
- SessionStart: [],
1435
- UserPromptSubmit: [],
1436
- SubagentStart: [],
1437
- SubagentStop: [],
1438
- Stop: []
1439
- },
1440
- project_doc_max_bytes: 0,
1441
- notify: [],
1442
- web_search: "disabled"
1443
- };
1444
- const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
1445
- "apps",
1446
- "code_mode",
1447
- "code_mode_only",
1448
- "current_time_reminder",
1449
- "deferred_executor",
1450
- "enable_fanout",
1451
- "goals",
1452
- "hooks",
1453
- "image_generation",
1454
- "memories",
1455
- "multi_agent",
1456
- "multi_agent_v2",
1457
- "plugins",
1458
- "standalone_web_search",
1459
- "token_budget"
1460
- ]);
1461
- const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
1462
- "mdm",
1463
- "system",
1464
- "enterpriseManaged",
1465
- "user",
1466
- "project",
1467
- "sessionFlags"
1468
- ]);
1469
- const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
1470
- const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
1471
- function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
1472
- const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
1473
- const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
1474
- return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
1475
- }
1476
- function formatCodexThreadLifecycleTimingSummary(params) {
1477
- const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
1478
- 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}`;
1251
+ const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
1252
+ if (retiredSharedClient) {
1253
+ if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
1254
+ return;
1255
+ }
1256
+ if (clearSharedCodexAppServerClientIfCurrent(client)) {
1257
+ await closeClientAndWaitIfAvailable(client);
1258
+ return;
1259
+ }
1260
+ await closeClientAndWaitIfAvailable(client);
1479
1261
  }
1480
- function createCodexThreadLifecycleTimingTracker(options = {}) {
1481
- const log = options.log ?? embeddedAgentLog;
1482
- if (!options.enabled && log.isEnabled?.("trace") !== true) return {
1483
- async measure(_name, run) {
1484
- return await run();
1485
- },
1486
- measureSync(_name, run) {
1487
- return run();
1488
- },
1489
- mark() {},
1490
- logSummary() {}
1262
+ /** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
1263
+ function interruptCodexTurnBestEffort(client, params) {
1264
+ const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
1265
+ const requestParams = {
1266
+ threadId: params.threadId,
1267
+ turnId: params.turnId
1491
1268
  };
1492
- const now = options.now ?? Date.now;
1493
- const startedAt = now();
1494
- let didLog = false;
1495
- const spans = [];
1496
- const toMs = (value) => Math.max(0, Math.round(value));
1497
- const record = (name, spanStartedAt) => {
1498
- const currentAt = now();
1499
- spans.push({
1500
- name,
1501
- durationMs: toMs(currentAt - spanStartedAt),
1502
- elapsedMs: toMs(currentAt - startedAt)
1269
+ try {
1270
+ const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
1271
+ Promise.resolve(interrupt).catch((error) => {
1272
+ embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
1503
1273
  });
1504
- };
1505
- const snapshot = () => ({
1506
- totalMs: toMs(now() - startedAt),
1507
- spans: spans.slice()
1508
- });
1509
- return {
1510
- async measure(name, run) {
1511
- const spanStartedAt = now();
1512
- try {
1513
- return await run();
1514
- } finally {
1515
- record(name, spanStartedAt);
1516
- }
1517
- },
1518
- measureSync(name, run) {
1519
- const spanStartedAt = now();
1520
- try {
1521
- return run();
1522
- } finally {
1523
- record(name, spanStartedAt);
1524
- }
1525
- },
1526
- mark(name) {
1527
- record(name, now());
1528
- },
1529
- logSummary(params) {
1530
- if (didLog) return;
1531
- const summary = snapshot();
1532
- const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
1533
- if (!shouldWarn && !log.isEnabled?.("trace")) return;
1534
- didLog = true;
1535
- const message = formatCodexThreadLifecycleTimingSummary({
1536
- runId: params.runId,
1537
- sessionId: params.sessionId,
1538
- sessionKey: params.sessionKey,
1539
- action: params.action,
1540
- summary
1541
- });
1542
- const meta = {
1543
- runId: params.runId,
1544
- sessionId: params.sessionId,
1545
- sessionKey: params.sessionKey,
1546
- action: params.action,
1547
- threadId: params.threadId,
1548
- totalMs: summary.totalMs,
1549
- spans: summary.spans
1550
- };
1551
- if (shouldWarn) log.warn(message, meta);
1552
- else log.trace(message, meta);
1553
- }
1554
- };
1274
+ } catch (error) {
1275
+ embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
1276
+ }
1555
1277
  }
1556
- async function startOrResumeThread(params) {
1557
- const bindingIdentity = sessionBindingIdentity({
1558
- sessionId: params.params.sessionId,
1559
- sessionKey: params.params.sessionKey,
1560
- agentId: params.agentId ?? params.params.agentId,
1561
- config: params.params.config
1562
- });
1563
- return await params.bindingStore.withLease(bindingIdentity, async () => {
1564
- const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
1565
- ...params.timing,
1566
- enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
1278
+ /** Unsubscribes from a thread while swallowing cleanup-only failures. */
1279
+ async function unsubscribeCodexThreadBestEffort(client, params) {
1280
+ try {
1281
+ await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
1282
+ return true;
1283
+ } catch (error) {
1284
+ embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
1285
+ threadId: params.threadId,
1286
+ error
1567
1287
  });
1568
- const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => legacyFingerprintDynamicTools(params.dynamicTools));
1569
- const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
1570
- const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
1571
- const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
1572
- config: params.params.config,
1573
- disableTools: params.params.disableTools,
1574
- nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
1575
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1576
- webSearchAllowed: params.webSearchAllowed
1577
- })).threadConfig);
1578
- const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
1579
- const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
1580
- const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
1581
- agentId: params.agentId ?? params.params.agentId,
1582
- agentDir: params.params.agentDir,
1583
- allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
1584
- onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
1585
- serverName,
1586
- error: formatErrorMessage(error)
1587
- })
1288
+ return false;
1289
+ }
1290
+ }
1291
+ /**
1292
+ * Retires the shared client after a timed-out turn so later runs do not reuse a
1293
+ * potentially wedged app-server connection.
1294
+ */
1295
+ async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
1296
+ const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
1297
+ const detachedSharedClient = Boolean(retiredSharedClient);
1298
+ if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
1299
+ interruptCodexTurnBestEffort(client, {
1300
+ threadId: params.threadId,
1301
+ turnId: params.turnId,
1302
+ timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
1588
1303
  });
1589
- const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
1590
- const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
1591
- const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
1592
- const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
1593
- const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
1594
- if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
1595
- const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
1596
- if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
1597
- const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
1598
- version: 1,
1599
- baseInstructions: CODEX_RING_ZERO_BASE_INSTRUCTIONS,
1600
- config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
1601
- }) : void 0;
1602
- const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
1603
- let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
1604
- const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
1605
- authProfileId,
1606
- modelProvider,
1607
- authProfileStore: params.params.authProfileStore,
1608
- agentDir: params.params.agentDir,
1609
- config: params.params.config
1304
+ await unsubscribeCodexThreadBestEffort(client, {
1305
+ threadId: params.threadId,
1306
+ timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
1610
1307
  });
1611
- const throwIfAborted = () => {
1612
- if (!params.signal?.aborted) return;
1613
- const reason = params.signal.reason;
1614
- if (reason instanceof Error) throw reason;
1615
- const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
1616
- error.name = "AbortError";
1617
- throw error;
1618
- };
1619
- if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
1620
- if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
1621
- bindingStore: params.bindingStore,
1622
- identity: bindingIdentity,
1623
- config: params.params.config
1624
- }))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
1625
- }
1626
- if (binding?.pendingSupervisionBranch) {
1627
- const pendingBinding = binding;
1628
- const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
1629
- const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
1630
- configPatch: params.finalConfigPatch,
1631
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
1632
- };
1633
- const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
1634
- return await materializePendingSupervisionBranch({
1635
- client: params.client,
1636
- abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
1637
- bindingStore: params.bindingStore,
1638
- bindingIdentity,
1639
- binding: pendingBinding,
1640
- attempt: params.params,
1641
- cwd: params.cwd,
1642
- dynamicTools: params.dynamicTools,
1643
- appServer: params.appServer,
1644
- developerInstructions: params.developerInstructions,
1645
- config,
1646
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1647
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1648
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1649
- webSearchAllowed: params.webSearchAllowed,
1650
- environmentSelection: params.environmentSelection,
1651
- signal: params.signal,
1652
- throwIfAborted,
1653
- lifecycleTiming,
1654
- normalizeBindingModelProvider,
1655
- bindingPatch: {
1656
- cwd: params.cwd,
1657
- authProfileId: void 0,
1658
- preserveNativeModel: true,
1659
- dynamicToolsFingerprint,
1660
- dynamicToolsContainDeferred,
1661
- webSearchThreadConfigFingerprint,
1662
- userMcpServersFingerprint,
1663
- mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
1664
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
1665
- networkProxyConfigFingerprint,
1666
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
1667
- appServerRuntimeFingerprint: buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir),
1668
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
1669
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
1670
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
1671
- contextEngine: contextEngineBinding,
1672
- environmentSelectionFingerprint,
1673
- conversationSourceTransferComplete: true
1674
- }
1675
- });
1676
- }
1677
- const clearCurrentBinding = async (operation) => {
1678
- const current = binding;
1679
- if (!current?.threadId) return;
1680
- assertCodexBindingMayBeReplaced(current, operation);
1681
- if (!await params.bindingStore.mutate(bindingIdentity, {
1682
- kind: "clear",
1683
- threadId: current.threadId
1684
- })) throw new CodexThreadBindingConflictError(current.threadId, operation);
1685
- binding = void 0;
1686
- };
1687
- if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
1688
- embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
1689
- await clearCurrentBinding("rotating a ring-zero thread binding");
1690
- }
1691
- if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
1692
- connectionClass: params.appServer.connectionClass,
1693
- current: binding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
1694
- binding: binding.appServerRuntimeFingerprint
1695
- })) {
1696
- embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
1697
- threadId: binding.threadId,
1698
- connectionClass: params.appServer.connectionClass
1699
- });
1700
- await clearCurrentBinding("rotating a stale thread binding");
1701
- binding = void 0;
1702
- }
1703
- if (binding?.threadId && shouldRotateCodexGpt56MultiAgentBinding({
1704
- bindingModel: binding.model,
1705
- requestedModel: params.params.modelId
1706
- })) {
1707
- embeddedAgentLog.debug("codex app-server GPT-5.6 multi-agent version changed; starting a new thread", {
1708
- threadId: binding.threadId,
1709
- bindingModel: binding.model,
1710
- requestedModel: params.params.modelId
1711
- });
1712
- await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
1713
- binding = void 0;
1714
- }
1715
- const startModelSelection = resolveCodexAppServerThreadModelSelection({
1716
- provider: params.params.provider,
1717
- model: params.params.modelId,
1718
- binding,
1719
- authProfileId: params.params.authProfileId,
1720
- authProfileStore: params.params.authProfileStore,
1721
- agentDir: params.params.agentDir,
1722
- config: params.params.config
1723
- });
1724
- const startModelProvider = startModelSelection.modelProvider;
1725
- let preserveExistingBinding = !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
1726
- let rotatedContextEngineBinding = false;
1727
- let prebuiltPluginThreadConfig;
1728
- const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
1729
- const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
1730
- const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
1731
- const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
1732
- const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
1733
- const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
1734
- if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
1735
- assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
1736
- if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
1737
- embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
1738
- preserveExistingBinding = true;
1739
- } else {
1740
- embeddedAgentLog.debug("codex app-server MCP config changed; starting a new thread", { threadId: binding.threadId });
1741
- await clearCurrentBinding("rotating a stale thread binding");
1742
- }
1743
- binding = void 0;
1744
- }
1745
- const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
1746
- if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
1747
- assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
1748
- if (!ringZeroActive && transientWebSearchRestriction) {
1749
- embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
1750
- preserveExistingBinding = true;
1751
- } else {
1752
- embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
1753
- await clearCurrentBinding("rotating a stale thread binding");
1754
- }
1755
- binding = void 0;
1756
- }
1757
- if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
1758
- assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
1759
- embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
1760
- preserveExistingBinding = true;
1761
- binding = void 0;
1762
- }
1763
- if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
1764
- if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
1765
- embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
1766
- threadId: binding.threadId,
1767
- engineId: contextEngineBinding?.engineId,
1768
- previousEngineId: binding.contextEngine?.engineId,
1769
- epoch: contextEngineBinding?.projection?.epoch,
1770
- previousEpoch: binding.contextEngine?.projection?.epoch,
1771
- fingerprint: contextEngineBinding?.projection?.fingerprint,
1772
- previousFingerprint: binding.contextEngine?.projection?.fingerprint,
1773
- policyFingerprint: contextEngineBinding?.policyFingerprint,
1774
- previousPolicyFingerprint: binding.contextEngine?.policyFingerprint
1775
- });
1776
- await clearCurrentBinding("rotating a stale thread binding");
1777
- binding = void 0;
1778
- rotatedContextEngineBinding = true;
1779
- }
1780
- }
1781
- if (binding?.threadId && !areUserMcpServersFingerprintsCompatible({
1782
- previous: binding.userMcpServersFingerprint,
1783
- next: userMcpServersFingerprint,
1784
- nextLegacy: legacyUserMcpServersFingerprint
1785
- })) {
1786
- embeddedAgentLog.debug("codex app-server user MCP config changed; starting a new thread", { threadId: binding.threadId });
1787
- await clearCurrentBinding("rotating a stale thread binding");
1788
- binding = void 0;
1789
- }
1790
- if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
1791
- embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
1792
- await clearCurrentBinding("rotating a stale thread binding");
1793
- binding = void 0;
1794
- }
1795
- if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
1796
- embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
1797
- await clearCurrentBinding("rotating a stale thread binding");
1798
- binding = void 0;
1799
- }
1800
- if (binding?.threadId) {
1801
- let pluginBindingStale = isCodexPluginThreadBindingStale({
1802
- codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
1803
- bindingFingerprint: binding.pluginAppsFingerprint,
1804
- bindingInputFingerprint: binding.pluginAppsInputFingerprint,
1805
- currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
1806
- hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
1308
+ }
1309
+ let closedClient = retiredSharedClient?.closed ?? false;
1310
+ if (!detachedSharedClient) {
1311
+ const close = client.close;
1312
+ if (typeof close === "function") try {
1313
+ close.call(client);
1314
+ closedClient = true;
1315
+ } catch (error) {
1316
+ embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
1317
+ threadId: params.threadId,
1318
+ turnId: params.turnId,
1319
+ error
1807
1320
  });
1808
- if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
1809
- binding,
1810
- pluginThreadConfig: params.pluginThreadConfig
1811
- })) try {
1812
- prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build());
1813
- pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
1814
- } catch (error) {
1815
- embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
1816
- error,
1817
- threadId: binding.threadId
1818
- });
1819
- }
1820
- if (pluginBindingStale) {
1821
- embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
1822
- await clearCurrentBinding("rotating a stale thread binding");
1823
- binding = void 0;
1824
- }
1825
1321
  }
1826
- if (binding?.threadId) {
1827
- if (binding.dynamicToolsFingerprint && params.dynamicTools.length > 0 && binding.dynamicToolsContainDeferred !== dynamicToolsContainDeferred && (binding.dynamicToolsContainDeferred !== void 0 || !dynamicToolsContainDeferred)) {
1828
- embeddedAgentLog.debug("codex app-server dynamic tool loading changed; starting a new thread", { threadId: binding.threadId });
1829
- await clearCurrentBinding("rotating a stale thread binding");
1830
- binding = void 0;
1831
- }
1322
+ }
1323
+ embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
1324
+ threadId: params.threadId,
1325
+ turnId: params.turnId,
1326
+ reason: params.reason,
1327
+ detachedSharedClient,
1328
+ closedClient,
1329
+ activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
1330
+ });
1331
+ }
1332
+ //#endregion
1333
+ //#region extensions/codex/src/app-server/dynamic-tool-profile.ts
1334
+ /** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
1335
+ const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
1336
+ "read",
1337
+ "write",
1338
+ "edit",
1339
+ "apply_patch",
1340
+ "exec",
1341
+ "process",
1342
+ "update_plan",
1343
+ "tool_call",
1344
+ "tool_describe",
1345
+ "tool_search",
1346
+ "tool_search_code"
1347
+ ];
1348
+ const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
1349
+ const DYNAMIC_TOOL_NAME_ALIASES = {
1350
+ bash: "exec",
1351
+ "apply-patch": "apply_patch"
1352
+ };
1353
+ /** Normalizes OpenClaw/Codex tool names before filtering and allowlist checks. */
1354
+ function normalizeCodexDynamicToolName(name) {
1355
+ const normalized = name.trim().toLowerCase();
1356
+ return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
1357
+ }
1358
+ /** True only for the host-scoped OpenClaw run's exact tool contract. */
1359
+ function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
1360
+ return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
1361
+ }
1362
+ /** Returns true for private QA runs that force the Codex runtime profile. */
1363
+ function isForcedPrivateQaCodexRuntime(env = process.env) {
1364
+ return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
1365
+ }
1366
+ /** Resolves whether dynamic tools load directly or through Codex tool search. */
1367
+ function resolveCodexDynamicToolsLoading(config, env = process.env) {
1368
+ return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
1369
+ }
1370
+ function normalizeCodexModelId(modelId) {
1371
+ const normalized = modelId?.trim().toLowerCase();
1372
+ if (!normalized) return "";
1373
+ return normalized.includes("/") ? normalized.split("/").at(-1) : normalized;
1374
+ }
1375
+ /** Returns true when model behavior requires direct dynamic-tool registration. */
1376
+ function shouldUseDirectCodexDynamicToolsForModel(modelId) {
1377
+ return shouldDisableCodexToolSearchForModel(modelId);
1378
+ }
1379
+ /** Returns true for models whose tool-search path is unsupported or inefficient. */
1380
+ function shouldDisableCodexToolSearchForModel(modelId) {
1381
+ return normalizeCodexModelId(modelId) === "gpt-5.4-nano";
1382
+ }
1383
+ /** Resolves dynamic-tool loading after applying model-specific restrictions. */
1384
+ function resolveCodexDynamicToolsLoadingForModel(config, modelId, env = process.env) {
1385
+ const loading = resolveCodexDynamicToolsLoading(config, env);
1386
+ return loading === "searchable" && shouldUseDirectCodexDynamicToolsForModel(modelId) ? "direct" : loading;
1387
+ }
1388
+ /** Resolves dynamic-tool loading for the app-server connection that will execute the turn. */
1389
+ function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}, env = process.env) {
1390
+ const loading = resolveCodexDynamicToolsLoadingForModel(config, modelId, env);
1391
+ return loading === "searchable" && options.connectionClass === "remote" ? "direct" : loading;
1392
+ }
1393
+ /** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
1394
+ function filterCodexDynamicTools(tools, config, env = process.env) {
1395
+ return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: false });
1396
+ }
1397
+ /** Keeps exec/process only when Codex cannot advertise an environment-backed native shell. */
1398
+ function filterCodexDynamicToolsWithOpenClawShell(tools, config, env = process.env) {
1399
+ return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: true });
1400
+ }
1401
+ function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
1402
+ const excludes = /* @__PURE__ */ new Set();
1403
+ if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
1404
+ if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
1405
+ excludes.add(name);
1406
+ }
1407
+ for (const name of config.codexDynamicToolsExclude ?? []) {
1408
+ const trimmed = normalizeCodexDynamicToolName(name);
1409
+ if (trimmed) excludes.add(trimmed);
1410
+ }
1411
+ return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
1412
+ }
1413
+ //#endregion
1414
+ //#region extensions/codex/src/app-server/profiler-flag.ts
1415
+ const PROFILER_FLAGS = ["profiler", "codex.profiler"];
1416
+ /** Checks the generic and Codex-specific profiler diagnostic flags. */
1417
+ function isCodexAppServerProfilerEnabled(config, env = process.env) {
1418
+ return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
1419
+ }
1420
+ //#endregion
1421
+ //#region extensions/codex/src/app-server/thread-binding-policy.ts
1422
+ function shouldRotateCodexAppServerBindingForRuntime(params) {
1423
+ if (!params.current) return false;
1424
+ if (params.binding === params.current) return false;
1425
+ return params.connectionClass === "remote" || Boolean(params.binding);
1426
+ }
1427
+ function resolveCodexGpt56MultiAgentVersion(modelRef) {
1428
+ let modelId = modelRef?.trim().toLowerCase();
1429
+ if (!modelId) return;
1430
+ const slashIndex = modelId.indexOf("/");
1431
+ if (slashIndex > 0) {
1432
+ const provider = modelId.slice(0, slashIndex);
1433
+ if (provider !== "openai" && provider !== "codex") return;
1434
+ modelId = modelId.slice(slashIndex + 1);
1435
+ }
1436
+ if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
1437
+ return modelId === "gpt-5.6-luna" ? "v1" : void 0;
1438
+ }
1439
+ function shouldRotateCodexGpt56MultiAgentBinding(params) {
1440
+ const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
1441
+ const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
1442
+ return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
1443
+ }
1444
+ function isTransientWebSearchRestriction(params) {
1445
+ if (params.nativeProviderWebSearchSupport === "unknown") return true;
1446
+ if (params.params.config?.tools?.web?.search?.enabled === false) return false;
1447
+ if (params.params.disableTools === true) return true;
1448
+ const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
1449
+ if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
1450
+ if (params.webSearchAllowed !== false) return false;
1451
+ if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
1452
+ if (params.params.toolsAllow === void 0) return false;
1453
+ return !params.params.toolsAllow.some((name) => {
1454
+ const normalized = normalizeCodexDynamicToolName(name);
1455
+ return normalized === "*" || normalized === "web_search";
1456
+ });
1457
+ }
1458
+ function shouldRecheckRecoverablePluginBinding(params) {
1459
+ if (!params.pluginThreadConfig?.enabled) return false;
1460
+ if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
1461
+ const policyContext = params.binding.pluginAppPolicyContext;
1462
+ if (!policyContext) return false;
1463
+ const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
1464
+ const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
1465
+ const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
1466
+ const bindingContainsSettledPlugin = enabledPluginConfigKeys.filter((configKey) => !recoverablePluginConfigKeySet.has(configKey)).some((configKey) => (policyContext.pluginAppIds[configKey]?.length ?? 0) > 0 || Object.values(policyContext.apps).some((app) => app.source !== "account" && app.configKey === configKey));
1467
+ const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
1468
+ return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
1469
+ }
1470
+ //#endregion
1471
+ //#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
1472
+ var CodexThreadStartRequestError = class extends Error {
1473
+ constructor(cause) {
1474
+ super(formatErrorMessage(cause), { cause });
1475
+ this.name = "CodexThreadStartRequestError";
1476
+ }
1477
+ };
1478
+ var CodexThreadBindingConflictError = class extends Error {
1479
+ constructor(threadId, operation) {
1480
+ super(`Codex thread binding changed while ${operation}: ${threadId}`);
1481
+ this.name = "CodexThreadBindingConflictError";
1482
+ }
1483
+ };
1484
+ var CodexRingZeroAttestationError = class extends Error {
1485
+ constructor(cause) {
1486
+ super("Codex ring-zero MCP attestation failed", { cause });
1487
+ this.name = "CodexRingZeroAttestationError";
1488
+ }
1489
+ };
1490
+ var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
1491
+ var CodexAdoptedThreadActiveError = class extends Error {
1492
+ constructor() {
1493
+ super("Codex session became active in another runner; wait for it to finish before continuing");
1494
+ this.name = "CodexAdoptedThreadActiveError";
1495
+ }
1496
+ };
1497
+ //#endregion
1498
+ //#region extensions/codex/src/app-server/reasoning-effort.ts
1499
+ const CODEX_REASONING_EFFORTS = [
1500
+ "minimal",
1501
+ "low",
1502
+ "medium",
1503
+ "high",
1504
+ "xhigh",
1505
+ "max",
1506
+ "ultra"
1507
+ ];
1508
+ const GPT_56_MAX_REASONING_EFFORTS = [
1509
+ "low",
1510
+ "medium",
1511
+ "high",
1512
+ "xhigh",
1513
+ "max"
1514
+ ];
1515
+ const GPT_56_ULTRA_REASONING_EFFORTS = [...GPT_56_MAX_REASONING_EFFORTS, "ultra"];
1516
+ const GPT_5_PRO_REASONING_EFFORTS = [
1517
+ "medium",
1518
+ "high",
1519
+ "xhigh"
1520
+ ];
1521
+ const GPT_56_ULTRA_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.6-sol", "gpt-5.6-terra"]);
1522
+ const GPT_56_MAX_MODEL_IDS = /* @__PURE__ */ new Set([...GPT_56_ULTRA_MODEL_IDS, "gpt-5.6-luna"]);
1523
+ const MODERN_CODEX_MODEL_IDS = /* @__PURE__ */ new Set([
1524
+ ...GPT_56_MAX_MODEL_IDS,
1525
+ "gpt-5.5",
1526
+ "gpt-5.5-pro",
1527
+ "gpt-5.4",
1528
+ "gpt-5.4-pro",
1529
+ "gpt-5.4-mini",
1530
+ "gpt-5.3-codex-spark"
1531
+ ]);
1532
+ function normalizeCodexReasoningEfforts(efforts) {
1533
+ if (!efforts) return [];
1534
+ const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
1535
+ return CODEX_REASONING_EFFORTS.filter((effort) => supported.has(effort));
1536
+ }
1537
+ /** Read reasoning metadata after the Codex app-server route has been selected. */
1538
+ function readCodexSupportedReasoningEfforts(compat) {
1539
+ if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
1540
+ const efforts = compat.supportedReasoningEfforts;
1541
+ if (!Array.isArray(efforts)) return;
1542
+ return efforts.filter((effort) => typeof effort === "string");
1543
+ }
1544
+ function resolveSupportedReasoningEffort(params) {
1545
+ const supported = normalizeCodexReasoningEfforts(params.supportedReasoningEfforts);
1546
+ if (supported.includes(params.requested)) return params.requested;
1547
+ const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
1548
+ const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
1549
+ return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
1550
+ }
1551
+ function resolveFallbackReasoningEfforts(modelId) {
1552
+ const normalized = modelId.trim().toLowerCase();
1553
+ if (GPT_56_ULTRA_MODEL_IDS.has(normalized)) return GPT_56_ULTRA_REASONING_EFFORTS;
1554
+ if (normalized === "gpt-5.6-luna") return GPT_56_MAX_REASONING_EFFORTS;
1555
+ if (normalized === "gpt-5.5-pro" || normalized === "gpt-5.4-pro") return GPT_5_PRO_REASONING_EFFORTS;
1556
+ }
1557
+ /** Resolve a turn effort from app-server metadata, with exact-name offline fallbacks. */
1558
+ function resolveCodexAppServerReasoningEffort(params) {
1559
+ if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
1560
+ const supportedReasoningEfforts = params.supportedReasoningEfforts ?? resolveFallbackReasoningEfforts(params.modelId);
1561
+ if (supportedReasoningEfforts) return resolveSupportedReasoningEffort({
1562
+ requested: params.thinkLevel,
1563
+ supportedReasoningEfforts
1564
+ }) ?? null;
1565
+ const normalizedModelId = params.modelId.trim().toLowerCase();
1566
+ if (params.thinkLevel === "minimal") return MODERN_CODEX_MODEL_IDS.has(normalizedModelId) ? "low" : "minimal";
1567
+ if (params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
1568
+ return params.thinkLevel === "max" && GPT_56_MAX_MODEL_IDS.has(normalizedModelId) ? "max" : null;
1569
+ }
1570
+ //#endregion
1571
+ //#region extensions/codex/src/app-server/thread-model-selection.ts
1572
+ const CODEX_NATIVE_PERSONALITY_NONE = "none";
1573
+ function resolveCodexBindingModelProviderFallback(params) {
1574
+ const provider = params.provider?.trim().toLowerCase();
1575
+ if (provider && provider !== "codex") return;
1576
+ const currentModel = params.currentModel?.trim();
1577
+ const bindingModel = params.bindingModel?.trim();
1578
+ if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
1579
+ return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
1580
+ }
1581
+ function resolveCodexAppServerThreadModelSelection(params) {
1582
+ const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
1583
+ const explicitModelProvider = resolveCodexAppServerModelProvider({
1584
+ provider: params.provider,
1585
+ authProfileId,
1586
+ authProfileStore: params.authProfileStore,
1587
+ agentDir: params.agentDir,
1588
+ config: params.config
1589
+ });
1590
+ const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
1591
+ provider: params.provider,
1592
+ currentModel: params.model,
1593
+ bindingModel: params.binding.model,
1594
+ bindingModelProvider: params.binding.modelProvider
1595
+ }) : void 0;
1596
+ return resolveCodexAppServerRequestModelSelection({
1597
+ model: params.model,
1598
+ modelProvider: explicitModelProvider ?? bindingModelProvider,
1599
+ authProfileId,
1600
+ authProfileStore: params.authProfileStore,
1601
+ agentDir: params.agentDir,
1602
+ config: params.config
1603
+ });
1604
+ }
1605
+ function resolveCodexAppServerRequestModelSelection(params) {
1606
+ const model = params.model.trim();
1607
+ const modelProvider = params.modelProvider?.trim();
1608
+ if (modelProvider) return {
1609
+ model,
1610
+ modelProvider
1611
+ };
1612
+ const slashIndex = model.indexOf("/");
1613
+ if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
1614
+ const inferredModelProvider = resolveCodexAppServerModelProvider({
1615
+ provider: model.slice(0, slashIndex),
1616
+ authProfileId: params.authProfileId,
1617
+ authProfileStore: params.authProfileStore,
1618
+ agentDir: params.agentDir,
1619
+ config: params.config
1620
+ });
1621
+ return {
1622
+ model: model.slice(slashIndex + 1).trim(),
1623
+ ...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
1624
+ };
1625
+ }
1626
+ function hasProviderQualifiedModelRef(model) {
1627
+ const trimmed = model?.trim();
1628
+ const slashIndex = trimmed?.indexOf("/") ?? -1;
1629
+ return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
1630
+ }
1631
+ function resolveCodexAppServerModelProvider(params) {
1632
+ const normalized = params.provider.trim();
1633
+ const normalizedLower = normalized.toLowerCase();
1634
+ if (!normalized || normalizedLower === "codex") return;
1635
+ if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
1636
+ return normalizedLower === "openai" ? "openai" : normalized;
1637
+ }
1638
+ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
1639
+ return resolveCodexAppServerReasoningEffort({
1640
+ thinkLevel,
1641
+ modelId,
1642
+ supportedReasoningEfforts
1643
+ });
1644
+ }
1645
+ //#endregion
1646
+ //#region extensions/codex/src/app-server/thread-prompt.ts
1647
+ function buildDeveloperInstructions(params, options = {}) {
1648
+ const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
1649
+ surface: "codex_app_server",
1650
+ includeLegacyGlobalGuidance: false
1651
+ }).join("\n");
1652
+ return [
1653
+ "You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
1654
+ buildDeferredDynamicToolManifest(options.dynamicTools),
1655
+ buildSkillWorkshopInstruction(options.dynamicTools),
1656
+ "Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.",
1657
+ buildVisibleReplyInstruction(params, options.dynamicTools),
1658
+ nativeCommandGuidance,
1659
+ params.extraSystemPrompt
1660
+ ].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
1661
+ }
1662
+ function buildDeferredDynamicToolManifest(dynamicTools) {
1663
+ const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
1664
+ if (deferredToolNames.length === 0) return;
1665
+ return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
1666
+ }
1667
+ function buildSkillWorkshopInstruction(dynamicTools) {
1668
+ if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
1669
+ return buildSkillWorkshopPromptSection().join("\n");
1670
+ }
1671
+ function buildVisibleReplyInstruction(params, dynamicTools) {
1672
+ const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
1673
+ if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. If `final` is omitted, OpenClaw continues and resolves the latest omitted source reply only when the turn ends successfully. Do not repeat visible message content in your final answer.";
1674
+ if (messageToolAvailable) return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation. Use `message` only for explicit out-of-band sends, media/file sends, or sends to a different target.";
1675
+ return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
1676
+ }
1677
+ //#endregion
1678
+ //#region extensions/codex/src/app-server/web-search.ts
1679
+ const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG = {
1680
+ "features.standalone_web_search": false,
1681
+ web_search: "disabled"
1682
+ };
1683
+ function normalizeOptionalString$1(value) {
1684
+ return typeof value === "string" ? value.trim() || void 0 : void 0;
1685
+ }
1686
+ function normalizeUniqueStrings(value) {
1687
+ if (!Array.isArray(value)) return;
1688
+ const normalized = [...new Set(value.map(normalizeOptionalString$1).filter((entry) => Boolean(entry)))];
1689
+ return normalized.length > 0 ? normalized : void 0;
1690
+ }
1691
+ function hasManagedSearchProvider(config) {
1692
+ return normalizeOptionalString$1(config?.tools?.web?.search?.provider) !== void 0;
1693
+ }
1694
+ function hasNativeDomainRestrictions(config) {
1695
+ return normalizeUniqueStrings(config?.tools?.web?.search?.openaiCodex?.allowedDomains) !== void 0;
1696
+ }
1697
+ function buildCodexNativeWebSearchThreadConfig(config) {
1698
+ const nativeConfig = config?.tools?.web?.search?.openaiCodex;
1699
+ const threadConfig = {
1700
+ "features.standalone_web_search": false,
1701
+ web_search: nativeConfig?.mode === "live" ? "live" : "cached"
1702
+ };
1703
+ const allowedDomains = normalizeUniqueStrings(nativeConfig?.allowedDomains);
1704
+ if (allowedDomains) threadConfig["tools.web_search.allowed_domains"] = allowedDomains;
1705
+ if (nativeConfig?.contextSize) threadConfig["tools.web_search.context_size"] = nativeConfig.contextSize;
1706
+ const location = nativeConfig?.userLocation;
1707
+ const country = normalizeOptionalString$1(location?.country);
1708
+ const region = normalizeOptionalString$1(location?.region);
1709
+ const city = normalizeOptionalString$1(location?.city);
1710
+ const timezone = normalizeOptionalString$1(location?.timezone);
1711
+ if (country) threadConfig["tools.web_search.location.country"] = country;
1712
+ if (region) threadConfig["tools.web_search.location.region"] = region;
1713
+ if (city) threadConfig["tools.web_search.location.city"] = city;
1714
+ if (timezone) threadConfig["tools.web_search.location.timezone"] = timezone;
1715
+ return threadConfig;
1716
+ }
1717
+ function resolveCodexWebSearchPlan(params) {
1718
+ if (params.disableTools === true || params.webSearchAllowed === false || params.config?.tools?.web?.search?.enabled === false) return {
1719
+ kind: "disabled",
1720
+ suppressManagedWebSearch: true,
1721
+ threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
1722
+ };
1723
+ const nativeConfig = params.config?.tools?.web?.search?.openaiCodex;
1724
+ const managedSearchExplicit = hasManagedSearchProvider(params.config) || nativeConfig?.enabled === false;
1725
+ const nativeProviderSupportsSearch = params.nativeProviderWebSearchSupport === void 0 || params.nativeProviderWebSearchSupport === "supported";
1726
+ if (!(params.nativeToolSurfaceEnabled !== false && nativeProviderSupportsSearch && nativeConfig?.enabled !== false && !hasManagedSearchProvider(params.config))) {
1727
+ if (!managedSearchExplicit && hasNativeDomainRestrictions(params.config)) return {
1728
+ kind: "disabled",
1729
+ suppressManagedWebSearch: true,
1730
+ threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
1731
+ };
1732
+ return {
1733
+ kind: "managed",
1734
+ suppressManagedWebSearch: false,
1735
+ threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
1736
+ };
1737
+ }
1738
+ return {
1739
+ kind: "native-hosted",
1740
+ suppressManagedWebSearch: true,
1741
+ threadConfig: buildCodexNativeWebSearchThreadConfig(params.config)
1742
+ };
1743
+ }
1744
+ const CODEX_CODE_MODE_THREAD_CONFIG = {
1745
+ "features.code_mode": true,
1746
+ "features.code_mode_only": false,
1747
+ "features.apply_patch_streaming_events": true
1748
+ };
1749
+ const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
1750
+ "features.code_mode": false,
1751
+ "features.code_mode_only": false
1752
+ };
1753
+ const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
1754
+ const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
1755
+ const CODEX_RING_ZERO_THREAD_CONFIG = {
1756
+ "features.apps": false,
1757
+ "features.current_time_reminder": false,
1758
+ "features.deferred_executor": false,
1759
+ "features.enable_fanout": false,
1760
+ "features.goals": false,
1761
+ "features.hooks": false,
1762
+ "features.image_generation": false,
1763
+ "features.memories": false,
1764
+ "features.multi_agent": false,
1765
+ "features.multi_agent_v2": false,
1766
+ "features.plugins": false,
1767
+ "features.standalone_web_search": false,
1768
+ "features.token_budget": false,
1769
+ "orchestrator.mcp.enabled": false,
1770
+ "orchestrator.skills.enabled": false,
1771
+ "tools.experimental_request_user_input.enabled": false,
1772
+ hooks: {
1773
+ PreToolUse: [],
1774
+ PermissionRequest: [],
1775
+ PostToolUse: [],
1776
+ PreCompact: [],
1777
+ PostCompact: [],
1778
+ SessionStart: [],
1779
+ UserPromptSubmit: [],
1780
+ SubagentStart: [],
1781
+ SubagentStop: [],
1782
+ Stop: []
1783
+ },
1784
+ project_doc_max_bytes: 0,
1785
+ notify: [],
1786
+ web_search: "disabled"
1787
+ };
1788
+ const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
1789
+ "apps",
1790
+ "code_mode",
1791
+ "code_mode_only",
1792
+ "current_time_reminder",
1793
+ "deferred_executor",
1794
+ "enable_fanout",
1795
+ "goals",
1796
+ "hooks",
1797
+ "image_generation",
1798
+ "memories",
1799
+ "multi_agent",
1800
+ "multi_agent_v2",
1801
+ "plugins",
1802
+ "standalone_web_search",
1803
+ "token_budget"
1804
+ ]);
1805
+ const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
1806
+ "mdm",
1807
+ "system",
1808
+ "enterpriseManaged",
1809
+ "user",
1810
+ "project",
1811
+ "sessionFlags"
1812
+ ]);
1813
+ function buildThreadStartParams(params, options) {
1814
+ const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
1815
+ const resolvedModelProvider = resolveCodexAppServerModelProvider({
1816
+ provider: params.provider,
1817
+ authProfileId: params.authProfileId,
1818
+ authProfileStore: params.authProfileStore,
1819
+ agentDir: params.agentDir,
1820
+ config: params.config
1821
+ });
1822
+ const modelSelection = resolveCodexAppServerRequestModelSelection({
1823
+ model: options.model ?? params.modelId,
1824
+ modelProvider: options.modelProvider ?? resolvedModelProvider,
1825
+ authProfileId: params.authProfileId,
1826
+ authProfileStore: params.authProfileStore,
1827
+ agentDir: params.agentDir,
1828
+ config: params.config
1829
+ });
1830
+ return {
1831
+ model: modelSelection.model,
1832
+ ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
1833
+ cwd: options.cwd,
1834
+ approvalPolicy: options.appServer.approvalPolicy,
1835
+ approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
1836
+ ...codexThreadSandboxOrPermissions(options.appServer),
1837
+ ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
1838
+ personality: CODEX_NATIVE_PERSONALITY_NONE,
1839
+ serviceName: "OpenClaw",
1840
+ ...ringZeroActive ? { baseInstructions: "" } : {},
1841
+ config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
1842
+ nativeCodeModeEnabled: options.nativeCodeModeEnabled,
1843
+ nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
1844
+ nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
1845
+ directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
1846
+ webSearchAllowed: options.webSearchAllowed,
1847
+ appServer: options.appServer,
1848
+ hostSystemAgentActive: options.hostSystemAgentActive,
1849
+ ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
1850
+ }),
1851
+ ...resolveCodexThreadEnvironmentSelection(options),
1852
+ developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
1853
+ dynamicTools: [...options.dynamicTools],
1854
+ experimentalRawEvents: true
1855
+ };
1856
+ }
1857
+ function buildThreadResumeParams(params, options) {
1858
+ const modelSelection = options.preserveNativeModel ? void 0 : resolveCodexAppServerRequestModelSelection({
1859
+ model: options.model ?? params.modelId,
1860
+ modelProvider: options.modelProvider ?? resolveCodexAppServerModelProvider({
1861
+ provider: params.provider,
1862
+ authProfileId: options.authProfileId ?? params.authProfileId,
1863
+ authProfileStore: params.authProfileStore,
1864
+ agentDir: params.agentDir,
1865
+ config: params.config
1866
+ }),
1867
+ authProfileId: options.authProfileId ?? params.authProfileId,
1868
+ authProfileStore: params.authProfileStore,
1869
+ agentDir: params.agentDir,
1870
+ config: params.config
1871
+ });
1872
+ return {
1873
+ threadId: options.threadId,
1874
+ ...modelSelection ? {
1875
+ model: modelSelection.model,
1876
+ ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
1877
+ } : {},
1878
+ approvalPolicy: options.appServer.approvalPolicy,
1879
+ approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
1880
+ ...codexThreadSandboxOrPermissions(options.appServer),
1881
+ ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
1882
+ personality: CODEX_NATIVE_PERSONALITY_NONE,
1883
+ config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
1884
+ nativeCodeModeEnabled: options.nativeCodeModeEnabled,
1885
+ nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
1886
+ nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
1887
+ directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
1888
+ webSearchAllowed: options.webSearchAllowed,
1889
+ appServer: options.appServer,
1890
+ hostSystemAgentActive: options.hostSystemAgentActive,
1891
+ ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
1892
+ }),
1893
+ developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
1894
+ };
1895
+ }
1896
+ function buildCodexRuntimeThreadConfig(config, options = {}) {
1897
+ const codeModeConfig = {
1898
+ ...CODEX_CODE_MODE_THREAD_CONFIG,
1899
+ "features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
1900
+ };
1901
+ if (options.nativeCodeModeEnabled === false) {
1902
+ const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
1903
+ delete disabledConfig["features.apply_patch_streaming_events"];
1904
+ return disabledConfig;
1905
+ }
1906
+ if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, { "features.code_mode_only": true }) ?? {
1907
+ ...codeModeConfig,
1908
+ "features.code_mode_only": true
1909
+ }, options.directOnlyToolNamespaces);
1910
+ return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config) ?? { ...codeModeConfig }, options.directOnlyToolNamespaces);
1911
+ }
1912
+ function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
1913
+ if (!requiredNamespaces?.length) return config;
1914
+ const configured = config["code_mode.direct_only_tool_namespaces"];
1915
+ const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
1916
+ return {
1917
+ ...config,
1918
+ "code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
1919
+ };
1920
+ }
1921
+ function resolveDirectOnlyToolNamespaces(dynamicTools) {
1922
+ return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
1923
+ }
1924
+ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
1925
+ const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
1926
+ const configMcpServers = config?.mcp_servers;
1927
+ if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
1928
+ const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
1929
+ const webSearchConfig = resolveCodexWebSearchPlan({
1930
+ config: params.config,
1931
+ disableTools: params.disableTools,
1932
+ nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
1933
+ nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
1934
+ webSearchAllowed: options.webSearchAllowed
1935
+ }).threadConfig;
1936
+ const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
1937
+ const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
1938
+ if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
1939
+ return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
1940
+ ...runtimeConfig,
1941
+ ...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
1942
+ };
1943
+ }
1944
+ function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
1945
+ if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
1946
+ const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
1947
+ return {
1948
+ ...CODEX_RING_ZERO_THREAD_CONFIG,
1949
+ ...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
1950
+ };
1951
+ }
1952
+ async function readCodexInheritedMcpServerNames(client, cwd, signal) {
1953
+ const response = await client.request("config/read", {
1954
+ cwd,
1955
+ includeLayers: true
1956
+ }, { signal });
1957
+ if (!isJsonObject(response) || !isJsonObject(response.config)) throw new Error("Codex config/read returned an invalid effective config");
1958
+ if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
1959
+ for (const layer of response.layers) {
1960
+ if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
1961
+ if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex ring-zero cannot override config layer ${layer.name.type}`);
1962
+ if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex ring-zero does not recognize config layer ${layer.name.type}`);
1963
+ }
1964
+ const configuredServers = response.config.mcp_servers;
1965
+ if (configuredServers === void 0) return [];
1966
+ if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
1967
+ return Object.keys(configuredServers).toSorted();
1968
+ }
1969
+ async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
1970
+ const response = await client.request("configRequirements/read", void 0, { signal });
1971
+ if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
1972
+ if (response.requirements === null) return;
1973
+ if (!isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
1974
+ for (const key of [
1975
+ "hooks",
1976
+ "managedHooks",
1977
+ "managed_hooks"
1978
+ ]) {
1979
+ const hooks = response.requirements[key];
1980
+ if (hooks === void 0 || hooks === null) continue;
1981
+ if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
1982
+ if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex ring-zero cannot override managed hooks");
1983
+ }
1984
+ for (const key of ["featureRequirements", "feature_requirements"]) {
1985
+ const requirements = response.requirements[key];
1986
+ if (requirements === void 0 || requirements === null) continue;
1987
+ if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
1988
+ for (const [feature, enabled] of Object.entries(requirements)) {
1989
+ if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
1990
+ if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(feature)) throw new Error(`Codex ring-zero cannot override required feature ${feature}`);
1832
1991
  }
1833
- if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
1834
- assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
1835
- preserveExistingBinding = shouldStartTransientNoToolThread({
1836
- previous: binding.dynamicToolsFingerprint,
1837
- nextHasDynamicTools: params.dynamicTools.length > 0
1838
- });
1839
- if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
1840
- else {
1841
- embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
1842
- await clearCurrentBinding("rotating a stale thread binding");
1843
- }
1844
- } else {
1845
- const resumeBinding = binding;
1846
- let resumeReservation;
1847
- try {
1848
- const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
1849
- const finalConfigPatch = params.buildFinalConfigPatch?.({
1850
- action: "resume",
1851
- binding: resumeBinding
1852
- }) ?? {
1853
- configPatch: params.finalConfigPatch,
1854
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
1855
- };
1856
- const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
1857
- const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
1858
- const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
1859
- threadId: resumeBinding.threadId,
1860
- authProfileId,
1861
- model: startModelSelection.model,
1862
- modelProvider: startModelProvider,
1863
- preserveNativeModel: resumeBinding.preserveNativeModel === true,
1864
- appServer: params.appServer,
1865
- dynamicTools: params.dynamicTools,
1866
- developerInstructions: params.developerInstructions,
1867
- config: resumeConfig,
1868
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1869
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1870
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1871
- webSearchAllowed: params.webSearchAllowed,
1872
- hostSystemAgentActive,
1873
- ringZeroInheritedMcpServerNames
1874
- }));
1875
- const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
1876
- throwIfAborted();
1877
- if (resumeBinding.preserveNativeModel === true) {
1878
- const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
1879
- threadId: resumeBinding.threadId,
1880
- includeTurns: false
1881
- }, { signal: params.signal }));
1882
- throwIfAborted();
1883
- if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
1884
- }
1885
- resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
1886
- const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
1887
- client: params.client,
1888
- abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
1889
- request: resumeParams,
1890
- signal: params.signal
1891
- }));
1892
- if (ringZeroActive) try {
1893
- await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
1894
- } catch (error) {
1895
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
1896
- throw new CodexRingZeroAttestationError(error);
1897
- }
1898
- throwIfAborted();
1899
- const boundAuthProfileId = authProfileId;
1900
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
1901
- const resumePatch = {
1902
- cwd: params.cwd,
1903
- authProfileId: boundAuthProfileId,
1904
- model: response.model ?? resumeParams.model ?? params.params.modelId,
1905
- preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
1906
- modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
1907
- dynamicToolsFingerprint,
1908
- dynamicToolsContainDeferred,
1909
- webSearchThreadConfigFingerprint,
1910
- userMcpServersFingerprint,
1911
- mcpServersFingerprint: nextMcpServersFingerprint,
1912
- ringZeroConfigFingerprint,
1913
- ringZeroClientInstanceId,
1914
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
1915
- networkProxyConfigFingerprint,
1916
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
1917
- appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
1918
- pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
1919
- pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
1920
- pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
1921
- contextEngine: contextEngineBinding,
1922
- environmentSelectionFingerprint
1923
- };
1924
- if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
1925
- kind: "patch",
1926
- threadId: resumeBinding.threadId,
1927
- patch: resumePatch
1928
- }))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
1929
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
1930
- sessionId: params.params.sessionId,
1931
- sessionKey: params.params.sessionKey,
1932
- threadId: response.thread.id,
1933
- engineId: contextEngineBinding.engineId,
1934
- epoch: contextEngineBinding.projection?.epoch,
1935
- fingerprint: contextEngineBinding.projection?.fingerprint,
1936
- action: "resumed"
1937
- });
1938
- lifecycleTiming.mark("thread-ready");
1939
- lifecycleTiming.logSummary({
1940
- runId: params.params.runId,
1941
- sessionId: params.params.sessionId,
1942
- sessionKey: params.params.sessionKey,
1943
- threadId: response.thread.id,
1944
- action: "resumed"
1945
- });
1946
- const activeTurnIds = readActiveCodexTurnIds(response.thread);
1947
- return {
1948
- ...resumeBinding,
1949
- threadId: response.thread.id,
1950
- ...resumePatch,
1951
- lifecycle: {
1952
- action: "resumed",
1953
- ...activeTurnIds.length ? { activeTurnIds } : {}
1954
- }
1955
- };
1956
- } catch (error) {
1957
- resumeReservation?.release();
1958
- if (isCodexAppServerStartSelectionChangedError(error)) throw error;
1959
- if (error instanceof CodexRingZeroAttestationError) {
1960
- await clearCurrentBinding("retiring a failed ring-zero thread attestation");
1961
- throw error;
1962
- }
1963
- if (error instanceof CodexAdoptedThreadActiveError) throw error;
1964
- if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
1965
- const resumeRejected = error instanceof CodexAppServerRpcError;
1966
- if (!await unsubscribeCodexThreadBestEffort(params.client, {
1967
- threadId: resumeBinding.threadId,
1968
- timeoutMs: 5e3
1969
- }) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
1970
- if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
1971
- embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
1972
- await clearCurrentBinding("rotating a stale thread binding");
1973
- }
1992
+ }
1993
+ }
1994
+ async function attestCodexRingZeroThreadHasNoMcpServers(client, threadId, signal) {
1995
+ const response = await client.request("mcpServerStatus/list", {
1996
+ threadId,
1997
+ limit: 1,
1998
+ detail: "toolsAndAuthOnly"
1999
+ }, { signal });
2000
+ if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid ring-zero attestation");
2001
+ if (response.data.length > 0) {
2002
+ const first = response.data[0];
2003
+ const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
2004
+ throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
2005
+ }
2006
+ if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
2007
+ }
2008
+ function hasNonEmptyJsonValue(value) {
2009
+ if (value === null || value === false || value === "") return false;
2010
+ if (Array.isArray(value)) return value.length > 0;
2011
+ if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
2012
+ return true;
2013
+ }
2014
+ function resolveCodexThreadApprovalsReviewer(appServer, config) {
2015
+ return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
2016
+ }
2017
+ function codexThreadSandboxOrPermissions(appServer) {
2018
+ if (appServer.networkProxy) return {};
2019
+ return { sandbox: appServer.sandbox };
2020
+ }
2021
+ function resolveCodexThreadEnvironmentSelection(options) {
2022
+ if (options.nativeCodeModeEnabled === false) return { environments: [] };
2023
+ if (options.environmentSelection) return { environments: options.environmentSelection };
2024
+ return {};
2025
+ }
2026
+ //#endregion
2027
+ //#region extensions/codex/src/app-server/thread-resume.ts
2028
+ /** Owns Codex thread/resume subscription safety. */
2029
+ /** Resumes one thread and retires the physical client when acceptance is indeterminate. */
2030
+ async function resumeCodexAppServerThread(params) {
2031
+ const threadId = params.request.threadId;
2032
+ let response;
2033
+ try {
2034
+ response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
2035
+ ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
2036
+ ...params.signal ? { signal: params.signal } : {}
2037
+ }));
2038
+ assertCodexThreadResumeSubscription(threadId, response.thread.id);
2039
+ } catch (error) {
2040
+ if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
2041
+ if (error instanceof CodexAppServerRpcError) throw error;
2042
+ try {
2043
+ await params.abandonClient();
2044
+ } catch (abandonError) {
2045
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
1974
2046
  }
1975
- const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
1976
- const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2047
+ if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
2048
+ throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
2049
+ }
2050
+ return response;
2051
+ }
2052
+ //#endregion
2053
+ //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
2054
+ async function resumeExistingCodexThread(params, context) {
2055
+ const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
2056
+ let resumeReservation;
2057
+ try {
2058
+ const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
2059
+ const finalConfigPatch = params.buildFinalConfigPatch?.({
2060
+ action: "resume",
2061
+ binding: resumeBinding
2062
+ }) ?? {
1977
2063
  configPatch: params.finalConfigPatch,
1978
2064
  nativeHookRelayGeneration: params.nativeHookRelayGeneration
1979
2065
  };
1980
- const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
1981
- const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
1982
- cwd: params.cwd,
1983
- dynamicTools: params.dynamicTools,
2066
+ const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
2067
+ const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
2068
+ const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
2069
+ threadId: resumeBinding.threadId,
2070
+ authProfileId,
2071
+ model: startModelSelection.model,
2072
+ modelProvider: startModelProvider,
2073
+ preserveNativeModel: resumeBinding.preserveNativeModel === true,
1984
2074
  appServer: params.appServer,
2075
+ dynamicTools: params.dynamicTools,
1985
2076
  developerInstructions: params.developerInstructions,
1986
- config,
2077
+ config: resumeConfig,
1987
2078
  nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1988
2079
  nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1989
2080
  nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1990
2081
  webSearchAllowed: params.webSearchAllowed,
1991
- environmentSelection: params.environmentSelection,
1992
- model: startModelSelection.model,
1993
- modelProvider: startModelProvider,
1994
2082
  hostSystemAgentActive,
1995
2083
  ringZeroInheritedMcpServerNames
1996
2084
  }));
1997
- const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
1998
- const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
1999
- try {
2000
- return await params.client.request("thread/start", startParams, { signal: params.signal });
2001
- } catch (error) {
2002
- if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
2003
- throw error;
2004
- }
2085
+ const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
2086
+ throwIfAborted();
2087
+ if (resumeBinding.preserveNativeModel === true) {
2088
+ const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
2089
+ threadId: resumeBinding.threadId,
2090
+ includeTurns: false
2091
+ }, { signal: params.signal }));
2092
+ throwIfAborted();
2093
+ if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
2094
+ }
2095
+ resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
2096
+ const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
2097
+ client: params.client,
2098
+ abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
2099
+ request: resumeParams,
2100
+ signal: params.signal
2005
2101
  }));
2006
2102
  if (ringZeroActive) try {
2007
2103
  await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
2008
2104
  } catch (error) {
2009
2105
  await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2010
- throw error;
2106
+ throw new CodexRingZeroAttestationError(error);
2011
2107
  }
2012
2108
  throwIfAborted();
2013
- const modelProvider = resolveCodexAppServerModelProvider({
2014
- provider: params.params.provider,
2015
- authProfileId: params.params.authProfileId,
2016
- authProfileStore: params.params.authProfileStore,
2017
- agentDir: params.params.agentDir,
2018
- config: params.params.config
2109
+ const boundAuthProfileId = authProfileId;
2110
+ const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
2111
+ const resumePatch = {
2112
+ cwd: params.cwd,
2113
+ authProfileId: boundAuthProfileId,
2114
+ model: response.model ?? resumeParams.model ?? params.params.modelId,
2115
+ preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
2116
+ modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
2117
+ dynamicToolsFingerprint,
2118
+ dynamicToolsContainDeferred,
2119
+ webSearchThreadConfigFingerprint,
2120
+ userMcpServersFingerprint,
2121
+ mcpServersFingerprint: nextMcpServersFingerprint,
2122
+ ringZeroConfigFingerprint,
2123
+ ringZeroClientInstanceId,
2124
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
2125
+ networkProxyConfigFingerprint,
2126
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
2127
+ appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
2128
+ pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
2129
+ pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
2130
+ pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
2131
+ contextEngine: contextEngineBinding,
2132
+ environmentSelectionFingerprint
2133
+ };
2134
+ if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2135
+ kind: "patch",
2136
+ threadId: resumeBinding.threadId,
2137
+ patch: resumePatch
2138
+ }))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
2139
+ if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2140
+ sessionId: params.params.sessionId,
2141
+ sessionKey: params.params.sessionKey,
2142
+ threadId: response.thread.id,
2143
+ engineId: contextEngineBinding.engineId,
2144
+ epoch: contextEngineBinding.projection?.epoch,
2145
+ fingerprint: contextEngineBinding.projection?.fingerprint,
2146
+ action: "resumed"
2019
2147
  });
2020
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2021
- if (!preserveExistingBinding) {
2022
- if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2023
- kind: "set",
2024
- if: { kind: "absent" },
2025
- binding: {
2026
- threadId: response.thread.id,
2027
- cwd: params.cwd,
2028
- authProfileId: params.params.authProfileId,
2029
- model: response.model ?? startParams.model ?? params.params.modelId,
2030
- modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
2031
- dynamicToolsFingerprint,
2032
- dynamicToolsContainDeferred,
2033
- webSearchThreadConfigFingerprint,
2034
- userMcpServersFingerprint,
2035
- mcpServersFingerprint: nextMcpServersFingerprint,
2036
- ringZeroConfigFingerprint,
2037
- ringZeroClientInstanceId,
2038
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2039
- networkProxyConfigFingerprint,
2040
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2041
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2042
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2043
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2044
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2045
- contextEngine: contextEngineBinding,
2046
- environmentSelectionFingerprint
2047
- }
2048
- }))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
2049
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2050
- sessionId: params.params.sessionId,
2051
- sessionKey: params.params.sessionKey,
2052
- threadId: response.thread.id,
2053
- engineId: contextEngineBinding.engineId,
2054
- epoch: contextEngineBinding.projection?.epoch,
2055
- fingerprint: contextEngineBinding.projection?.fingerprint,
2056
- action: rotatedContextEngineBinding ? "rotated" : "started"
2057
- });
2058
- }
2059
2148
  lifecycleTiming.mark("thread-ready");
2060
2149
  lifecycleTiming.logSummary({
2061
2150
  runId: params.params.runId,
2062
2151
  sessionId: params.params.sessionId,
2063
2152
  sessionKey: params.params.sessionKey,
2064
2153
  threadId: response.thread.id,
2154
+ action: "resumed"
2155
+ });
2156
+ const activeTurnIds = readActiveCodexTurnIds(response.thread);
2157
+ return {
2158
+ ...resumeBinding,
2159
+ threadId: response.thread.id,
2160
+ ...resumePatch,
2161
+ lifecycle: {
2162
+ action: "resumed",
2163
+ ...activeTurnIds.length ? { activeTurnIds } : {}
2164
+ }
2165
+ };
2166
+ } catch (error) {
2167
+ resumeReservation?.release();
2168
+ if (isCodexAppServerStartSelectionChangedError(error)) throw error;
2169
+ if (error instanceof CodexRingZeroAttestationError) {
2170
+ await clearCurrentBinding("retiring a failed ring-zero thread attestation");
2171
+ throw error;
2172
+ }
2173
+ if (error instanceof CodexAdoptedThreadActiveError) throw error;
2174
+ if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
2175
+ const resumeRejected = error instanceof CodexAppServerRpcError;
2176
+ if (!await unsubscribeCodexThreadBestEffort(params.client, {
2177
+ threadId: resumeBinding.threadId,
2178
+ timeoutMs: 5e3
2179
+ }) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
2180
+ if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
2181
+ embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
2182
+ await clearCurrentBinding("rotating a stale thread binding");
2183
+ }
2184
+ }
2185
+ async function startFreshCodexThread(params, context) {
2186
+ const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding } = context;
2187
+ const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2188
+ const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2189
+ configPatch: params.finalConfigPatch,
2190
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
2191
+ };
2192
+ const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
2193
+ const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
2194
+ cwd: params.cwd,
2195
+ dynamicTools: params.dynamicTools,
2196
+ appServer: params.appServer,
2197
+ developerInstructions: params.developerInstructions,
2198
+ config,
2199
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2200
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2201
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2202
+ webSearchAllowed: params.webSearchAllowed,
2203
+ environmentSelection: params.environmentSelection,
2204
+ model: startModelSelection.model,
2205
+ modelProvider: startModelProvider,
2206
+ hostSystemAgentActive,
2207
+ ringZeroInheritedMcpServerNames
2208
+ }));
2209
+ const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
2210
+ const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
2211
+ try {
2212
+ return await params.client.request("thread/start", startParams, { signal: params.signal });
2213
+ } catch (error) {
2214
+ if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
2215
+ throw error;
2216
+ }
2217
+ }));
2218
+ if (ringZeroActive) try {
2219
+ await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
2220
+ } catch (error) {
2221
+ await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2222
+ throw error;
2223
+ }
2224
+ throwIfAborted();
2225
+ const modelProvider = resolveCodexAppServerModelProvider({
2226
+ provider: params.params.provider,
2227
+ authProfileId: params.params.authProfileId,
2228
+ authProfileStore: params.params.authProfileStore,
2229
+ agentDir: params.params.agentDir,
2230
+ config: params.params.config
2231
+ });
2232
+ const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2233
+ if (!preserveExistingBinding) {
2234
+ if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2235
+ kind: "set",
2236
+ if: { kind: "absent" },
2237
+ binding: {
2238
+ threadId: response.thread.id,
2239
+ cwd: params.cwd,
2240
+ authProfileId: params.params.authProfileId,
2241
+ model: response.model ?? startParams.model ?? params.params.modelId,
2242
+ modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
2243
+ dynamicToolsFingerprint,
2244
+ dynamicToolsContainDeferred,
2245
+ webSearchThreadConfigFingerprint,
2246
+ userMcpServersFingerprint,
2247
+ mcpServersFingerprint: nextMcpServersFingerprint,
2248
+ ringZeroConfigFingerprint,
2249
+ ringZeroClientInstanceId,
2250
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
2251
+ networkProxyConfigFingerprint,
2252
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2253
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2254
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2255
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2256
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2257
+ contextEngine: contextEngineBinding,
2258
+ environmentSelectionFingerprint
2259
+ }
2260
+ }))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
2261
+ if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2262
+ sessionId: params.params.sessionId,
2263
+ sessionKey: params.params.sessionKey,
2264
+ threadId: response.thread.id,
2265
+ engineId: contextEngineBinding.engineId,
2266
+ epoch: contextEngineBinding.projection?.epoch,
2267
+ fingerprint: contextEngineBinding.projection?.fingerprint,
2065
2268
  action: rotatedContextEngineBinding ? "rotated" : "started"
2066
2269
  });
2067
- return {
2068
- threadId: response.thread.id,
2069
- cwd: params.cwd,
2070
- authProfileId: params.params.authProfileId,
2071
- model: response.model ?? startParams.model ?? params.params.modelId,
2072
- modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
2073
- dynamicToolsFingerprint,
2074
- dynamicToolsContainDeferred,
2075
- userMcpServersFingerprint,
2076
- mcpServersFingerprint: nextMcpServersFingerprint,
2077
- ringZeroConfigFingerprint,
2078
- ringZeroClientInstanceId,
2079
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2080
- networkProxyConfigFingerprint,
2081
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2082
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2083
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2084
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2085
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2086
- contextEngine: contextEngineBinding,
2087
- environmentSelectionFingerprint,
2088
- lifecycle: {
2089
- action: "started",
2090
- ...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
2091
- }
2092
- };
2270
+ }
2271
+ lifecycleTiming.mark("thread-ready");
2272
+ lifecycleTiming.logSummary({
2273
+ runId: params.params.runId,
2274
+ sessionId: params.params.sessionId,
2275
+ sessionKey: params.params.sessionKey,
2276
+ threadId: response.thread.id,
2277
+ action: rotatedContextEngineBinding ? "rotated" : "started"
2278
+ });
2279
+ return {
2280
+ threadId: response.thread.id,
2281
+ cwd: params.cwd,
2282
+ authProfileId: params.params.authProfileId,
2283
+ model: response.model ?? startParams.model ?? params.params.modelId,
2284
+ modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
2285
+ dynamicToolsFingerprint,
2286
+ dynamicToolsContainDeferred,
2287
+ userMcpServersFingerprint,
2288
+ mcpServersFingerprint: nextMcpServersFingerprint,
2289
+ ringZeroConfigFingerprint,
2290
+ ringZeroClientInstanceId,
2291
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
2292
+ networkProxyConfigFingerprint,
2293
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2294
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2295
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2296
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2297
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2298
+ contextEngine: contextEngineBinding,
2299
+ environmentSelectionFingerprint,
2300
+ lifecycle: {
2301
+ action: "started",
2302
+ ...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
2303
+ }
2304
+ };
2305
+ }
2306
+ //#endregion
2307
+ //#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
2308
+ const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
2309
+ const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
2310
+ function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
2311
+ const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
2312
+ const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
2313
+ return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
2314
+ }
2315
+ function formatCodexThreadLifecycleTimingSummary(params) {
2316
+ const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
2317
+ 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}`;
2318
+ }
2319
+ function createCodexThreadLifecycleTimingTracker(options = {}) {
2320
+ const log = options.log ?? embeddedAgentLog;
2321
+ if (!options.enabled && log.isEnabled?.("trace") !== true) return {
2322
+ async measure(_name, run) {
2323
+ return await run();
2324
+ },
2325
+ measureSync(_name, run) {
2326
+ return run();
2327
+ },
2328
+ mark() {},
2329
+ logSummary() {}
2330
+ };
2331
+ const now = options.now ?? Date.now;
2332
+ const startedAt = now();
2333
+ let didLog = false;
2334
+ const spans = [];
2335
+ const toMs = (value) => Math.max(0, Math.round(value));
2336
+ const record = (name, spanStartedAt) => {
2337
+ const currentAt = now();
2338
+ spans.push({
2339
+ name,
2340
+ durationMs: toMs(currentAt - spanStartedAt),
2341
+ elapsedMs: toMs(currentAt - startedAt)
2342
+ });
2343
+ };
2344
+ const snapshot = () => ({
2345
+ totalMs: toMs(now() - startedAt),
2346
+ spans: spans.slice()
2093
2347
  });
2348
+ return {
2349
+ async measure(name, run) {
2350
+ const spanStartedAt = now();
2351
+ try {
2352
+ return await run();
2353
+ } finally {
2354
+ record(name, spanStartedAt);
2355
+ }
2356
+ },
2357
+ measureSync(name, run) {
2358
+ const spanStartedAt = now();
2359
+ try {
2360
+ return run();
2361
+ } finally {
2362
+ record(name, spanStartedAt);
2363
+ }
2364
+ },
2365
+ mark(name) {
2366
+ record(name, now());
2367
+ },
2368
+ logSummary(params) {
2369
+ if (didLog) return;
2370
+ const summary = snapshot();
2371
+ const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
2372
+ if (!shouldWarn && !log.isEnabled?.("trace")) return;
2373
+ didLog = true;
2374
+ const message = formatCodexThreadLifecycleTimingSummary({
2375
+ runId: params.runId,
2376
+ sessionId: params.sessionId,
2377
+ sessionKey: params.sessionKey,
2378
+ action: params.action,
2379
+ summary
2380
+ });
2381
+ const meta = {
2382
+ runId: params.runId,
2383
+ sessionId: params.sessionId,
2384
+ sessionKey: params.sessionKey,
2385
+ action: params.action,
2386
+ threadId: params.threadId,
2387
+ totalMs: summary.totalMs,
2388
+ spans: summary.spans
2389
+ };
2390
+ if (shouldWarn) log.warn(message, meta);
2391
+ else log.trace(message, meta);
2392
+ }
2393
+ };
2094
2394
  }
2395
+ //#endregion
2396
+ //#region extensions/codex/src/app-server/thread-supervision.ts
2095
2397
  async function materializePendingSupervisionBranch(params) {
2096
2398
  let pending = params.binding.pendingSupervisionBranch;
2097
2399
  const connectionFingerprint = buildCodexAppServerConnectionFingerprint(params.appServer, params.attempt.agentDir);
@@ -2364,408 +2666,480 @@ async function recoverPendingSupervisionArtifacts(params, pending) {
2364
2666
  expected: pending,
2365
2667
  pending: next
2366
2668
  })) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
2367
- return next;
2368
- }
2369
- async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
2370
- const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
2371
- if (!await params.bindingStore.mutate(params.bindingIdentity, {
2372
- kind: "patch-pending-supervision-branch",
2373
- expected: pending,
2374
- pending: next
2375
- })) {
2376
- const cleanupFailed = [];
2377
- for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
2378
- if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
2379
- throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
2380
- }
2381
- return next;
2382
- }
2383
- function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
2384
- return {
2385
- sourceThreadId: pending.sourceThreadId,
2386
- ...pending.connectionFingerprint ? { connectionFingerprint: pending.connectionFingerprint } : {},
2387
- ...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
2388
- ...cleanupThreadIds.length > 0 ? { cleanupThreadIds } : {}
2389
- };
2390
- }
2391
- async function cleanPendingSupervisionArtifacts(client, pending) {
2392
- const remaining = [];
2393
- for (const threadId of pending.cleanupThreadIds ?? []) if (!await archiveSupervisionArtifact(client, threadId)) remaining.push(threadId);
2394
- return { remaining };
2395
- }
2396
- async function archiveSupervisionArtifact(client, threadId) {
2397
- try {
2398
- await client.request("thread/archive", { threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
2399
- return true;
2400
- } catch (error) {
2401
- const message = formatErrorMessage(error).toLowerCase();
2402
- if (message.includes("no rollout found for thread id") || message.includes("thread not found") || message.includes("already archived")) return true;
2403
- await unsubscribeCodexThreadBestEffort(client, {
2404
- threadId,
2405
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
2406
- });
2407
- embeddedAgentLog.warn("failed to archive temporary Codex supervision thread", {
2408
- threadId,
2409
- error
2410
- });
2411
- return false;
2412
- }
2413
- }
2414
- function shouldRotateCodexAppServerBindingForRuntime(params) {
2415
- if (!params.current) return false;
2416
- if (params.binding === params.current) return false;
2417
- return params.connectionClass === "remote" || Boolean(params.binding);
2418
- }
2419
- function resolveCodexGpt56MultiAgentVersion(modelRef) {
2420
- let modelId = modelRef?.trim().toLowerCase();
2421
- if (!modelId) return;
2422
- const slashIndex = modelId.indexOf("/");
2423
- if (slashIndex > 0) {
2424
- const provider = modelId.slice(0, slashIndex);
2425
- if (provider !== "openai" && provider !== "codex") return;
2426
- modelId = modelId.slice(slashIndex + 1);
2427
- }
2428
- if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
2429
- return modelId === "gpt-5.6-luna" ? "v1" : void 0;
2430
- }
2431
- function shouldRotateCodexGpt56MultiAgentBinding(params) {
2432
- const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
2433
- const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
2434
- return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
2435
- }
2436
- function isTransientWebSearchRestriction(params) {
2437
- if (params.nativeProviderWebSearchSupport === "unknown") return true;
2438
- if (params.params.config?.tools?.web?.search?.enabled === false) return false;
2439
- if (params.params.disableTools === true) return true;
2440
- const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
2441
- if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
2442
- if (params.webSearchAllowed !== false) return false;
2443
- if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
2444
- if (params.params.toolsAllow === void 0) return false;
2445
- return !params.params.toolsAllow.some((name) => {
2446
- const normalized = normalizeCodexDynamicToolName(name);
2447
- return normalized === "*" || normalized === "web_search";
2448
- });
2449
- }
2450
- function buildContextEngineBinding(params, projection) {
2451
- const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
2452
- const engineId = contextEngine?.info?.id?.trim();
2453
- if (!contextEngine || !engineId) return;
2454
- return {
2455
- schemaVersion: 1,
2456
- engineId,
2457
- policyFingerprint: JSON.stringify({
2458
- schemaVersion: 1,
2459
- engineId,
2460
- engineVersion: contextEngine.info.version,
2461
- ownsCompaction: contextEngine.info.ownsCompaction === true,
2462
- turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
2463
- citationsMode: resolveContextEngineCitationsMode(params.config),
2464
- contextTokenBudget: params.contextTokenBudget,
2465
- projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
2466
- contextTokenBudget: params.contextTokenBudget,
2467
- reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
2468
- })
2469
- }),
2470
- projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
2471
- };
2472
- }
2473
- function buildContextEngineProjectionBinding(projection) {
2474
- return {
2475
- schemaVersion: 1,
2476
- mode: "thread_bootstrap",
2477
- epoch: projection.epoch,
2478
- fingerprint: projection.fingerprint
2479
- };
2480
- }
2481
- function isContextEngineBindingCompatible(previous, next) {
2482
- return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
2483
- }
2484
- function areContextEngineProjectionBindingsCompatible(previous, next) {
2485
- if (!next) return previous === void 0;
2486
- return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
2487
- }
2488
- function resolveContextEngineCitationsMode(config) {
2489
- const rootConfig = isUnknownRecord(config) ? config : void 0;
2490
- const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
2491
- return isJsonConfigValue(citations) ? citations : void 0;
2492
- }
2493
- function isUnknownRecord(value) {
2494
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
2495
- }
2496
- function isJsonConfigValue(value) {
2497
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
2498
- if (typeof value === "number") return Number.isFinite(value);
2499
- if (Array.isArray(value)) return value.every(isJsonConfigValue);
2500
- return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
2501
- }
2502
- function shouldRecheckRecoverablePluginBinding(params) {
2503
- if (!params.pluginThreadConfig?.enabled) return false;
2504
- if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
2505
- const policyContext = params.binding.pluginAppPolicyContext;
2506
- if (!policyContext) return false;
2507
- const expectedPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
2508
- return Object.keys(policyContext.apps).length === 0 || expectedPluginConfigKeys.length > 0;
2509
- }
2510
- function buildThreadStartParams(params, options) {
2511
- const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
2512
- const resolvedModelProvider = resolveCodexAppServerModelProvider({
2513
- provider: params.provider,
2514
- authProfileId: params.authProfileId,
2515
- authProfileStore: params.authProfileStore,
2516
- agentDir: params.agentDir,
2517
- config: params.config
2518
- });
2519
- const modelSelection = resolveCodexAppServerRequestModelSelection({
2520
- model: options.model ?? params.modelId,
2521
- modelProvider: options.modelProvider ?? resolvedModelProvider,
2522
- authProfileId: params.authProfileId,
2523
- authProfileStore: params.authProfileStore,
2524
- agentDir: params.agentDir,
2525
- config: params.config
2526
- });
2527
- return {
2528
- model: modelSelection.model,
2529
- ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
2530
- cwd: options.cwd,
2531
- approvalPolicy: options.appServer.approvalPolicy,
2532
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2533
- ...codexThreadSandboxOrPermissions(options.appServer),
2534
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2535
- personality: CODEX_NATIVE_PERSONALITY_NONE,
2536
- serviceName: "OpenClaw",
2537
- ...ringZeroActive ? { baseInstructions: CODEX_RING_ZERO_BASE_INSTRUCTIONS } : {},
2538
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2539
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2540
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2541
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2542
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
2543
- webSearchAllowed: options.webSearchAllowed,
2544
- appServer: options.appServer,
2545
- hostSystemAgentActive: options.hostSystemAgentActive,
2546
- ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
2547
- }),
2548
- ...resolveCodexThreadEnvironmentSelection(options),
2549
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
2550
- dynamicTools: [...options.dynamicTools],
2551
- experimentalRawEvents: true
2552
- };
2669
+ return next;
2553
2670
  }
2554
- function buildThreadResumeParams(params, options) {
2555
- const modelSelection = options.preserveNativeModel ? void 0 : resolveCodexAppServerRequestModelSelection({
2556
- model: options.model ?? params.modelId,
2557
- modelProvider: options.modelProvider ?? resolveCodexAppServerModelProvider({
2558
- provider: params.provider,
2559
- authProfileId: options.authProfileId ?? params.authProfileId,
2560
- authProfileStore: params.authProfileStore,
2561
- agentDir: params.agentDir,
2562
- config: params.config
2563
- }),
2564
- authProfileId: options.authProfileId ?? params.authProfileId,
2565
- authProfileStore: params.authProfileStore,
2566
- agentDir: params.agentDir,
2567
- config: params.config
2568
- });
2671
+ async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
2672
+ const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
2673
+ if (!await params.bindingStore.mutate(params.bindingIdentity, {
2674
+ kind: "patch-pending-supervision-branch",
2675
+ expected: pending,
2676
+ pending: next
2677
+ })) {
2678
+ const cleanupFailed = [];
2679
+ for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
2680
+ if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
2681
+ throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
2682
+ }
2683
+ return next;
2684
+ }
2685
+ function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
2569
2686
  return {
2570
- threadId: options.threadId,
2571
- ...modelSelection ? {
2572
- model: modelSelection.model,
2573
- ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
2574
- } : {},
2575
- approvalPolicy: options.appServer.approvalPolicy,
2576
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2577
- ...codexThreadSandboxOrPermissions(options.appServer),
2578
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2579
- personality: CODEX_NATIVE_PERSONALITY_NONE,
2580
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2581
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2582
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2583
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2584
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
2585
- webSearchAllowed: options.webSearchAllowed,
2586
- appServer: options.appServer,
2587
- hostSystemAgentActive: options.hostSystemAgentActive,
2588
- ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
2589
- }),
2590
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
2687
+ sourceThreadId: pending.sourceThreadId,
2688
+ ...pending.connectionFingerprint ? { connectionFingerprint: pending.connectionFingerprint } : {},
2689
+ ...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
2690
+ ...cleanupThreadIds.length > 0 ? { cleanupThreadIds } : {}
2591
2691
  };
2592
2692
  }
2593
- function resolveCodexBindingModelProviderFallback(params) {
2594
- const provider = params.provider?.trim().toLowerCase();
2595
- if (provider && provider !== "codex") return;
2596
- const currentModel = params.currentModel?.trim();
2597
- const bindingModel = params.bindingModel?.trim();
2598
- if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
2599
- return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
2693
+ async function cleanPendingSupervisionArtifacts(client, pending) {
2694
+ const remaining = [];
2695
+ for (const threadId of pending.cleanupThreadIds ?? []) if (!await archiveSupervisionArtifact(client, threadId)) remaining.push(threadId);
2696
+ return { remaining };
2600
2697
  }
2601
- function resolveCodexAppServerThreadModelSelection(params) {
2602
- const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
2603
- const explicitModelProvider = resolveCodexAppServerModelProvider({
2604
- provider: params.provider,
2605
- authProfileId,
2606
- authProfileStore: params.authProfileStore,
2607
- agentDir: params.agentDir,
2608
- config: params.config
2609
- });
2610
- const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
2611
- provider: params.provider,
2612
- currentModel: params.model,
2613
- bindingModel: params.binding.model,
2614
- bindingModelProvider: params.binding.modelProvider
2615
- }) : void 0;
2616
- return resolveCodexAppServerRequestModelSelection({
2617
- model: params.model,
2618
- modelProvider: explicitModelProvider ?? bindingModelProvider,
2619
- authProfileId,
2620
- authProfileStore: params.authProfileStore,
2621
- agentDir: params.agentDir,
2622
- config: params.config
2623
- });
2698
+ async function archiveSupervisionArtifact(client, threadId) {
2699
+ try {
2700
+ await client.request("thread/archive", { threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
2701
+ return true;
2702
+ } catch (error) {
2703
+ const message = formatErrorMessage(error).toLowerCase();
2704
+ if (message.includes("no rollout found for thread id") || message.includes("thread not found") || message.includes("already archived")) return true;
2705
+ await unsubscribeCodexThreadBestEffort(client, {
2706
+ threadId,
2707
+ timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
2708
+ });
2709
+ embeddedAgentLog.warn("failed to archive temporary Codex supervision thread", {
2710
+ threadId,
2711
+ error
2712
+ });
2713
+ return false;
2714
+ }
2624
2715
  }
2625
- function resolveCodexAppServerRequestModelSelection(params) {
2626
- const model = params.model.trim();
2627
- const modelProvider = params.modelProvider?.trim();
2628
- if (modelProvider) return {
2629
- model,
2630
- modelProvider
2631
- };
2632
- const slashIndex = model.indexOf("/");
2633
- if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
2634
- const inferredModelProvider = resolveCodexAppServerModelProvider({
2635
- provider: model.slice(0, slashIndex),
2636
- authProfileId: params.authProfileId,
2637
- authProfileStore: params.authProfileStore,
2638
- agentDir: params.agentDir,
2639
- config: params.config
2716
+ //#endregion
2717
+ //#region extensions/codex/src/app-server/thread-lifecycle-run.ts
2718
+ async function startOrResumeThread(params) {
2719
+ const bindingIdentity = sessionBindingIdentity({
2720
+ sessionId: params.params.sessionId,
2721
+ sessionKey: params.params.sessionKey,
2722
+ agentId: params.agentId ?? params.params.agentId,
2723
+ config: params.params.config
2724
+ });
2725
+ return await params.bindingStore.withLease(bindingIdentity, async () => {
2726
+ const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
2727
+ ...params.timing,
2728
+ enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
2729
+ });
2730
+ const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
2731
+ const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
2732
+ const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
2733
+ const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
2734
+ config: params.params.config,
2735
+ disableTools: params.params.disableTools,
2736
+ nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
2737
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2738
+ webSearchAllowed: params.webSearchAllowed
2739
+ })).threadConfig);
2740
+ const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
2741
+ const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
2742
+ const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
2743
+ agentId: params.agentId ?? params.params.agentId,
2744
+ agentDir: params.params.agentDir,
2745
+ allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
2746
+ onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
2747
+ serverName,
2748
+ error: formatErrorMessage(error)
2749
+ })
2750
+ });
2751
+ const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2752
+ const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2753
+ const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
2754
+ const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
2755
+ const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
2756
+ if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
2757
+ const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
2758
+ if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
2759
+ const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
2760
+ version: 1,
2761
+ baseInstructions: "",
2762
+ config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
2763
+ }) : void 0;
2764
+ const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
2765
+ let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
2766
+ const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
2767
+ authProfileId,
2768
+ modelProvider,
2769
+ authProfileStore: params.params.authProfileStore,
2770
+ agentDir: params.params.agentDir,
2771
+ config: params.params.config
2772
+ });
2773
+ const throwIfAborted = () => {
2774
+ if (!params.signal?.aborted) return;
2775
+ const reason = params.signal.reason;
2776
+ if (reason instanceof Error) throw reason;
2777
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
2778
+ error.name = "AbortError";
2779
+ throw error;
2780
+ };
2781
+ if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
2782
+ if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
2783
+ bindingStore: params.bindingStore,
2784
+ identity: bindingIdentity,
2785
+ config: params.params.config
2786
+ }))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
2787
+ }
2788
+ if (binding?.pendingSupervisionBranch) {
2789
+ const pendingBinding = binding;
2790
+ const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2791
+ const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2792
+ configPatch: params.finalConfigPatch,
2793
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
2794
+ };
2795
+ const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
2796
+ return await materializePendingSupervisionBranch({
2797
+ client: params.client,
2798
+ abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
2799
+ bindingStore: params.bindingStore,
2800
+ bindingIdentity,
2801
+ binding: pendingBinding,
2802
+ attempt: params.params,
2803
+ cwd: params.cwd,
2804
+ dynamicTools: params.dynamicTools,
2805
+ appServer: params.appServer,
2806
+ developerInstructions: params.developerInstructions,
2807
+ config,
2808
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2809
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2810
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2811
+ webSearchAllowed: params.webSearchAllowed,
2812
+ environmentSelection: params.environmentSelection,
2813
+ signal: params.signal,
2814
+ throwIfAborted,
2815
+ lifecycleTiming,
2816
+ normalizeBindingModelProvider,
2817
+ bindingPatch: {
2818
+ cwd: params.cwd,
2819
+ authProfileId: void 0,
2820
+ preserveNativeModel: true,
2821
+ dynamicToolsFingerprint,
2822
+ dynamicToolsContainDeferred,
2823
+ webSearchThreadConfigFingerprint,
2824
+ userMcpServersFingerprint,
2825
+ mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
2826
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
2827
+ networkProxyConfigFingerprint,
2828
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2829
+ appServerRuntimeFingerprint: buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir),
2830
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2831
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2832
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2833
+ contextEngine: contextEngineBinding,
2834
+ environmentSelectionFingerprint,
2835
+ conversationSourceTransferComplete: true
2836
+ }
2837
+ });
2838
+ }
2839
+ const clearCurrentBinding = async (operation) => {
2840
+ const current = binding;
2841
+ if (!current?.threadId) return;
2842
+ assertCodexBindingMayBeReplaced(current, operation);
2843
+ if (!await params.bindingStore.mutate(bindingIdentity, {
2844
+ kind: "clear",
2845
+ threadId: current.threadId
2846
+ })) throw new CodexThreadBindingConflictError(current.threadId, operation);
2847
+ binding = void 0;
2848
+ };
2849
+ if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
2850
+ embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
2851
+ await clearCurrentBinding("rotating a ring-zero thread binding");
2852
+ }
2853
+ if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
2854
+ connectionClass: params.appServer.connectionClass,
2855
+ current: binding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
2856
+ binding: binding.appServerRuntimeFingerprint
2857
+ })) {
2858
+ embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
2859
+ threadId: binding.threadId,
2860
+ connectionClass: params.appServer.connectionClass
2861
+ });
2862
+ await clearCurrentBinding("rotating a stale thread binding");
2863
+ binding = void 0;
2864
+ }
2865
+ if (binding?.threadId && shouldRotateCodexGpt56MultiAgentBinding({
2866
+ bindingModel: binding.model,
2867
+ requestedModel: params.params.modelId
2868
+ })) {
2869
+ embeddedAgentLog.debug("codex app-server GPT-5.6 multi-agent version changed; starting a new thread", {
2870
+ threadId: binding.threadId,
2871
+ bindingModel: binding.model,
2872
+ requestedModel: params.params.modelId
2873
+ });
2874
+ await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
2875
+ binding = void 0;
2876
+ }
2877
+ const startModelSelection = resolveCodexAppServerThreadModelSelection({
2878
+ provider: params.params.provider,
2879
+ model: params.params.modelId,
2880
+ binding,
2881
+ authProfileId: params.params.authProfileId,
2882
+ authProfileStore: params.params.authProfileStore,
2883
+ agentDir: params.params.agentDir,
2884
+ config: params.params.config
2885
+ });
2886
+ const startModelProvider = startModelSelection.modelProvider;
2887
+ let preserveExistingBinding = !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
2888
+ let rotatedContextEngineBinding = false;
2889
+ let prebuiltPluginThreadConfig;
2890
+ const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
2891
+ const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
2892
+ const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
2893
+ const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
2894
+ const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
2895
+ const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
2896
+ if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
2897
+ assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
2898
+ if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
2899
+ embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
2900
+ preserveExistingBinding = true;
2901
+ } else {
2902
+ embeddedAgentLog.debug("codex app-server MCP config changed; starting a new thread", { threadId: binding.threadId });
2903
+ await clearCurrentBinding("rotating a stale thread binding");
2904
+ }
2905
+ binding = void 0;
2906
+ }
2907
+ const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
2908
+ if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
2909
+ assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
2910
+ if (!ringZeroActive && transientWebSearchRestriction) {
2911
+ embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
2912
+ preserveExistingBinding = true;
2913
+ } else {
2914
+ embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
2915
+ await clearCurrentBinding("rotating a stale thread binding");
2916
+ }
2917
+ binding = void 0;
2918
+ }
2919
+ if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
2920
+ assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
2921
+ embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
2922
+ preserveExistingBinding = true;
2923
+ binding = void 0;
2924
+ }
2925
+ if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
2926
+ if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
2927
+ embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
2928
+ threadId: binding.threadId,
2929
+ engineId: contextEngineBinding?.engineId,
2930
+ previousEngineId: binding.contextEngine?.engineId,
2931
+ epoch: contextEngineBinding?.projection?.epoch,
2932
+ previousEpoch: binding.contextEngine?.projection?.epoch,
2933
+ fingerprint: contextEngineBinding?.projection?.fingerprint,
2934
+ previousFingerprint: binding.contextEngine?.projection?.fingerprint,
2935
+ policyFingerprint: contextEngineBinding?.policyFingerprint,
2936
+ previousPolicyFingerprint: binding.contextEngine?.policyFingerprint
2937
+ });
2938
+ await clearCurrentBinding("rotating a stale thread binding");
2939
+ binding = void 0;
2940
+ rotatedContextEngineBinding = true;
2941
+ }
2942
+ }
2943
+ if (binding?.threadId && !areUserMcpServersFingerprintsCompatible({
2944
+ previous: binding.userMcpServersFingerprint,
2945
+ next: userMcpServersFingerprint,
2946
+ nextLegacy: legacyUserMcpServersFingerprint
2947
+ })) {
2948
+ embeddedAgentLog.debug("codex app-server user MCP config changed; starting a new thread", { threadId: binding.threadId });
2949
+ await clearCurrentBinding("rotating a stale thread binding");
2950
+ binding = void 0;
2951
+ }
2952
+ if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
2953
+ embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
2954
+ await clearCurrentBinding("rotating a stale thread binding");
2955
+ binding = void 0;
2956
+ }
2957
+ if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
2958
+ embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
2959
+ await clearCurrentBinding("rotating a stale thread binding");
2960
+ binding = void 0;
2961
+ }
2962
+ if (binding?.threadId) {
2963
+ let pluginBindingStale = isCodexPluginThreadBindingStale({
2964
+ codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
2965
+ bindingFingerprint: binding.pluginAppsFingerprint,
2966
+ bindingInputFingerprint: binding.pluginAppsInputFingerprint,
2967
+ currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
2968
+ hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
2969
+ });
2970
+ if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
2971
+ binding,
2972
+ pluginThreadConfig: params.pluginThreadConfig
2973
+ })) try {
2974
+ prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build());
2975
+ pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
2976
+ } catch (error) {
2977
+ embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
2978
+ error,
2979
+ threadId: binding.threadId
2980
+ });
2981
+ }
2982
+ if (pluginBindingStale) {
2983
+ embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
2984
+ await clearCurrentBinding("rotating a stale thread binding");
2985
+ binding = void 0;
2986
+ }
2987
+ }
2988
+ if (binding?.threadId) {
2989
+ if (binding.dynamicToolsFingerprint && params.dynamicTools.length > 0 && binding.dynamicToolsContainDeferred !== dynamicToolsContainDeferred && (binding.dynamicToolsContainDeferred !== void 0 || !dynamicToolsContainDeferred)) {
2990
+ embeddedAgentLog.debug("codex app-server dynamic tool loading changed; starting a new thread", { threadId: binding.threadId });
2991
+ await clearCurrentBinding("rotating a stale thread binding");
2992
+ binding = void 0;
2993
+ }
2994
+ }
2995
+ if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
2996
+ assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
2997
+ preserveExistingBinding = shouldStartTransientNoToolThread({
2998
+ previous: binding.dynamicToolsFingerprint,
2999
+ nextHasDynamicTools: params.dynamicTools.length > 0
3000
+ });
3001
+ if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
3002
+ else {
3003
+ embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
3004
+ await clearCurrentBinding("rotating a stale thread binding");
3005
+ }
3006
+ } else {
3007
+ const resumed = await resumeExistingCodexThread(params, {
3008
+ binding,
3009
+ bindingIdentity,
3010
+ startModelSelection,
3011
+ startModelProvider,
3012
+ userMcpServersConfigPatch,
3013
+ dynamicToolsFingerprint,
3014
+ dynamicToolsContainDeferred,
3015
+ webSearchThreadConfigFingerprint,
3016
+ userMcpServersFingerprint,
3017
+ ringZeroConfigFingerprint,
3018
+ ringZeroClientInstanceId,
3019
+ networkProxyConfigFingerprint,
3020
+ contextEngineBinding,
3021
+ environmentSelectionFingerprint,
3022
+ hostSystemAgentActive,
3023
+ ringZeroActive,
3024
+ ringZeroInheritedMcpServerNames,
3025
+ lifecycleTiming,
3026
+ normalizeBindingModelProvider,
3027
+ throwIfAborted,
3028
+ clearCurrentBinding
3029
+ });
3030
+ if (resumed) return resumed;
3031
+ }
3032
+ return await startFreshCodexThread(params, {
3033
+ bindingIdentity,
3034
+ startModelSelection,
3035
+ startModelProvider,
3036
+ userMcpServersConfigPatch,
3037
+ dynamicToolsFingerprint,
3038
+ dynamicToolsContainDeferred,
3039
+ webSearchThreadConfigFingerprint,
3040
+ userMcpServersFingerprint,
3041
+ ringZeroConfigFingerprint,
3042
+ ringZeroClientInstanceId,
3043
+ networkProxyConfigFingerprint,
3044
+ contextEngineBinding,
3045
+ environmentSelectionFingerprint,
3046
+ hostSystemAgentActive,
3047
+ ringZeroActive,
3048
+ ringZeroInheritedMcpServerNames,
3049
+ lifecycleTiming,
3050
+ normalizeBindingModelProvider,
3051
+ throwIfAborted,
3052
+ prebuiltPluginThreadConfig,
3053
+ preserveExistingBinding,
3054
+ rotatedContextEngineBinding
3055
+ });
2640
3056
  });
2641
- return {
2642
- model: model.slice(slashIndex + 1).trim(),
2643
- ...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
2644
- };
2645
- }
2646
- function hasProviderQualifiedModelRef(model) {
2647
- const trimmed = model?.trim();
2648
- const slashIndex = trimmed?.indexOf("/") ?? -1;
2649
- return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
2650
- }
2651
- function buildCodexRuntimeThreadConfig(config, options = {}) {
2652
- const codeModeConfig = {
2653
- ...CODEX_CODE_MODE_THREAD_CONFIG,
2654
- "features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
2655
- };
2656
- if (options.nativeCodeModeEnabled === false) {
2657
- const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
2658
- delete disabledConfig["features.apply_patch_streaming_events"];
2659
- return disabledConfig;
2660
- }
2661
- if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, { "features.code_mode_only": true }) ?? {
2662
- ...codeModeConfig,
2663
- "features.code_mode_only": true
2664
- }, options.directOnlyToolNamespaces);
2665
- return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config) ?? { ...codeModeConfig }, options.directOnlyToolNamespaces);
2666
- }
2667
- function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
2668
- if (!requiredNamespaces?.length) return config;
2669
- const configured = config["code_mode.direct_only_tool_namespaces"];
2670
- const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
2671
- return {
2672
- ...config,
2673
- "code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
2674
- };
2675
- }
2676
- function resolveDirectOnlyToolNamespaces(dynamicTools) {
2677
- return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
2678
- }
2679
- function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2680
- const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
2681
- const configMcpServers = config?.mcp_servers;
2682
- if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
2683
- const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
2684
- const webSearchConfig = resolveCodexWebSearchPlan({
2685
- config: params.config,
2686
- disableTools: params.disableTools,
2687
- nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
2688
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2689
- webSearchAllowed: options.webSearchAllowed
2690
- }).threadConfig;
2691
- const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
2692
- const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
2693
- if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
2694
- return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
2695
- ...runtimeConfig,
2696
- ...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
2697
- };
2698
- }
2699
- function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
2700
- if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
2701
- const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
2702
- return {
2703
- ...CODEX_RING_ZERO_THREAD_CONFIG,
2704
- ...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
2705
- };
2706
3057
  }
2707
- async function readCodexInheritedMcpServerNames(client, cwd, signal) {
2708
- const response = await client.request("config/read", {
2709
- cwd,
2710
- includeLayers: true
2711
- }, { signal });
2712
- if (!isJsonObject(response) || !isJsonObject(response.config)) throw new Error("Codex config/read returned an invalid effective config");
2713
- if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
2714
- for (const layer of response.layers) {
2715
- if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
2716
- if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex ring-zero cannot override config layer ${layer.name.type}`);
2717
- if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex ring-zero does not recognize config layer ${layer.name.type}`);
2718
- }
2719
- const configuredServers = response.config.mcp_servers;
2720
- if (configuredServers === void 0) return [];
2721
- if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
2722
- return Object.keys(configuredServers).toSorted();
3058
+ //#endregion
3059
+ //#region extensions/codex/src/app-server/image-payload-sanitizer.ts
3060
+ /**
3061
+ * Sanitizes inline image payloads mirrored through Codex history so invalid
3062
+ * base64 data becomes readable text instead of poisoning replayed transcripts.
3063
+ */
3064
+ const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
3065
+ /** Validates and normalizes an inline image data URL for Codex history payloads. */
3066
+ function sanitizeInlineImageDataUrl$1(imageUrl) {
3067
+ return sanitizeInlineImageDataUrl(imageUrl);
2723
3068
  }
2724
- async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
2725
- const response = await client.request("configRequirements/read", void 0, { signal });
2726
- if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
2727
- if (response.requirements === null) return;
2728
- if (!isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
2729
- for (const key of [
2730
- "hooks",
2731
- "managedHooks",
2732
- "managed_hooks"
2733
- ]) {
2734
- const hooks = response.requirements[key];
2735
- if (hooks === void 0 || hooks === null) continue;
2736
- if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
2737
- if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex ring-zero cannot override managed hooks");
3069
+ /** Builds the replacement text inserted when an inline image payload is invalid. */
3070
+ function invalidInlineImageText(label) {
3071
+ return `[${label}] ${IMAGE_OMITTED_TEXT}`;
3072
+ }
3073
+ function sanitizeImageContentRecord(record, label) {
3074
+ if (record.type === "image" && typeof record.data === "string") {
3075
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
3076
+ const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
3077
+ if (!imageUrl) return {
3078
+ type: "text",
3079
+ text: invalidInlineImageText(label)
3080
+ };
3081
+ const commaIndex = imageUrl.indexOf(",");
3082
+ const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
3083
+ return {
3084
+ ...record,
3085
+ mimeType: mime,
3086
+ data: imageUrl.slice(commaIndex + 1)
3087
+ };
2738
3088
  }
2739
- for (const key of ["featureRequirements", "feature_requirements"]) {
2740
- const requirements = response.requirements[key];
2741
- if (requirements === void 0 || requirements === null) continue;
2742
- if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
2743
- for (const [feature, enabled] of Object.entries(requirements)) {
2744
- if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
2745
- if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(feature)) throw new Error(`Codex ring-zero cannot override required feature ${feature}`);
2746
- }
3089
+ if (record.type === "inputImage" && typeof record.imageUrl === "string") {
3090
+ const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
3091
+ return imageUrl ? {
3092
+ ...record,
3093
+ imageUrl
3094
+ } : {
3095
+ type: "inputText",
3096
+ text: invalidInlineImageText(label)
3097
+ };
2747
3098
  }
2748
- }
2749
- async function attestCodexRingZeroThreadHasNoMcpServers(client, threadId, signal) {
2750
- const response = await client.request("mcpServerStatus/list", {
2751
- threadId,
2752
- limit: 1,
2753
- detail: "toolsAndAuthOnly"
2754
- }, { signal });
2755
- if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid ring-zero attestation");
2756
- if (response.data.length > 0) {
2757
- const first = response.data[0];
2758
- const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
2759
- throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
3099
+ if (record.type === "input_image" && typeof record.image_url === "string") {
3100
+ const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
3101
+ return imageUrl ? {
3102
+ ...record,
3103
+ image_url: imageUrl
3104
+ } : {
3105
+ type: "input_text",
3106
+ text: invalidInlineImageText(label)
3107
+ };
2760
3108
  }
2761
- if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
2762
3109
  }
2763
- function hasNonEmptyJsonValue(value) {
2764
- if (value === null || value === false || value === "") return false;
2765
- if (Array.isArray(value)) return value.length > 0;
2766
- if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
2767
- return true;
3110
+ /** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
3111
+ function sanitizeCodexHistoryImagePayloads(value, label) {
3112
+ if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
3113
+ if (!isRecord(value)) return value;
3114
+ const imageRecord = sanitizeImageContentRecord(value, label);
3115
+ if (imageRecord) return imageRecord;
3116
+ const next = {};
3117
+ for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
3118
+ return next;
3119
+ }
3120
+ //#endregion
3121
+ //#region extensions/codex/src/app-server/user-input.ts
3122
+ /** Builds ordered Codex user input for both new turns and same-turn steering. */
3123
+ function buildCodexUserInput(text, images) {
3124
+ const imageInputs = (images ?? []).map((image) => {
3125
+ const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
3126
+ return imageUrl ? {
3127
+ type: "image",
3128
+ url: imageUrl
3129
+ } : {
3130
+ type: "text",
3131
+ text: invalidInlineImageText("codex user input"),
3132
+ text_elements: []
3133
+ };
3134
+ });
3135
+ return [...text === void 0 ? [] : [{
3136
+ type: "text",
3137
+ text,
3138
+ text_elements: []
3139
+ }], ...imageInputs];
2768
3140
  }
3141
+ //#endregion
3142
+ //#region extensions/codex/src/app-server/turn-params.ts
2769
3143
  function buildTurnStartParams(params, options) {
2770
3144
  const modelSelection = options.preserveNativeTurnSettings ? void 0 : resolveCodexAppServerRequestModelSelection({
2771
3145
  model: options.model ?? params.modelId,
@@ -2778,7 +3152,7 @@ function buildTurnStartParams(params, options) {
2778
3152
  const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
2779
3153
  return {
2780
3154
  threadId: options.threadId,
2781
- input: buildUserInput(params, options.promptText),
3155
+ input: buildCodexUserInput(options.promptText ?? params.prompt, params.images),
2782
3156
  cwd: options.cwd,
2783
3157
  approvalPolicy: options.appServer.approvalPolicy,
2784
3158
  approvalsReviewer: options.appServer.approvalsReviewer,
@@ -2799,18 +3173,6 @@ function buildTurnStartParams(params, options) {
2799
3173
  }) } : {}
2800
3174
  };
2801
3175
  }
2802
- function resolveCodexThreadApprovalsReviewer(appServer, config) {
2803
- return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
2804
- }
2805
- function codexThreadSandboxOrPermissions(appServer) {
2806
- if (appServer.networkProxy) return {};
2807
- return { sandbox: appServer.sandbox };
2808
- }
2809
- function resolveCodexThreadEnvironmentSelection(options) {
2810
- if (options.nativeCodeModeEnabled === false) return { environments: [] };
2811
- if (options.environmentSelection) return { environments: options.environmentSelection };
2812
- return {};
2813
- }
2814
3176
  function buildTurnCollaborationMode(params, options = {}) {
2815
3177
  const model = options.model ?? params.modelId;
2816
3178
  return {
@@ -2856,165 +3218,11 @@ function buildHeartbeatCollaborationInstructions() {
2856
3218
  return [
2857
3219
  "This is an OpenClaw heartbeat turn. Apply these instructions only to this heartbeat wake; ordinary chat turns should stay in Codex Default mode.",
2858
3220
  "When you are ready to end the heartbeat, prefer the structured `heartbeat_respond` tool so OpenClaw can record the wake outcome and notification decision. If `heartbeat_respond` is not already available and `tool_search` is available, search for `heartbeat_respond`, load it, then call it. Use `notify=false` when nothing should visibly interrupt the user.",
2859
- CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY
3221
+ GPT5_HEARTBEAT_PROMPT_OVERLAY
2860
3222
  ].join("\n\n");
2861
3223
  }
2862
3224
  function joinPresentSections(...sections) {
2863
3225
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
2864
3226
  }
2865
- function codexDynamicToolsFingerprint(dynamicTools) {
2866
- return fingerprintDynamicTools(dynamicTools);
2867
- }
2868
- function codexLegacyDynamicToolsFingerprint(dynamicTools) {
2869
- return legacyFingerprintDynamicTools(dynamicTools);
2870
- }
2871
- function areCodexDynamicToolFingerprintsCompatible(params) {
2872
- return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
2873
- }
2874
- function fingerprintDynamicTools(dynamicTools) {
2875
- return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
2876
- }
2877
- function legacyFingerprintDynamicTools(dynamicTools) {
2878
- return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
2879
- }
2880
- function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
2881
- return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
2882
- }
2883
- function fingerprintUserMcpServersConfigPatch(configPatch) {
2884
- return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
2885
- }
2886
- function redactUserMcpServersFingerprintSecrets(value) {
2887
- if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
2888
- if (!value || typeof value !== "object") return value;
2889
- const next = {};
2890
- for (const [key, entry] of Object.entries(value)) {
2891
- if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
2892
- next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
2893
- continue;
2894
- }
2895
- next[key] = redactUserMcpServersFingerprintSecrets(entry);
2896
- }
2897
- return next;
2898
- }
2899
- function fingerprintUserMcpServersAuthorizationHeader(value) {
2900
- return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
2901
- }
2902
- function fingerprintJsonObject(value) {
2903
- return JSON.stringify(stabilizeJsonValue(value));
2904
- }
2905
- function fingerprintEnvironmentSelection(environments) {
2906
- return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
2907
- }
2908
- function fingerprintDynamicToolSpec(tool) {
2909
- return stabilizeDynamicToolFingerprintValue(tool);
2910
- }
2911
- function stabilizeDynamicToolFingerprintValue(value) {
2912
- if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
2913
- if (!isJsonObject(value)) return value;
2914
- const stable = {};
2915
- for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
2916
- if (key === "description") continue;
2917
- stable[key] = stabilizeDynamicToolFingerprintValue(child);
2918
- }
2919
- return stable;
2920
- }
2921
- function stabilizeJsonValue(value) {
2922
- if (Array.isArray(value)) return value.map(stabilizeJsonValue);
2923
- if (!isJsonObject(value)) return value;
2924
- const stable = {};
2925
- for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
2926
- return stable;
2927
- }
2928
- function readActiveCodexTurnIds(thread) {
2929
- return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
2930
- }
2931
- const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
2932
- const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
2933
- function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
2934
- return !previous || previous === next || previous === nextLegacy;
2935
- }
2936
- function areUserMcpServersFingerprintsCompatible(params) {
2937
- return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
2938
- }
2939
- function shouldStartTransientNoToolThread(params) {
2940
- return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
2941
- }
2942
- function isEmptyDynamicToolsFingerprint(fingerprint) {
2943
- return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
2944
- }
2945
- function compareJsonFingerprint(left, right) {
2946
- return JSON.stringify(left).localeCompare(JSON.stringify(right));
2947
- }
2948
- function buildDeveloperInstructions(params, options = {}) {
2949
- const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
2950
- surface: "codex_app_server",
2951
- includeLegacyGlobalGuidance: false
2952
- }).join("\n");
2953
- return [
2954
- "You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
2955
- buildDeferredDynamicToolManifest(options.dynamicTools),
2956
- buildSkillWorkshopInstruction(options.dynamicTools),
2957
- "Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.",
2958
- buildVisibleReplyInstruction(params, options.dynamicTools),
2959
- nativeCommandGuidance,
2960
- params.extraSystemPrompt
2961
- ].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
2962
- }
2963
- function buildDeferredDynamicToolManifest(dynamicTools) {
2964
- const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
2965
- if (deferredToolNames.length === 0) return;
2966
- return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
2967
- }
2968
- function buildSkillWorkshopInstruction(dynamicTools) {
2969
- if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
2970
- return buildSkillWorkshopPromptSection().join("\n");
2971
- }
2972
- function buildVisibleReplyInstruction(params, dynamicTools) {
2973
- const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
2974
- if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. Do not repeat that visible content in your final answer.";
2975
- if (messageToolAvailable) return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation. Use `message` only for explicit out-of-band sends, media/file sends, or sends to a different target.";
2976
- return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
2977
- }
2978
- function buildUserInput(params, promptText = params.prompt) {
2979
- const imageInputs = (params.images ?? []).map((image) => {
2980
- const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
2981
- return imageUrl ? {
2982
- type: "image",
2983
- url: imageUrl
2984
- } : {
2985
- type: "text",
2986
- text: invalidInlineImageText("codex user input"),
2987
- text_elements: []
2988
- };
2989
- });
2990
- return [{
2991
- type: "text",
2992
- text: promptText,
2993
- text_elements: []
2994
- }, ...imageInputs];
2995
- }
2996
- function resolveCodexAppServerModelProvider(params) {
2997
- const normalized = params.provider.trim();
2998
- const normalizedLower = normalized.toLowerCase();
2999
- if (!normalized || normalizedLower === "codex") return;
3000
- if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
3001
- return normalizedLower === "openai" ? "openai" : normalized;
3002
- }
3003
- function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
3004
- if (thinkLevel === "off" || thinkLevel === "adaptive") return null;
3005
- if (supportedReasoningEfforts) return resolveCodexSupportedReasoningEffort({
3006
- requested: thinkLevel,
3007
- supportedReasoningEfforts
3008
- }) ?? null;
3009
- const fallbackReasoningEfforts = resolveCodexFallbackReasoningEfforts(modelId);
3010
- if (fallbackReasoningEfforts) return resolveCodexSupportedReasoningEffort({
3011
- requested: thinkLevel,
3012
- supportedReasoningEfforts: fallbackReasoningEfforts
3013
- }) ?? null;
3014
- if (thinkLevel === "minimal") return isModernCodexModel(modelId) ? "low" : "minimal";
3015
- if (thinkLevel === "low" || thinkLevel === "medium" || thinkLevel === "high" || thinkLevel === "xhigh") return thinkLevel;
3016
- if (thinkLevel === "max" && isMaxReasoningCodexModel(modelId)) return "max";
3017
- return null;
3018
- }
3019
3227
  //#endregion
3020
- export { fitCodexProjectedContextForTurnStart as A, unsubscribeCodexThreadBestEffort as B, sanitizeInlineImageDataUrl$1 as C, normalizeCodexDynamicToolName as D, isSystemAgentOnlyCodexDynamicToolAllowlist as E, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as F, shouldBuildCodexPluginThreadConfig as G, buildCodexPluginThreadConfig as H, CodexAppServerUnsafeSubscriptionError as I, ensureCodexPluginActivation as K, closeCodexStartupClientBestEffort as L, resolveCodexContextEngineProjectionMaxChars as M, resolveCodexContextEngineProjectionReserveTokens as N, resolveCodexDynamicToolsLoading as O, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as P, interruptCodexTurnBestEffort as R, sanitizeCodexHistoryImagePayloads as S, isForcedPrivateQaCodexRuntime as T, buildCodexPluginThreadConfigInputFingerprint as U, buildCodexPluginAppsConfigPatchFromPolicyContext as V, mergeCodexThreadConfigs as W, buildCodexNativeWebSearchThreadConfig as _, buildDeveloperInstructions as a, isCodexAppServerProfilerEnabled as b, codexDynamicToolsFingerprint as c, resolveCodexAppServerModelProvider as d, resolveCodexAppServerRequestModelSelection as f, startOrResumeThread as g, resolveReasoningEffort as h, buildContextEngineBinding as i, projectContextEngineAssemblyForCodex as j, resolveCodexDynamicToolsLoadingForRuntime as k, codexLegacyDynamicToolsFingerprint as l, resolveCodexBindingModelProviderFallback as m, areCodexDynamicToolFingerprintsCompatible as n, buildTurnCollaborationMode as o, resolveCodexAppServerThreadModelSelection as p, pluginReadParams as q, buildCodexRuntimeThreadConfig as r, buildTurnStartParams as s, CODEX_NATIVE_PERSONALITY_NONE as t, isContextEngineBindingCompatible as u, resolveCodexWebSearchPlan as v, filterCodexDynamicTools as w, invalidInlineImageText as x, resumeCodexAppServerThread as y, retireCodexAppServerClientAfterTimedOutTurn as z };
3228
+ export { CodexAppServerUnsafeSubscriptionError as A, fitCodexProjectedContextForTurnStart as B, isForcedPrivateQaCodexRuntime as C, resolveCodexDynamicToolsLoadingForRuntime as D, resolveCodexDynamicToolsLoading as E, areCodexDynamicToolFingerprintsCompatible as F, buildCodexPluginThreadConfig as G, resolveCodexContextEngineProjectionMaxChars as H, codexDynamicToolsFingerprint as I, mergeCodexThreadConfigs as J, buildCodexPluginThreadConfigInputFingerprint as K, codexLegacyDynamicToolsFingerprint as L, interruptCodexTurnBestEffort as M, retireCodexAppServerClientAfterTimedOutTurn as N, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as O, unsubscribeCodexThreadBestEffort as P, resolveRecoverableCodexPluginConfigKeys as Q, buildContextEngineBinding as R, filterCodexDynamicToolsWithOpenClawShell as S, normalizeCodexDynamicToolName as T, resolveCodexContextEngineProjectionReserveTokens as U, projectContextEngineAssemblyForCodex as V, buildCodexPluginAppsConfigPatchFromPolicyContext as W, ensureCodexPluginActivation as X, shouldBuildCodexPluginThreadConfig as Y, pluginReadParams as Z, resolveCodexBindingModelProviderFallback as _, sanitizeCodexHistoryImagePayloads as a, isCodexAppServerProfilerEnabled as b, resumeCodexAppServerThread as c, resolveCodexWebSearchPlan as d, buildDeveloperInstructions as f, resolveCodexAppServerThreadModelSelection as g, resolveCodexAppServerRequestModelSelection as h, invalidInlineImageText as i, closeCodexStartupClientBestEffort as j, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as k, buildCodexRuntimeThreadConfig as l, resolveCodexAppServerModelProvider as m, buildTurnStartParams as n, sanitizeInlineImageDataUrl$1 as o, CODEX_NATIVE_PERSONALITY_NONE as p, buildCodexPluginThreadConfigTimeoutFallback as q, buildCodexUserInput as r, startOrResumeThread as s, buildTurnCollaborationMode as t, buildCodexNativeWebSearchThreadConfig as u, resolveReasoningEffort as v, isSystemAgentOnlyCodexDynamicToolAllowlist as w, filterCodexDynamicTools as x, readCodexSupportedReasoningEfforts as y, isContextEngineBindingCompatible as z };