@openclaw/codex 2026.7.1-beta.1 → 2026.7.1-beta.4

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 (39) hide show
  1. package/dist/{app-server-policy-xi0ZmBGO.js → app-server-policy-BUk0GLMy.js} +5 -5
  2. package/dist/{attempt-notifications-l9LElGOU.js → attempt-notifications-BGsEIIDI.js} +5 -40
  3. package/dist/{command-handlers-vZy8xX5X.js → command-handlers-UVniG5Hl.js} +267 -232
  4. package/dist/command-rpc-BjgEPh19.js +784 -0
  5. package/dist/compact-CZzbcygx.js +603 -0
  6. package/dist/{computer-use-FPGG-mtX.js → computer-use-DDeySrnb.js} +3 -3
  7. package/dist/{config-C_whBx3M.js → config-CYEDnLJ2.js} +36 -15
  8. package/dist/conversation-binding-data-BHRpYbX3.js +121 -0
  9. package/dist/doctor-contract-api.js +439 -22
  10. package/dist/harness.js +29 -10
  11. package/dist/index.js +1119 -28
  12. package/dist/{media-understanding-provider-C5yYzx_6.js → media-understanding-provider-DgqRvU2_.js} +13 -8
  13. package/dist/media-understanding-provider.js +1 -1
  14. package/dist/{models-Ccy1_7nN.js → models-DDmO1zwd.js} +2 -2
  15. package/dist/notification-correlation-Bo7KB3ks.js +35 -0
  16. package/dist/{plugin-app-cache-key-CDioyur-.js → plugin-app-cache-key-o-AHbdaf.js} +8 -4
  17. package/dist/{protocol-dh-ETiNd.js → protocol-2POPqAY4.js} +2 -1
  18. package/dist/{protocol-validators-CB4mtyrs.js → protocol-validators-dZQ-UTOa.js} +117 -62
  19. package/dist/{provider-capabilities-CE5k8cmO.js → provider-capabilities-gTCwjfmh.js} +2920 -114
  20. package/dist/{provider-DosqwRb6.js → provider-cc_62eQE.js} +19 -10
  21. package/dist/provider.js +2 -2
  22. package/dist/{request-BQuSQF29.js → request-BcJyl8KL.js} +2 -2
  23. package/dist/{run-attempt-O3Zs32KI.js → run-attempt-8SPLiDlT.js} +1016 -2877
  24. package/dist/{sandbox-guard-pTY2COoC.js → sandbox-guard-DA2TQfZW.js} +3 -3
  25. package/dist/session-binding-Dc03iwRF.js +595 -0
  26. package/dist/session-binding-meta-B7aEMU7g.js +6 -0
  27. package/dist/{shared-client-rR6TDJA3.js → shared-client-4ICy3U6d.js} +892 -103
  28. package/dist/{side-question-DDVgFmpv.js → side-question-DkjXvYv7.js} +132 -140
  29. package/dist/{thread-lifecycle-DeSMZSXr.js → thread-lifecycle-BskXnNP-.js} +609 -444
  30. package/dist/{web-search-provider.runtime-Q89rQhsL.js → web-search-provider.runtime-B5F42P7x.js} +3 -3
  31. package/npm-shrinkwrap.json +34 -34
  32. package/openclaw.plugin.json +20 -0
  33. package/package.json +9 -6
  34. package/dist/client-IcTBKEqA.js +0 -732
  35. package/dist/client-factory-CwjdQQ4j.js +0 -18
  36. package/dist/compact-BzB9AqN1.js +0 -278
  37. package/dist/conversation-binding-CEE2UyYb.js +0 -1570
  38. package/dist/rate-limit-cache-C7qmZ0Jh.js +0 -26
  39. package/dist/session-binding-Cm0Gg7Z1.js +0 -339
@@ -1,79 +1,22 @@
1
- import { n as isJsonObject, t as flattenCodexDynamicToolFunctions } from "./protocol-dh-ETiNd.js";
2
- import { i as codexSandboxPolicyForTurn, m as resolveCodexPluginsPolicy, t as CODEX_PLUGINS_MARKETPLACE_NAME } from "./config-C_whBx3M.js";
3
- import { n as assertCodexThreadResumeResponse, r as assertCodexThreadStartResponse } from "./protocol-validators-CB4mtyrs.js";
1
+ import { n as flattenCodexDynamicToolFunctions, r as isJsonObject } from "./protocol-2POPqAY4.js";
2
+ import { h as resolveCodexPluginsPolicy, i as codexSandboxPolicyForTurn, t as CODEX_PLUGINS_MARKETPLACE_NAME } from "./config-CYEDnLJ2.js";
3
+ import { n as assertCodexThreadResumeResponse, r as assertCodexThreadStartResponse } from "./protocol-validators-dZQ-UTOa.js";
4
4
  import { CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "./prompt-overlay.js";
5
- import { a as readCodexSupportedReasoningEfforts, i as isModernCodexModel, o as resolveCodexSupportedReasoningEffort, r as isMaxReasoningCodexModel } from "./provider-DosqwRb6.js";
6
- import { a as isCodexAppServerConnectionClosedError, n as CodexAppServerRpcError } from "./client-IcTBKEqA.js";
7
- import { i as isCodexAppServerNativeAuthProfile, l as writeCodexAppServerBinding, n as clearCodexAppServerBinding, o as readCodexAppServerBinding } from "./session-binding-Cm0Gg7Z1.js";
5
+ import { a as readCodexSupportedReasoningEfforts, i as isModernCodexModel, o as resolveCodexFallbackReasoningEfforts, r as isMaxReasoningCodexModel, s as resolveCodexSupportedReasoningEffort } from "./provider-cc_62eQE.js";
6
+ import { f as isCodexAppServerConnectionClosedError, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, s as retireSharedCodexAppServerClientIfCurrent, t as clearSharedCodexAppServerClientIfCurrent, u as CodexAppServerRpcError } from "./shared-client-4ICy3U6d.js";
7
+ import { a as reclaimCurrentCodexSessionGeneration, i as normalizeCodexAppServerBindingModelProvider, o as sessionBindingIdentity, r as isCodexAppServerNativeAuthProfile } from "./session-binding-Dc03iwRF.js";
8
8
  import "node:fs/promises";
9
9
  import "node:path";
10
10
  import { isFutureDateTimestampMs, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
11
- import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
11
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
12
12
  import crypto from "node:crypto";
13
+ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
13
14
  import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine } from "openclaw/plugin-sdk/agent-harness-runtime";
14
15
  import { buildCodexUserMcpServersThreadConfigPatch } from "openclaw/plugin-sdk/codex-mcp-projection";
15
16
  import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
16
17
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
17
18
  import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
18
19
  import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
19
- //#region extensions/codex/src/app-server/notification-correlation.ts
20
- /**
21
- * Correlates Codex app-server notifications with the active thread/turn so
22
- * projectors can ignore global or stale events without losing diagnostics.
23
- */
24
- /** Returns true when a notification payload belongs to the exact active thread and turn. */
25
- function isCodexNotificationForTurn(value, threadId, turnId) {
26
- if (!isJsonObject(value)) return false;
27
- return readCodexNotificationThreadId(value) === threadId && readCodexNotificationTurnId(value) === turnId;
28
- }
29
- /** Reads a thread id from either top-level notification params or nested turn payloads. */
30
- function readCodexNotificationThreadId(record) {
31
- return readNestedTurnThreadId(record) ?? readString(record, "threadId");
32
- }
33
- /** Reads a turn id from either top-level notification params or nested turn payloads. */
34
- function readCodexNotificationTurnId(record) {
35
- return readNestedTurnId(record) ?? readString(record, "turnId");
36
- }
37
- /** Builds structured correlation details for logs when notification routing is ambiguous. */
38
- function describeCodexNotificationCorrelation(notification, active) {
39
- const params = isJsonObject(notification.params) ? notification.params : void 0;
40
- const turn = params && isJsonObject(params.turn) ? params.turn : void 0;
41
- const threadId = params ? readString(params, "threadId") : void 0;
42
- const turnId = params ? readString(params, "turnId") : void 0;
43
- const nestedTurnThreadId = turn ? readString(turn, "threadId") : void 0;
44
- const nestedTurnId = turn ? readString(turn, "id") : void 0;
45
- const resolvedThreadId = params ? readCodexNotificationThreadId(params) : void 0;
46
- const resolvedTurnId = params ? readCodexNotificationTurnId(params) : void 0;
47
- const matchesActiveThread = resolvedThreadId === active.threadId;
48
- const matchesActiveTurn = active.turnId ? matchesActiveThread && resolvedTurnId === active.turnId : void 0;
49
- const items = turn?.items;
50
- return {
51
- method: notification.method,
52
- ...params ? { paramsKeys: Object.keys(params).toSorted() } : {},
53
- activeThreadId: active.threadId,
54
- ...active.turnId ? { activeTurnId: active.turnId } : {},
55
- ...threadId ? { threadId } : {},
56
- ...turnId ? { turnId } : {},
57
- ...nestedTurnThreadId ? { nestedTurnThreadId } : {},
58
- ...nestedTurnId ? { nestedTurnId } : {},
59
- ...turn ? { turnStatus: readString(turn, "status") } : {},
60
- ...Array.isArray(items) ? { turnItemCount: items.length } : {},
61
- matchesActiveThread,
62
- ...matchesActiveTurn === void 0 ? {} : { matchesActiveTurn }
63
- };
64
- }
65
- function readNestedTurnId(record) {
66
- const turn = record.turn;
67
- return isJsonObject(turn) ? readString(turn, "id") : void 0;
68
- }
69
- function readNestedTurnThreadId(record) {
70
- const turn = record.turn;
71
- return isJsonObject(turn) ? readString(turn, "threadId") : void 0;
72
- }
73
- function readString(record, key) {
74
- const value = record[key];
75
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
76
- }
77
20
  const CODEX_TARGETED_APP_INVENTORY_LIMIT = 1e3;
78
21
  const MAX_SERIALIZED_ERROR_MESSAGE_LENGTH = 500;
79
22
  /** In-memory app inventory cache with coalesced refreshes per key. */
@@ -275,7 +218,7 @@ function redactErrorData(value, depth = 0) {
275
218
  for (const [key, entry] of Object.entries(value)) redacted[key] = isSensitiveErrorDataKey(key) ? "<redacted>" : redactErrorData(entry, depth + 1) ?? null;
276
219
  return redacted;
277
220
  }
278
- if (typeof value === "string" && value.length > 500) return `${value.slice(0, 500)}...`;
221
+ if (typeof value === "string" && value.length > 500) return `${truncateUtf16Safe(value, 500)}...`;
279
222
  if (typeof value === "string") return value;
280
223
  if (typeof value === "bigint") return value.toString();
281
224
  if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol()";
@@ -576,20 +519,21 @@ function activationFailure(identity, reason, diagnostic, extraDiagnostics = [])
576
519
  //#endregion
577
520
  //#region extensions/codex/src/app-server/plugin-thread-config.ts
578
521
  /**
579
- * Builds Codex thread config patches that expose only policy-approved
580
- * plugin-owned apps for native Codex turns.
522
+ * Builds Codex thread config patches that expose only policy-approved apps
523
+ * for native Codex turns.
581
524
  */
582
- const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 2;
583
- const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 1;
525
+ const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 3;
526
+ const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
584
527
  /** Returns true when plugin config exists and thread config may need app patches. */
585
528
  function shouldBuildCodexPluginThreadConfig(pluginConfig) {
586
529
  return resolveCodexPluginsPolicy(pluginConfig).configured;
587
530
  }
588
531
  /** Fingerprints policy and app-cache identity before runtime inventory is read. */
589
532
  function buildCodexPluginThreadConfigInputFingerprint(params) {
533
+ const policy = resolveCodexPluginsPolicy(params.pluginConfig);
590
534
  return fingerprintJson({
591
535
  version: CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION,
592
- policy: policyFingerprint(resolveCodexPluginsPolicy(params.pluginConfig)),
536
+ policy: policyFingerprint(policy),
593
537
  appCacheKey: params.appCacheKey ?? null
594
538
  });
595
539
  }
@@ -606,7 +550,7 @@ async function buildCodexPluginThreadConfig(params) {
606
550
  inputFingerprint,
607
551
  configPatch: buildDisabledAppsConfigPatch()
608
552
  });
609
- let inventory = await readCodexPluginInventory({
553
+ let inventory = policy.pluginPolicies.length > 0 ? await readCodexPluginInventory({
610
554
  pluginConfig: params.pluginConfig,
611
555
  policy,
612
556
  request: params.request,
@@ -614,7 +558,7 @@ async function buildCodexPluginThreadConfig(params) {
614
558
  appCacheKey: params.appCacheKey,
615
559
  nowMs: params.nowMs,
616
560
  suppressAppInventoryRefresh: true
617
- });
561
+ }) : emptyCodexPluginInventory(policy);
618
562
  const appInventoryRefreshDeferredForActivation = inventory.records.some((record) => record.activationRequired) && shouldRefreshMissingAppInventory(params, policy, inventory);
619
563
  if (shouldWaitForInitialAppInventory(params, policy, inventory)) {
620
564
  await refreshAppInventoryNow(params, appCache, {
@@ -693,7 +637,12 @@ async function buildCodexPluginThreadConfig(params) {
693
637
  appCacheKey: params.appCacheKey
694
638
  });
695
639
  }
696
- const diagnostics = [...inventory.diagnostics, ...activationDiagnostics];
640
+ const accountAppsResult = policy.allowAllPlugins ? await readAccessibleAccountApps(params, appCache) : { apps: [] };
641
+ const diagnostics = [
642
+ ...inventory.diagnostics,
643
+ ...activationDiagnostics,
644
+ ...accountAppsResult.diagnostic ? [accountAppsResult.diagnostic] : []
645
+ ];
697
646
  const apps = { _default: {
698
647
  enabled: false,
699
648
  destructive_enabled: false,
@@ -701,6 +650,7 @@ async function buildCodexPluginThreadConfig(params) {
701
650
  } };
702
651
  const policyApps = {};
703
652
  const pluginAppIds = {};
653
+ const pluginOwnedAppIds = new Set(inventory.records.flatMap((record) => record.appOwnership === "proven" ? record.ownedAppIds : []));
704
654
  for (const record of inventory.records) {
705
655
  const activation = activationResults.find((item) => item.identity.configKey === record.policy.configKey);
706
656
  if (activation?.ok === false || record.activationRequired && !activation?.ok) continue;
@@ -725,14 +675,7 @@ async function buildCodexPluginThreadConfig(params) {
725
675
  app,
726
676
  diagnostics
727
677
  })) continue;
728
- const appConfig = {
729
- enabled: true,
730
- destructive_enabled: record.policy.allowDestructiveActions,
731
- open_world_enabled: true,
732
- default_tools_approval_mode: "auto"
733
- };
734
- if (record.policy.destructiveApprovalMode === "ask") appConfig.approvals_reviewer = "user";
735
- apps[app.id] = appConfig;
678
+ apps[app.id] = buildEnabledAppConfig(record.policy);
736
679
  policyApps[app.id] = {
737
680
  configKey: record.policy.configKey,
738
681
  marketplaceName: record.policy.marketplaceName,
@@ -743,6 +686,24 @@ async function buildCodexPluginThreadConfig(params) {
743
686
  };
744
687
  }
745
688
  }
689
+ for (const app of accountAppsResult.apps) {
690
+ if (pluginOwnedAppIds.has(app.id)) continue;
691
+ const accountApp = toOwnedAccountApp(app);
692
+ if (policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
693
+ request: params.request,
694
+ configCwd: params.configCwd,
695
+ app: accountApp,
696
+ diagnostics
697
+ })) continue;
698
+ apps[app.id] = buildEnabledAppConfig(policy);
699
+ policyApps[app.id] = {
700
+ source: "account",
701
+ appName: app.name,
702
+ allowDestructiveActions: policy.allowDestructiveActions,
703
+ destructiveApprovalMode: policy.destructiveApprovalMode,
704
+ mcpServerNames: []
705
+ };
706
+ }
746
707
  const configPatch = { apps };
747
708
  const policyContext = buildPluginAppPolicyContext(policyApps, pluginAppIds);
748
709
  return {
@@ -798,6 +759,15 @@ function buildDisabledAppsConfigPatch() {
798
759
  open_world_enabled: false
799
760
  } } };
800
761
  }
762
+ function buildEnabledAppConfig(policy) {
763
+ return {
764
+ enabled: true,
765
+ destructive_enabled: policy.allowDestructiveActions,
766
+ open_world_enabled: true,
767
+ default_tools_approval_mode: "auto",
768
+ ...policy.destructiveApprovalMode === "ask" ? { approvals_reviewer: "user" } : {}
769
+ };
770
+ }
801
771
  /** Rebuilds the safe per-thread apps patch persisted with a Codex thread binding. */
802
772
  function buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext) {
803
773
  const apps = { _default: {
@@ -817,7 +787,7 @@ function buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext) {
817
787
  function buildPluginAppPolicyContext(apps, pluginAppIds) {
818
788
  return {
819
789
  fingerprint: fingerprintJson({
820
- version: 1,
790
+ version: 2,
821
791
  apps,
822
792
  pluginAppIds
823
793
  }),
@@ -839,7 +809,7 @@ async function clearPersistedAppToolApprovalOverrides(params) {
839
809
  } catch (error) {
840
810
  params.diagnostics.push({
841
811
  code: "approval_overrides_clear_failed",
842
- plugin: params.plugin,
812
+ ...params.plugin ? { plugin: params.plugin } : {},
843
813
  message: `Could not clear durable Codex app approval overrides for ${params.app.id}: ${error instanceof Error ? error.message : String(error)}`
844
814
  });
845
815
  return false;
@@ -898,6 +868,37 @@ async function refreshAppInventoryNow(params, appCache, options = {}) {
898
868
  function collectInventoryOwnedAppIds(inventory) {
899
869
  return Array.from(new Set(inventory.records.flatMap((record) => record.ownedAppIds).filter(Boolean))).toSorted();
900
870
  }
871
+ function emptyCodexPluginInventory(policy) {
872
+ return {
873
+ policy,
874
+ records: [],
875
+ diagnostics: []
876
+ };
877
+ }
878
+ async function readAccessibleAccountApps(params, appCache) {
879
+ const snapshot = await refreshAppInventoryNow(params, appCache, {
880
+ forceRefetch: false,
881
+ reason: "account_apps_all",
882
+ targetAppIds: []
883
+ });
884
+ if (!snapshot) return {
885
+ apps: [],
886
+ diagnostic: {
887
+ code: "account_app_inventory_unavailable",
888
+ message: "Codex account app inventory was unavailable; account apps were not exposed."
889
+ }
890
+ };
891
+ return { apps: snapshot.apps.filter((app) => app.isAccessible).toSorted((left, right) => left.id.localeCompare(right.id)) };
892
+ }
893
+ function toOwnedAccountApp(app) {
894
+ return {
895
+ id: app.id,
896
+ name: app.name,
897
+ accessible: app.isAccessible,
898
+ enabled: app.isEnabled,
899
+ needsAuth: !app.isAccessible
900
+ };
901
+ }
901
902
  function resolveThreadConfigAppsForRecord(params) {
902
903
  if (params.inventory.appInventory?.state === "missing") return [];
903
904
  return params.record.apps;
@@ -913,6 +914,7 @@ function shouldForceRefreshForNotReadyPluginApps(params, policy, inventory) {
913
914
  function policyFingerprint(policy) {
914
915
  return {
915
916
  enabled: policy.enabled,
917
+ allowAllPlugins: policy.allowAllPlugins,
916
918
  allowDestructiveActions: policy.allowDestructiveActions,
917
919
  destructiveApprovalMode: policy.destructiveApprovalMode,
918
920
  plugins: policy.pluginPolicies.map((plugin) => ({
@@ -945,6 +947,127 @@ function stableStringify(value) {
945
947
  return JSON.stringify(value);
946
948
  }
947
949
  //#endregion
950
+ //#region extensions/codex/src/app-server/attempt-client-cleanup.ts
951
+ /**
952
+ * Best-effort cleanup helpers for Codex app-server startup attempts and turns.
953
+ */
954
+ /** Timeout for best-effort app-server turn interruption during cleanup. */
955
+ const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
956
+ /** Timeout for best-effort thread unsubscribe during cleanup. */
957
+ const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
958
+ /** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
959
+ var CodexAppServerUnsafeSubscriptionError = class extends Error {
960
+ constructor(message, options) {
961
+ super(message, options);
962
+ this.name = "CodexAppServerUnsafeSubscriptionError";
963
+ }
964
+ };
965
+ function isCodexAppServerUnsafeSubscriptionError(error) {
966
+ return error instanceof CodexAppServerUnsafeSubscriptionError;
967
+ }
968
+ /** Asserts Codex resumed the exact thread this attempt subscribed to. */
969
+ function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
970
+ if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
971
+ }
972
+ async function closeClientAndWaitIfAvailable(client) {
973
+ const closeable = client;
974
+ if (typeof closeable.closeAndWait === "function") {
975
+ await closeable.closeAndWait();
976
+ return;
977
+ }
978
+ closeable.close?.();
979
+ }
980
+ async function closeCodexStartupClientBestEffort(client) {
981
+ if (!client) return;
982
+ const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
983
+ if (unclaimedSharedClient.closed) {
984
+ await closeClientAndWaitIfAvailable(client);
985
+ return;
986
+ }
987
+ if (unclaimedSharedClient.found) {
988
+ if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
989
+ return;
990
+ }
991
+ const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
992
+ if (retiredSharedClient) {
993
+ if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
994
+ return;
995
+ }
996
+ if (clearSharedCodexAppServerClientIfCurrent(client)) {
997
+ await closeClientAndWaitIfAvailable(client);
998
+ return;
999
+ }
1000
+ await closeClientAndWaitIfAvailable(client);
1001
+ }
1002
+ /** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
1003
+ function interruptCodexTurnBestEffort(client, params) {
1004
+ const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
1005
+ const requestParams = {
1006
+ threadId: params.threadId,
1007
+ turnId: params.turnId
1008
+ };
1009
+ try {
1010
+ const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
1011
+ Promise.resolve(interrupt).catch((error) => {
1012
+ embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
1013
+ });
1014
+ } catch (error) {
1015
+ embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
1016
+ }
1017
+ }
1018
+ /** Unsubscribes from a thread while swallowing cleanup-only failures. */
1019
+ async function unsubscribeCodexThreadBestEffort(client, params) {
1020
+ try {
1021
+ await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
1022
+ return true;
1023
+ } catch (error) {
1024
+ embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
1025
+ threadId: params.threadId,
1026
+ error
1027
+ });
1028
+ return false;
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Retires the shared client after a timed-out turn so later runs do not reuse a
1033
+ * potentially wedged app-server connection.
1034
+ */
1035
+ async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
1036
+ const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
1037
+ const detachedSharedClient = Boolean(retiredSharedClient);
1038
+ interruptCodexTurnBestEffort(client, {
1039
+ threadId: params.threadId,
1040
+ turnId: params.turnId,
1041
+ timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
1042
+ });
1043
+ await unsubscribeCodexThreadBestEffort(client, {
1044
+ threadId: params.threadId,
1045
+ timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
1046
+ });
1047
+ let closedClient = retiredSharedClient?.closed ?? false;
1048
+ if (!detachedSharedClient) {
1049
+ const close = client.close;
1050
+ if (typeof close === "function") try {
1051
+ close.call(client);
1052
+ closedClient = true;
1053
+ } catch (error) {
1054
+ embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
1055
+ threadId: params.threadId,
1056
+ turnId: params.turnId,
1057
+ error
1058
+ });
1059
+ }
1060
+ }
1061
+ embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
1062
+ threadId: params.threadId,
1063
+ turnId: params.turnId,
1064
+ reason: params.reason,
1065
+ detachedSharedClient,
1066
+ closedClient,
1067
+ activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
1068
+ });
1069
+ }
1070
+ //#endregion
948
1071
  //#region extensions/codex/src/app-server/context-engine-projection.ts
949
1072
  const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
950
1073
  const CONTEXT_OPEN = "<conversation_context>";
@@ -1111,7 +1234,7 @@ function renderToolResultPayload(record) {
1111
1234
  }
1112
1235
  return payload;
1113
1236
  }
1114
- const TOOL_PAYLOAD_METADATA_KEYS = new Set([
1237
+ const TOOL_PAYLOAD_METADATA_KEYS = /* @__PURE__ */ new Set([
1115
1238
  "type",
1116
1239
  "name",
1117
1240
  "id",
@@ -1345,6 +1468,31 @@ function isCodexAppServerProfilerEnabled(config, env = process.env) {
1345
1468
  return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
1346
1469
  }
1347
1470
  //#endregion
1471
+ //#region extensions/codex/src/app-server/thread-resume.ts
1472
+ /** Owns Codex thread/resume subscription safety. */
1473
+ /** Resumes one thread and retires the physical client when acceptance is indeterminate. */
1474
+ async function resumeCodexAppServerThread(params) {
1475
+ const threadId = params.request.threadId;
1476
+ let response;
1477
+ try {
1478
+ response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
1479
+ ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
1480
+ ...params.signal ? { signal: params.signal } : {}
1481
+ }));
1482
+ assertCodexThreadResumeSubscription(threadId, response.thread.id);
1483
+ } catch (error) {
1484
+ if (error instanceof CodexAppServerRpcError) throw error;
1485
+ try {
1486
+ await params.abandonClient();
1487
+ } catch (abandonError) {
1488
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
1489
+ }
1490
+ if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
1491
+ throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
1492
+ }
1493
+ return response;
1494
+ }
1495
+ //#endregion
1348
1496
  //#region extensions/codex/src/app-server/web-search.ts
1349
1497
  const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG = {
1350
1498
  "features.standalone_web_search": false,
@@ -1419,6 +1567,12 @@ var CodexThreadStartRequestError = class extends Error {
1419
1567
  this.name = "CodexThreadStartRequestError";
1420
1568
  }
1421
1569
  };
1570
+ var CodexThreadBindingConflictError = class extends Error {
1571
+ constructor(threadId, operation) {
1572
+ super(`Codex thread binding changed while ${operation}: ${threadId}`);
1573
+ this.name = "CodexThreadBindingConflictError";
1574
+ }
1575
+ };
1422
1576
  const CODEX_NATIVE_PERSONALITY_NONE = "none";
1423
1577
  const CODEX_CODE_MODE_THREAD_CONFIG = {
1424
1578
  "features.code_mode": true,
@@ -1519,228 +1673,365 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
1519
1673
  };
1520
1674
  }
1521
1675
  async function startOrResumeThread(params) {
1522
- const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
1523
- ...params.timing,
1524
- enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
1525
- });
1526
- const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => fingerprintDynamicTools(params.dynamicTools));
1527
- const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
1528
- const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
1529
- config: params.params.config,
1530
- disableTools: params.params.disableTools,
1531
- nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
1532
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1533
- webSearchAllowed: params.webSearchAllowed
1534
- })).threadConfig);
1535
- const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
1536
- const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
1537
- const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : buildCodexUserMcpServersThreadConfigPatch(params.params.config, { agentId: params.agentId ?? params.params.agentId });
1538
- const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
1539
- const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
1540
- let binding = await lifecycleTiming.measure("read-binding", () => readCodexAppServerBinding(params.params.sessionFile, {
1541
- authProfileStore: params.params.authProfileStore,
1542
- agentDir: params.params.agentDir,
1543
- config: params.params.config
1544
- }));
1545
- if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
1546
- connectionClass: params.appServer.connectionClass,
1547
- current: params.appServerRuntimeFingerprint,
1548
- binding: binding.appServerRuntimeFingerprint
1549
- })) {
1550
- embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
1551
- threadId: binding.threadId,
1552
- connectionClass: params.appServer.connectionClass
1553
- });
1554
- await clearCodexAppServerBinding(params.params.sessionFile);
1555
- binding = void 0;
1556
- }
1557
- const startModelSelection = resolveCodexAppServerThreadModelSelection({
1558
- provider: params.params.provider,
1559
- model: params.params.modelId,
1560
- binding,
1561
- authProfileId: params.params.authProfileId,
1562
- authProfileStore: params.params.authProfileStore,
1563
- agentDir: params.params.agentDir,
1676
+ const bindingIdentity = sessionBindingIdentity({
1677
+ sessionId: params.params.sessionId,
1678
+ sessionKey: params.params.sessionKey,
1679
+ agentId: params.agentId ?? params.params.agentId,
1564
1680
  config: params.params.config
1565
1681
  });
1566
- const startModelProvider = startModelSelection.modelProvider;
1567
- let preserveExistingBinding = params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
1568
- let rotatedContextEngineBinding = false;
1569
- let prebuiltPluginThreadConfig;
1570
- const throwIfAborted = () => {
1571
- if (!params.signal?.aborted) return;
1572
- const reason = params.signal.reason;
1573
- if (reason instanceof Error) throw reason;
1574
- const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
1575
- error.name = "AbortError";
1576
- throw error;
1577
- };
1578
- const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
1579
- const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
1580
- const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
1581
- const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
1582
- const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
1583
- const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
1584
- if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
1585
- if (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport)) {
1586
- embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
1587
- preserveExistingBinding = true;
1588
- } else {
1589
- embeddedAgentLog.debug("codex app-server MCP config changed; starting a new thread", { threadId: binding.threadId });
1590
- await clearCodexAppServerBinding(params.params.sessionFile);
1591
- }
1592
- binding = void 0;
1593
- }
1594
- const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
1595
- if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
1596
- if (transientWebSearchRestriction) {
1597
- embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
1598
- preserveExistingBinding = true;
1599
- } else {
1600
- embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
1601
- await clearCodexAppServerBinding(params.params.sessionFile);
1682
+ return await params.bindingStore.withLease(bindingIdentity, async () => {
1683
+ const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
1684
+ ...params.timing,
1685
+ enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
1686
+ });
1687
+ const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => fingerprintDynamicTools(params.dynamicTools));
1688
+ const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
1689
+ const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
1690
+ config: params.params.config,
1691
+ disableTools: params.params.disableTools,
1692
+ nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
1693
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1694
+ webSearchAllowed: params.webSearchAllowed
1695
+ })).threadConfig);
1696
+ const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
1697
+ const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
1698
+ const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : buildCodexUserMcpServersThreadConfigPatch(params.params.config, { agentId: params.agentId ?? params.params.agentId });
1699
+ const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
1700
+ const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
1701
+ let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
1702
+ const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
1703
+ authProfileId,
1704
+ modelProvider,
1705
+ authProfileStore: params.params.authProfileStore,
1706
+ agentDir: params.params.agentDir,
1707
+ config: params.params.config
1708
+ });
1709
+ if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
1710
+ if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
1711
+ bindingStore: params.bindingStore,
1712
+ identity: bindingIdentity,
1713
+ config: params.params.config
1714
+ }))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
1602
1715
  }
1603
- binding = void 0;
1604
- }
1605
- if (binding?.threadId && transientNativeToolRestriction) {
1606
- embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
1607
- preserveExistingBinding = true;
1608
- binding = void 0;
1609
- }
1610
- if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
1611
- if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
1612
- embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
1716
+ const clearCurrentBinding = async (operation) => {
1717
+ const current = binding;
1718
+ if (!current?.threadId) return;
1719
+ if (!await params.bindingStore.mutate(bindingIdentity, {
1720
+ kind: "clear",
1721
+ threadId: current.threadId
1722
+ })) throw new CodexThreadBindingConflictError(current.threadId, operation);
1723
+ binding = void 0;
1724
+ };
1725
+ if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
1726
+ connectionClass: params.appServer.connectionClass,
1727
+ current: params.appServerRuntimeFingerprint,
1728
+ binding: binding.appServerRuntimeFingerprint
1729
+ })) {
1730
+ embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
1613
1731
  threadId: binding.threadId,
1614
- engineId: contextEngineBinding?.engineId,
1615
- previousEngineId: binding.contextEngine?.engineId,
1616
- epoch: contextEngineBinding?.projection?.epoch,
1617
- previousEpoch: binding.contextEngine?.projection?.epoch,
1618
- fingerprint: contextEngineBinding?.projection?.fingerprint,
1619
- previousFingerprint: binding.contextEngine?.projection?.fingerprint,
1620
- policyFingerprint: contextEngineBinding?.policyFingerprint,
1621
- previousPolicyFingerprint: binding.contextEngine?.policyFingerprint
1732
+ connectionClass: params.appServer.connectionClass
1622
1733
  });
1623
- await clearCodexAppServerBinding(params.params.sessionFile);
1734
+ await clearCurrentBinding("rotating a stale thread binding");
1624
1735
  binding = void 0;
1625
- rotatedContextEngineBinding = true;
1626
1736
  }
1627
- }
1628
- if (binding?.threadId && binding.userMcpServersFingerprint !== userMcpServersFingerprint) {
1629
- embeddedAgentLog.debug("codex app-server user MCP config changed; starting a new thread", { threadId: binding.threadId });
1630
- await clearCodexAppServerBinding(params.params.sessionFile);
1631
- binding = void 0;
1632
- }
1633
- if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
1634
- embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
1635
- await clearCodexAppServerBinding(params.params.sessionFile);
1636
- binding = void 0;
1637
- }
1638
- if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
1639
- embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
1640
- await clearCodexAppServerBinding(params.params.sessionFile);
1641
- binding = void 0;
1642
- }
1643
- if (binding?.threadId) {
1644
- let pluginBindingStale = isCodexPluginThreadBindingStale({
1645
- codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
1646
- bindingFingerprint: binding.pluginAppsFingerprint,
1647
- bindingInputFingerprint: binding.pluginAppsInputFingerprint,
1648
- currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
1649
- hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
1650
- });
1651
- if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
1737
+ const startModelSelection = resolveCodexAppServerThreadModelSelection({
1738
+ provider: params.params.provider,
1739
+ model: params.params.modelId,
1652
1740
  binding,
1653
- pluginThreadConfig: params.pluginThreadConfig
1654
- })) try {
1655
- prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build());
1656
- pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
1657
- } catch (error) {
1658
- embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
1659
- error,
1660
- threadId: binding.threadId
1661
- });
1741
+ authProfileId: params.params.authProfileId,
1742
+ authProfileStore: params.params.authProfileStore,
1743
+ agentDir: params.params.agentDir,
1744
+ config: params.params.config
1745
+ });
1746
+ const startModelProvider = startModelSelection.modelProvider;
1747
+ let preserveExistingBinding = params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
1748
+ let rotatedContextEngineBinding = false;
1749
+ let prebuiltPluginThreadConfig;
1750
+ const throwIfAborted = () => {
1751
+ if (!params.signal?.aborted) return;
1752
+ const reason = params.signal.reason;
1753
+ if (reason instanceof Error) throw reason;
1754
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
1755
+ error.name = "AbortError";
1756
+ throw error;
1757
+ };
1758
+ const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
1759
+ const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
1760
+ const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
1761
+ const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
1762
+ const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
1763
+ const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
1764
+ if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
1765
+ if (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport)) {
1766
+ embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
1767
+ preserveExistingBinding = true;
1768
+ } else {
1769
+ embeddedAgentLog.debug("codex app-server MCP config changed; starting a new thread", { threadId: binding.threadId });
1770
+ await clearCurrentBinding("rotating a stale thread binding");
1771
+ }
1772
+ binding = void 0;
1662
1773
  }
1663
- if (pluginBindingStale) {
1664
- embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
1665
- await clearCodexAppServerBinding(params.params.sessionFile);
1774
+ const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
1775
+ if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
1776
+ if (transientWebSearchRestriction) {
1777
+ embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
1778
+ preserveExistingBinding = true;
1779
+ } else {
1780
+ embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
1781
+ await clearCurrentBinding("rotating a stale thread binding");
1782
+ }
1666
1783
  binding = void 0;
1667
1784
  }
1668
- }
1669
- if (binding?.threadId) {
1670
- if (binding.dynamicToolsFingerprint && params.dynamicTools.length > 0 && binding.dynamicToolsContainDeferred !== dynamicToolsContainDeferred && (binding.dynamicToolsContainDeferred !== void 0 || !dynamicToolsContainDeferred)) {
1671
- embeddedAgentLog.debug("codex app-server dynamic tool loading changed; starting a new thread", { threadId: binding.threadId });
1672
- await clearCodexAppServerBinding(params.params.sessionFile);
1785
+ if (binding?.threadId && transientNativeToolRestriction) {
1786
+ embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
1787
+ preserveExistingBinding = true;
1673
1788
  binding = void 0;
1674
1789
  }
1675
- }
1676
- if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint)) {
1677
- preserveExistingBinding = shouldStartTransientNoToolThread({
1678
- previous: binding.dynamicToolsFingerprint,
1679
- next: dynamicToolsFingerprint
1680
- });
1681
- if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
1682
- else {
1683
- embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
1684
- await clearCodexAppServerBinding(params.params.sessionFile);
1790
+ if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
1791
+ if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
1792
+ embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
1793
+ threadId: binding.threadId,
1794
+ engineId: contextEngineBinding?.engineId,
1795
+ previousEngineId: binding.contextEngine?.engineId,
1796
+ epoch: contextEngineBinding?.projection?.epoch,
1797
+ previousEpoch: binding.contextEngine?.projection?.epoch,
1798
+ fingerprint: contextEngineBinding?.projection?.fingerprint,
1799
+ previousFingerprint: binding.contextEngine?.projection?.fingerprint,
1800
+ policyFingerprint: contextEngineBinding?.policyFingerprint,
1801
+ previousPolicyFingerprint: binding.contextEngine?.policyFingerprint
1802
+ });
1803
+ await clearCurrentBinding("rotating a stale thread binding");
1804
+ binding = void 0;
1805
+ rotatedContextEngineBinding = true;
1806
+ }
1685
1807
  }
1686
- } else {
1687
- const resumeBinding = binding;
1688
- try {
1689
- const authProfileId = params.params.authProfileId ?? resumeBinding.authProfileId;
1690
- const finalConfigPatch = params.buildFinalConfigPatch?.({
1691
- action: "resume",
1692
- binding: resumeBinding
1693
- }) ?? {
1694
- configPatch: params.finalConfigPatch,
1695
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
1696
- };
1697
- const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
1698
- const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
1699
- const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
1700
- threadId: resumeBinding.threadId,
1701
- authProfileId,
1702
- model: startModelSelection.model,
1703
- modelProvider: startModelProvider,
1704
- appServer: params.appServer,
1705
- dynamicTools: params.dynamicTools,
1706
- developerInstructions: params.developerInstructions,
1707
- config: resumeConfig,
1708
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1709
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1710
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1711
- webSearchAllowed: params.webSearchAllowed
1712
- }));
1713
- const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
1714
- const response = assertCodexThreadResumeResponse(await lifecycleTiming.measure("thread-resume-request", () => params.client.request("thread/resume", resumeParams, { signal: params.signal })));
1715
- throwIfAborted();
1716
- const boundAuthProfileId = authProfileId;
1717
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
1718
- await lifecycleTiming.measure("thread-resume-write-binding", () => writeCodexAppServerBinding(params.params.sessionFile, {
1719
- threadId: response.thread.id,
1720
- cwd: params.cwd,
1721
- authProfileId: boundAuthProfileId,
1722
- model: response.model ?? resumeParams.model ?? params.params.modelId,
1723
- modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider,
1724
- dynamicToolsFingerprint,
1725
- dynamicToolsContainDeferred,
1726
- webSearchThreadConfigFingerprint,
1727
- userMcpServersFingerprint,
1728
- mcpServersFingerprint: nextMcpServersFingerprint,
1729
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
1730
- networkProxyConfigFingerprint,
1731
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
1732
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
1733
- pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
1734
- pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
1735
- pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
1736
- contextEngine: contextEngineBinding,
1737
- environmentSelectionFingerprint,
1738
- createdAt: resumeBinding.createdAt
1739
- }, {
1740
- authProfileStore: params.params.authProfileStore,
1741
- agentDir: params.params.agentDir,
1742
- config: params.params.config
1743
- }));
1808
+ if (binding?.threadId && binding.userMcpServersFingerprint !== userMcpServersFingerprint) {
1809
+ embeddedAgentLog.debug("codex app-server user MCP config changed; starting a new thread", { threadId: binding.threadId });
1810
+ await clearCurrentBinding("rotating a stale thread binding");
1811
+ binding = void 0;
1812
+ }
1813
+ if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
1814
+ embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
1815
+ await clearCurrentBinding("rotating a stale thread binding");
1816
+ binding = void 0;
1817
+ }
1818
+ if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
1819
+ embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
1820
+ await clearCurrentBinding("rotating a stale thread binding");
1821
+ binding = void 0;
1822
+ }
1823
+ if (binding?.threadId) {
1824
+ let pluginBindingStale = isCodexPluginThreadBindingStale({
1825
+ codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
1826
+ bindingFingerprint: binding.pluginAppsFingerprint,
1827
+ bindingInputFingerprint: binding.pluginAppsInputFingerprint,
1828
+ currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
1829
+ hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
1830
+ });
1831
+ if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
1832
+ binding,
1833
+ pluginThreadConfig: params.pluginThreadConfig
1834
+ })) try {
1835
+ prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build());
1836
+ pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
1837
+ } catch (error) {
1838
+ embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
1839
+ error,
1840
+ threadId: binding.threadId
1841
+ });
1842
+ }
1843
+ if (pluginBindingStale) {
1844
+ embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
1845
+ await clearCurrentBinding("rotating a stale thread binding");
1846
+ binding = void 0;
1847
+ }
1848
+ }
1849
+ if (binding?.threadId) {
1850
+ if (binding.dynamicToolsFingerprint && params.dynamicTools.length > 0 && binding.dynamicToolsContainDeferred !== dynamicToolsContainDeferred && (binding.dynamicToolsContainDeferred !== void 0 || !dynamicToolsContainDeferred)) {
1851
+ embeddedAgentLog.debug("codex app-server dynamic tool loading changed; starting a new thread", { threadId: binding.threadId });
1852
+ await clearCurrentBinding("rotating a stale thread binding");
1853
+ binding = void 0;
1854
+ }
1855
+ }
1856
+ if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint)) {
1857
+ preserveExistingBinding = shouldStartTransientNoToolThread({
1858
+ previous: binding.dynamicToolsFingerprint,
1859
+ next: dynamicToolsFingerprint
1860
+ });
1861
+ if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
1862
+ else {
1863
+ embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
1864
+ await clearCurrentBinding("rotating a stale thread binding");
1865
+ }
1866
+ } else {
1867
+ const resumeBinding = binding;
1868
+ let resumeReservation;
1869
+ try {
1870
+ const authProfileId = params.params.authProfileId ?? resumeBinding.authProfileId;
1871
+ const finalConfigPatch = params.buildFinalConfigPatch?.({
1872
+ action: "resume",
1873
+ binding: resumeBinding
1874
+ }) ?? {
1875
+ configPatch: params.finalConfigPatch,
1876
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
1877
+ };
1878
+ const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
1879
+ const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
1880
+ const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
1881
+ threadId: resumeBinding.threadId,
1882
+ authProfileId,
1883
+ model: startModelSelection.model,
1884
+ modelProvider: startModelProvider,
1885
+ appServer: params.appServer,
1886
+ dynamicTools: params.dynamicTools,
1887
+ developerInstructions: params.developerInstructions,
1888
+ config: resumeConfig,
1889
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1890
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1891
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1892
+ webSearchAllowed: params.webSearchAllowed
1893
+ }));
1894
+ const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
1895
+ throwIfAborted();
1896
+ resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
1897
+ const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
1898
+ client: params.client,
1899
+ abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
1900
+ request: resumeParams,
1901
+ signal: params.signal
1902
+ }));
1903
+ throwIfAborted();
1904
+ const boundAuthProfileId = authProfileId;
1905
+ const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
1906
+ const resumePatch = {
1907
+ cwd: params.cwd,
1908
+ authProfileId: boundAuthProfileId,
1909
+ model: response.model ?? resumeParams.model ?? params.params.modelId,
1910
+ modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
1911
+ dynamicToolsFingerprint,
1912
+ dynamicToolsContainDeferred,
1913
+ webSearchThreadConfigFingerprint,
1914
+ userMcpServersFingerprint,
1915
+ mcpServersFingerprint: nextMcpServersFingerprint,
1916
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
1917
+ networkProxyConfigFingerprint,
1918
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
1919
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
1920
+ pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
1921
+ pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
1922
+ pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
1923
+ contextEngine: contextEngineBinding,
1924
+ environmentSelectionFingerprint
1925
+ };
1926
+ if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
1927
+ kind: "patch",
1928
+ threadId: resumeBinding.threadId,
1929
+ patch: resumePatch
1930
+ }))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
1931
+ if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
1932
+ sessionId: params.params.sessionId,
1933
+ sessionKey: params.params.sessionKey,
1934
+ threadId: response.thread.id,
1935
+ engineId: contextEngineBinding.engineId,
1936
+ epoch: contextEngineBinding.projection?.epoch,
1937
+ fingerprint: contextEngineBinding.projection?.fingerprint,
1938
+ action: "resumed"
1939
+ });
1940
+ lifecycleTiming.mark("thread-ready");
1941
+ lifecycleTiming.logSummary({
1942
+ runId: params.params.runId,
1943
+ sessionId: params.params.sessionId,
1944
+ sessionKey: params.params.sessionKey,
1945
+ threadId: response.thread.id,
1946
+ action: "resumed"
1947
+ });
1948
+ const activeTurnIds = readActiveCodexTurnIds(response.thread);
1949
+ return {
1950
+ ...resumeBinding,
1951
+ threadId: response.thread.id,
1952
+ ...resumePatch,
1953
+ lifecycle: {
1954
+ action: "resumed",
1955
+ ...activeTurnIds.length ? { activeTurnIds } : {}
1956
+ }
1957
+ };
1958
+ } catch (error) {
1959
+ resumeReservation?.release();
1960
+ if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
1961
+ const resumeRejected = error instanceof CodexAppServerRpcError;
1962
+ if (!await unsubscribeCodexThreadBestEffort(params.client, {
1963
+ threadId: resumeBinding.threadId,
1964
+ timeoutMs: 5e3
1965
+ }) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
1966
+ if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
1967
+ embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
1968
+ await clearCurrentBinding("rotating a stale thread binding");
1969
+ }
1970
+ }
1971
+ const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
1972
+ const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
1973
+ configPatch: params.finalConfigPatch,
1974
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
1975
+ };
1976
+ const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
1977
+ const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
1978
+ cwd: params.cwd,
1979
+ dynamicTools: params.dynamicTools,
1980
+ appServer: params.appServer,
1981
+ developerInstructions: params.developerInstructions,
1982
+ config,
1983
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1984
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1985
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1986
+ webSearchAllowed: params.webSearchAllowed,
1987
+ environmentSelection: params.environmentSelection,
1988
+ model: startModelSelection.model,
1989
+ modelProvider: startModelProvider
1990
+ }));
1991
+ const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
1992
+ const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
1993
+ try {
1994
+ return await params.client.request("thread/start", startParams, { signal: params.signal });
1995
+ } catch (error) {
1996
+ if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
1997
+ throw error;
1998
+ }
1999
+ }));
2000
+ throwIfAborted();
2001
+ const modelProvider = resolveCodexAppServerModelProvider({
2002
+ provider: params.params.provider,
2003
+ authProfileId: params.params.authProfileId,
2004
+ authProfileStore: params.params.authProfileStore,
2005
+ agentDir: params.params.agentDir,
2006
+ config: params.params.config
2007
+ });
2008
+ const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2009
+ if (!preserveExistingBinding) {
2010
+ if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2011
+ kind: "set",
2012
+ if: { kind: "absent" },
2013
+ binding: {
2014
+ threadId: response.thread.id,
2015
+ cwd: params.cwd,
2016
+ authProfileId: params.params.authProfileId,
2017
+ model: response.model ?? startParams.model ?? params.params.modelId,
2018
+ modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
2019
+ dynamicToolsFingerprint,
2020
+ dynamicToolsContainDeferred,
2021
+ webSearchThreadConfigFingerprint,
2022
+ userMcpServersFingerprint,
2023
+ mcpServersFingerprint: nextMcpServersFingerprint,
2024
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
2025
+ networkProxyConfigFingerprint,
2026
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2027
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2028
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2029
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2030
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2031
+ contextEngine: contextEngineBinding,
2032
+ environmentSelectionFingerprint
2033
+ }
2034
+ }))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
1744
2035
  if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
1745
2036
  sessionId: params.params.sessionId,
1746
2037
  sessionKey: params.params.sessionKey,
@@ -1748,90 +2039,18 @@ async function startOrResumeThread(params) {
1748
2039
  engineId: contextEngineBinding.engineId,
1749
2040
  epoch: contextEngineBinding.projection?.epoch,
1750
2041
  fingerprint: contextEngineBinding.projection?.fingerprint,
1751
- action: "resumed"
2042
+ action: rotatedContextEngineBinding ? "rotated" : "started"
1752
2043
  });
1753
- lifecycleTiming.mark("thread-ready");
1754
- lifecycleTiming.logSummary({
1755
- runId: params.params.runId,
1756
- sessionId: params.params.sessionId,
1757
- sessionKey: params.params.sessionKey,
1758
- threadId: response.thread.id,
1759
- action: "resumed"
1760
- });
1761
- const activeTurnIds = readActiveCodexTurnIds(response.thread);
1762
- return {
1763
- ...resumeBinding,
1764
- threadId: response.thread.id,
1765
- cwd: params.cwd,
1766
- authProfileId: boundAuthProfileId,
1767
- model: response.model ?? resumeParams.model ?? params.params.modelId,
1768
- modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider,
1769
- dynamicToolsFingerprint,
1770
- dynamicToolsContainDeferred,
1771
- webSearchThreadConfigFingerprint,
1772
- userMcpServersFingerprint,
1773
- mcpServersFingerprint: nextMcpServersFingerprint,
1774
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
1775
- networkProxyConfigFingerprint,
1776
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
1777
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
1778
- pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
1779
- pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
1780
- pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
1781
- contextEngine: contextEngineBinding,
1782
- environmentSelectionFingerprint,
1783
- lifecycle: {
1784
- action: "resumed",
1785
- ...activeTurnIds.length ? { activeTurnIds } : {}
1786
- }
1787
- };
1788
- } catch (error) {
1789
- if (isCodexAppServerConnectionClosedError(error)) throw error;
1790
- embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
1791
- await clearCodexAppServerBinding(params.params.sessionFile);
1792
2044
  }
1793
- }
1794
- const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
1795
- const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
1796
- configPatch: params.finalConfigPatch,
1797
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
1798
- };
1799
- const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
1800
- const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
1801
- cwd: params.cwd,
1802
- dynamicTools: params.dynamicTools,
1803
- appServer: params.appServer,
1804
- developerInstructions: params.developerInstructions,
1805
- config,
1806
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1807
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1808
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1809
- webSearchAllowed: params.webSearchAllowed,
1810
- environmentSelection: params.environmentSelection,
1811
- model: startModelSelection.model,
1812
- modelProvider: startModelProvider
1813
- }));
1814
- const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
1815
- const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
1816
- try {
1817
- return await params.client.request("thread/start", startParams, { signal: params.signal });
1818
- } catch (error) {
1819
- if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
1820
- throw error;
1821
- }
1822
- }));
1823
- throwIfAborted();
1824
- const modelProvider = resolveCodexAppServerModelProvider({
1825
- provider: params.params.provider,
1826
- authProfileId: params.params.authProfileId,
1827
- authProfileStore: params.params.authProfileStore,
1828
- agentDir: params.params.agentDir,
1829
- config: params.params.config
1830
- });
1831
- const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1832
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
1833
- if (!preserveExistingBinding) {
1834
- await lifecycleTiming.measure("thread-start-write-binding", () => writeCodexAppServerBinding(params.params.sessionFile, {
2045
+ lifecycleTiming.mark("thread-ready");
2046
+ lifecycleTiming.logSummary({
2047
+ runId: params.params.runId,
2048
+ sessionId: params.params.sessionId,
2049
+ sessionKey: params.params.sessionKey,
2050
+ threadId: response.thread.id,
2051
+ action: rotatedContextEngineBinding ? "rotated" : "started"
2052
+ });
2053
+ return {
1835
2054
  threadId: response.thread.id,
1836
2055
  cwd: params.cwd,
1837
2056
  authProfileId: params.params.authProfileId,
@@ -1839,7 +2058,6 @@ async function startOrResumeThread(params) {
1839
2058
  modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
1840
2059
  dynamicToolsFingerprint,
1841
2060
  dynamicToolsContainDeferred,
1842
- webSearchThreadConfigFingerprint,
1843
2061
  userMcpServersFingerprint,
1844
2062
  mcpServersFingerprint: nextMcpServersFingerprint,
1845
2063
  networkProxyProfileName: params.appServer.networkProxy?.profileName,
@@ -1851,58 +2069,12 @@ async function startOrResumeThread(params) {
1851
2069
  pluginAppPolicyContext: pluginThreadConfig?.policyContext,
1852
2070
  contextEngine: contextEngineBinding,
1853
2071
  environmentSelectionFingerprint,
1854
- createdAt
1855
- }, {
1856
- authProfileStore: params.params.authProfileStore,
1857
- agentDir: params.params.agentDir,
1858
- config: params.params.config
1859
- }));
1860
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
1861
- sessionId: params.params.sessionId,
1862
- sessionKey: params.params.sessionKey,
1863
- threadId: response.thread.id,
1864
- engineId: contextEngineBinding.engineId,
1865
- epoch: contextEngineBinding.projection?.epoch,
1866
- fingerprint: contextEngineBinding.projection?.fingerprint,
1867
- action: rotatedContextEngineBinding ? "rotated" : "started"
1868
- });
1869
- }
1870
- lifecycleTiming.mark("thread-ready");
1871
- lifecycleTiming.logSummary({
1872
- runId: params.params.runId,
1873
- sessionId: params.params.sessionId,
1874
- sessionKey: params.params.sessionKey,
1875
- threadId: response.thread.id,
1876
- action: rotatedContextEngineBinding ? "rotated" : "started"
2072
+ lifecycle: {
2073
+ action: "started",
2074
+ ...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
2075
+ }
2076
+ };
1877
2077
  });
1878
- return {
1879
- schemaVersion: 2,
1880
- threadId: response.thread.id,
1881
- sessionFile: params.params.sessionFile,
1882
- cwd: params.cwd,
1883
- authProfileId: params.params.authProfileId,
1884
- model: response.model ?? startParams.model ?? params.params.modelId,
1885
- modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
1886
- dynamicToolsFingerprint,
1887
- dynamicToolsContainDeferred,
1888
- userMcpServersFingerprint,
1889
- mcpServersFingerprint: nextMcpServersFingerprint,
1890
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
1891
- networkProxyConfigFingerprint,
1892
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
1893
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
1894
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
1895
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
1896
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
1897
- contextEngine: contextEngineBinding,
1898
- environmentSelectionFingerprint,
1899
- createdAt,
1900
- updatedAt: createdAt,
1901
- lifecycle: {
1902
- action: "started",
1903
- ...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
1904
- }
1905
- };
1906
2078
  }
1907
2079
  function shouldRotateCodexAppServerBindingForRuntime(params) {
1908
2080
  if (!params.current) return false;
@@ -2018,21 +2190,10 @@ function buildThreadStartParams(params, options) {
2018
2190
  }),
2019
2191
  ...resolveCodexThreadEnvironmentSelection(options),
2020
2192
  developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
2021
- dynamicTools: toCodexThreadStartDynamicTools(options.dynamicTools),
2022
- experimentalRawEvents: true,
2023
- persistExtendedHistory: true
2193
+ dynamicTools: [...options.dynamicTools],
2194
+ experimentalRawEvents: true
2024
2195
  };
2025
2196
  }
2026
- function toCodexThreadStartDynamicTools(dynamicTools) {
2027
- return dynamicTools.flatMap((tool) => tool.type === "namespace" ? tool.tools.map((child) => toCodexLegacyDynamicTool(child, tool.name)) : [toCodexLegacyDynamicTool(tool)]);
2028
- }
2029
- function toCodexLegacyDynamicTool(tool, namespace) {
2030
- const { type: _type, ...legacyTool } = tool;
2031
- return namespace ? {
2032
- ...legacyTool,
2033
- namespace
2034
- } : legacyTool;
2035
- }
2036
2197
  function buildThreadResumeParams(params, options) {
2037
2198
  const resolvedModelProvider = resolveCodexAppServerModelProvider({
2038
2199
  provider: params.provider,
@@ -2065,8 +2226,7 @@ function buildThreadResumeParams(params, options) {
2065
2226
  webSearchAllowed: options.webSearchAllowed,
2066
2227
  appServer: options.appServer
2067
2228
  }),
2068
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
2069
- persistExtendedHistory: true
2229
+ developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
2070
2230
  };
2071
2231
  }
2072
2232
  function resolveCodexBindingModelProviderFallback(params) {
@@ -2313,7 +2473,7 @@ function buildDeveloperInstructions(params, options = {}) {
2313
2473
  "You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
2314
2474
  buildDeferredDynamicToolManifest(options.dynamicTools),
2315
2475
  buildSkillWorkshopInstruction(options.dynamicTools),
2316
- "Use Codex native `spawn_agent` for Codex subagents. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation.",
2476
+ "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`.",
2317
2477
  buildVisibleReplyInstruction(params, options.dynamicTools),
2318
2478
  nativeCommandGuidance,
2319
2479
  params.extraSystemPrompt
@@ -2365,10 +2525,15 @@ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts)
2365
2525
  requested: thinkLevel,
2366
2526
  supportedReasoningEfforts
2367
2527
  }) ?? null;
2528
+ const fallbackReasoningEfforts = resolveCodexFallbackReasoningEfforts(modelId);
2529
+ if (fallbackReasoningEfforts) return resolveCodexSupportedReasoningEffort({
2530
+ requested: thinkLevel,
2531
+ supportedReasoningEfforts: fallbackReasoningEfforts
2532
+ }) ?? null;
2368
2533
  if (thinkLevel === "minimal") return isModernCodexModel(modelId) ? "low" : "minimal";
2369
2534
  if (thinkLevel === "low" || thinkLevel === "medium" || thinkLevel === "high" || thinkLevel === "xhigh") return thinkLevel;
2370
2535
  if (thinkLevel === "max" && isMaxReasoningCodexModel(modelId)) return "max";
2371
2536
  return null;
2372
2537
  }
2373
2538
  //#endregion
2374
- export { resolveCodexContextEngineProjectionReserveTokens as A, describeCodexNotificationCorrelation as B, isForcedPrivateQaCodexRuntime as C, fitCodexProjectedContextForTurnStart as D, resolveCodexDynamicToolsLoadingForRuntime as E, shouldBuildCodexPluginThreadConfig as F, readCodexNotificationThreadId as H, ensureCodexPluginActivation as I, pluginReadParams as L, buildCodexPluginThreadConfig as M, buildCodexPluginThreadConfigInputFingerprint as N, projectContextEngineAssemblyForCodex as O, mergeCodexThreadConfigs as P, buildCodexAppInventoryCacheKey as R, filterCodexDynamicTools as S, resolveCodexDynamicToolsLoading as T, readCodexNotificationTurnId as U, isCodexNotificationForTurn as V, resolveCodexWebSearchPlan as _, buildDeveloperInstructions as a, sanitizeCodexHistoryImagePayloads as b, codexDynamicToolsFingerprint as c, resolveCodexAppServerRequestModelSelection as d, resolveCodexAppServerThreadModelSelection as f, buildCodexNativeWebSearchThreadConfig as g, startOrResumeThread as h, buildContextEngineBinding as i, buildCodexPluginAppsConfigPatchFromPolicyContext as j, resolveCodexContextEngineProjectionMaxChars as k, isContextEngineBindingCompatible as l, resolveReasoningEffort as m, areCodexDynamicToolFingerprintsCompatible as n, buildTurnCollaborationMode as o, resolveCodexBindingModelProviderFallback as p, buildCodexRuntimeThreadConfig as r, buildTurnStartParams as s, CODEX_NATIVE_PERSONALITY_NONE as t, resolveCodexAppServerModelProvider as u, isCodexAppServerProfilerEnabled as v, normalizeCodexDynamicToolName as w, sanitizeInlineImageDataUrl$1 as x, invalidInlineImageText as y, defaultCodexAppInventoryCache as z };
2539
+ export { resolveCodexContextEngineProjectionReserveTokens as A, buildCodexPluginThreadConfigInputFingerprint as B, isForcedPrivateQaCodexRuntime as C, fitCodexProjectedContextForTurnStart as D, resolveCodexDynamicToolsLoadingForRuntime as E, interruptCodexTurnBestEffort as F, buildCodexAppInventoryCacheKey as G, shouldBuildCodexPluginThreadConfig as H, retireCodexAppServerClientAfterTimedOutTurn as I, defaultCodexAppInventoryCache as K, unsubscribeCodexThreadBestEffort as L, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as M, CodexAppServerUnsafeSubscriptionError as N, projectContextEngineAssemblyForCodex as O, closeCodexStartupClientBestEffort as P, buildCodexPluginAppsConfigPatchFromPolicyContext as R, filterCodexDynamicTools as S, resolveCodexDynamicToolsLoading as T, ensureCodexPluginActivation as U, mergeCodexThreadConfigs as V, pluginReadParams as W, resolveCodexWebSearchPlan as _, buildDeveloperInstructions as a, sanitizeCodexHistoryImagePayloads as b, codexDynamicToolsFingerprint as c, resolveCodexAppServerRequestModelSelection as d, resolveCodexAppServerThreadModelSelection as f, buildCodexNativeWebSearchThreadConfig as g, startOrResumeThread as h, buildContextEngineBinding as i, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as j, resolveCodexContextEngineProjectionMaxChars as k, isContextEngineBindingCompatible as l, resolveReasoningEffort as m, areCodexDynamicToolFingerprintsCompatible as n, buildTurnCollaborationMode as o, resolveCodexBindingModelProviderFallback as p, buildCodexRuntimeThreadConfig as r, buildTurnStartParams as s, CODEX_NATIVE_PERSONALITY_NONE as t, resolveCodexAppServerModelProvider as u, isCodexAppServerProfilerEnabled as v, normalizeCodexDynamicToolName as w, sanitizeInlineImageDataUrl$1 as x, invalidInlineImageText as y, buildCodexPluginThreadConfig as z };