@openclaw/codex 2026.7.2-beta.5 → 2026.7.33

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 (66) hide show
  1. package/README.md +3 -27
  2. package/dist/{command-formatters-ico4ZFnk.js → .setup/app-server-policy-C_qTMEMy.mjs} +51 -9
  3. package/dist/{attempt-notifications-CMR2_fmR.js → .setup/attempt-notifications-F8krwWL4.mjs} +6 -3
  4. package/dist/{command-handlers-CRmnwI2N.js → .setup/command-handlers-DSRFfor4.mjs} +1156 -1319
  5. package/dist/.setup/command-rpc-BvYsfQ7G.mjs +786 -0
  6. package/dist/{compact-dEN49UH6.js → .setup/compact-BNgh7I4t.mjs} +10 -40
  7. package/dist/.setup/computer-use-C3QkmSO3.mjs +377 -0
  8. package/dist/.setup/config-CYEDnLJ2.mjs +1138 -0
  9. package/dist/{conversation-binding-data-BHRpYbX3.js → .setup/conversation-binding-data-CrRxtDBu.mjs} +1 -1
  10. package/dist/{bounded-turn-FEbUXoYT.js → .setup/media-understanding-provider-EbJCyDqe.mjs} +193 -92
  11. package/dist/{models-BA01NEez.js → .setup/models-C_aZk03A.mjs} +11 -6
  12. package/dist/.setup/notification-correlation-iYs4WieS.mjs +35 -0
  13. package/dist/.setup/plugin-app-cache-key-DR8jV9oS.mjs +99 -0
  14. package/dist/.setup/protocol-2POPqAY4.mjs +13 -0
  15. package/dist/{protocol-validators-Dm4eXZmt.js → .setup/protocol-validators-dZQ-UTOa.mjs} +111 -334
  16. package/dist/{rate-limits-S_PXs0ul.js → .setup/provider-DGxDs6g8.mjs} +269 -13
  17. package/dist/{dynamic-tools-LIt9ojep.js → .setup/provider-capabilities-DS2N_zc1.mjs} +5465 -3399
  18. package/dist/.setup/request-DFR0JyoV.mjs +36 -0
  19. package/dist/.setup/run-attempt-C7xEKFVB.mjs +8180 -0
  20. package/dist/{sandbox-guard-D1Wky__S.js → .setup/sandbox-guard-DA2TQfZW.mjs} +1 -10
  21. package/dist/.setup/session-binding-DuihlMJc.mjs +628 -0
  22. package/dist/.setup/shared-client-B2KmLPRw.mjs +1854 -0
  23. package/dist/{side-question-CO12sHxo.js → .setup/side-question-Cv6JXWCS.mjs} +84 -159
  24. package/dist/{thread-lifecycle-DRg_FdzC.js → .setup/thread-lifecycle-u6CZdOOH.mjs} +1159 -1799
  25. package/dist/{web-search-provider.runtime-CaQMYDbp.js → .setup/web-search-provider.runtime-Ce2zOHxW.mjs} +3 -3
  26. package/dist/doctor-contract-api.js +122 -88
  27. package/dist/harness.js +20 -85
  28. package/dist/index.js +381 -1537
  29. package/dist/media-understanding-provider.js +1 -173
  30. package/dist/prompt-overlay.js +15 -0
  31. package/dist/provider-catalog.js +79 -0
  32. package/dist/provider-discovery.js +34 -0
  33. package/dist/provider.js +2 -0
  34. package/dist/web-search-contract-api.js +1 -1
  35. package/openclaw.plugin.json +178 -221
  36. package/package.json +8 -10
  37. package/dist/app-server-policy-BmDgseo_.js +0 -49
  38. package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
  39. package/dist/auth-binding-B4FfbcCg.js +0 -62
  40. package/dist/capabilities-BHRSI6Cn.js +0 -36
  41. package/dist/cli-metadata.js +0 -20
  42. package/dist/computer-use-BjGylO-E.js +0 -695
  43. package/dist/conversation-control-DE7d9DOj.js +0 -287
  44. package/dist/event-projector-assistant-message-DTgnPPVF.js +0 -101
  45. package/dist/event-projector-usage-tvgb1NCX.js +0 -69
  46. package/dist/incognito-session-ET_ufDH-.js +0 -2
  47. package/dist/notification-correlation-Bj_Ak_jI.js +0 -67
  48. package/dist/plugin-app-cache-key-DSXX-rXO.js +0 -315
  49. package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
  50. package/dist/realtime-voice-api.js +0 -2
  51. package/dist/request-CXpPennK.js +0 -157
  52. package/dist/run-attempt-C5GTPJOE.js +0 -11935
  53. package/dist/runtime-artifact-C2ITXdhM.js +0 -488
  54. package/dist/session-binding-DlVQ-Pxs.js +0 -2161
  55. package/dist/session-catalog-CP2oCcEb.js +0 -2444
  56. package/dist/session-cli-CqZ0Fp2a.js +0 -175
  57. package/dist/session-upstream-marker-QE-m5QFG.js +0 -82
  58. package/dist/settled-turn-finalizer-CA_2QXIE.js +0 -303
  59. package/dist/shared-client-BVKE40c-.js +0 -2952
  60. package/dist/transcript-mirror-uKA-A4tB.js +0 -550
  61. package/dist/transport-stdio-DXgGZ7Ib.js +0 -88
  62. package/dist/upstream-session-fork-_Vrz4WJr.js +0 -320
  63. package/dist/usage-UpVbdX5G.js +0 -25
  64. /package/dist/{rolldown-runtime-8H4AJuhK.js → .setup/rolldown-runtime-8H4AJuhK.mjs} +0 -0
  65. /package/dist/{session-binding-meta-B7aEMU7g.js → .setup/session-binding-meta-B7aEMU7g.mjs} +0 -0
  66. /package/dist/{web-search-provider.shared-BrZmlqyR.js → .setup/web-search-provider.shared-BrZmlqyR.mjs} +0 -0
@@ -1,95 +1,237 @@
1
- import { B as flattenCodexDynamicToolFunctions, E as resolveCodexAppServerClientInstanceId, V as isJsonObject, _ as CodexAppServerRpcError, s as isCodexAppServerStartSelectionChangedError, v as getCodexAppServerClientInstanceId, w as isCodexAppServerPrewriteRequestCancellationError, x as isCodexAppServerConnectionClosedError } from "./shared-client-BVKE40c-.js";
2
- import { A as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, E as resolveCodexPluginsPolicy, a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, f as codexSandboxPolicyForTurn, i as createCodexSessionGenerationSupersededError, k as CODEX_PLUGINS_MARKETPLACE_NAME, l as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-DlVQ-Pxs.js";
3
- import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, i as assertCodexThreadResumeSubscription, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError, u as unsubscribeCodexThreadBestEffort } from "./attempt-client-cleanup-Ia52yRWc.js";
4
- import { i as assertCodexThreadStartResponse, n as assertCodexThreadForkResponse, r as assertCodexThreadResumeResponse } from "./protocol-validators-Dm4eXZmt.js";
5
- import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-DSXX-rXO.js";
6
- import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
7
- import { a as projectBoundedCodexThreadHistory } from "./transcript-mirror-uKA-A4tB.js";
8
- import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
9
- import * as crypto$1 from "node:crypto";
1
+ import { n as flattenCodexDynamicToolFunctions, r as isJsonObject } from "./protocol-2POPqAY4.mjs";
2
+ import { h as resolveCodexPluginsPolicy, i as codexSandboxPolicyForTurn, t as CODEX_PLUGINS_MARKETPLACE_NAME } from "./config-CYEDnLJ2.mjs";
3
+ import { n as assertCodexThreadResumeResponse, r as assertCodexThreadStartResponse } from "./protocol-validators-dZQ-UTOa.mjs";
4
+ import { CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "../prompt-overlay.js";
5
+ import { a as readCodexSupportedReasoningEfforts, i as isModernCodexModel, o as resolveCodexFallbackReasoningEfforts, r as isMaxReasoningCodexModel, s as resolveCodexSupportedReasoningEffort } from "./provider-DGxDs6g8.mjs";
6
+ import { f as isCodexAppServerConnectionClosedError, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, s as retireSharedCodexAppServerClientIfCurrent, t as clearSharedCodexAppServerClientIfCurrent, u as CodexAppServerRpcError } from "./shared-client-B2KmLPRw.mjs";
7
+ import { a as normalizeCodexAppServerBindingModelProvider, i as isCodexAppServerNativeAuthProfile, o as reclaimCurrentCodexSessionGeneration, r as hashCodexAppServerBindingFingerprint, s as sessionBindingIdentity } from "./session-binding-DuihlMJc.mjs";
8
+ import "node:fs/promises";
9
+ import "node:path";
10
+ import { isFutureDateTimestampMs, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
11
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
10
12
  import crypto from "node:crypto";
11
13
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
12
- import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
13
- import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
14
- import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
15
- import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
14
+ import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine } from "openclaw/plugin-sdk/agent-harness-runtime";
15
+ import { buildCodexUserMcpServersThreadConfigPatch } from "openclaw/plugin-sdk/codex-mcp-projection";
16
16
  import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
17
- import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
17
+ import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
18
18
  import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
19
- //#region extensions/codex/src/app-server/event-projector-values.ts
20
- function readString(record, key) {
21
- const value = record[key];
22
- return typeof value === "string" ? value : void 0;
23
- }
24
- function normalizeNonEmptyString(value) {
25
- if (typeof value !== "string") return;
26
- return value.trim() || void 0;
27
- }
28
- function readNonEmptyString(record, key) {
29
- return normalizeNonEmptyString(record[key]);
30
- }
31
- function readNonEmptyStringArray(record, key) {
32
- const value = record[key];
33
- if (!Array.isArray(value)) return [];
34
- const entries = [];
35
- for (const entry of value) {
36
- const normalized = normalizeNonEmptyString(entry);
37
- if (normalized) entries.push(normalized);
19
+ import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
20
+ const CODEX_TARGETED_APP_INVENTORY_LIMIT = 1e3;
21
+ const MAX_SERIALIZED_ERROR_MESSAGE_LENGTH = 500;
22
+ /** In-memory app inventory cache with coalesced refreshes per key. */
23
+ var CodexAppInventoryCache = class {
24
+ constructor(options = {}) {
25
+ this.entries = /* @__PURE__ */ new Map();
26
+ this.inFlight = /* @__PURE__ */ new Map();
27
+ this.refreshTokens = /* @__PURE__ */ new Map();
28
+ this.diagnostics = /* @__PURE__ */ new Map();
29
+ this.revision = 0;
30
+ this.ttlMs = options.ttlMs ?? 36e5;
31
+ }
32
+ /** Reads a snapshot and schedules refresh when missing, stale, or forced. */
33
+ read(params) {
34
+ const nowMs = resolveDateTimestampMs(params.nowMs);
35
+ const entry = this.entries.get(params.key);
36
+ if (!entry) {
37
+ const refreshScheduled = params.suppressRefresh ? false : this.scheduleRefresh(params);
38
+ return {
39
+ state: "missing",
40
+ key: params.key,
41
+ revision: this.revision,
42
+ refreshScheduled,
43
+ ...this.diagnostics.get(params.key) ? { diagnostic: this.diagnostics.get(params.key) } : {}
44
+ };
45
+ }
46
+ const state = entry.invalidated || !isFutureDateTimestampMs(entry.expiresAtMs, { nowMs }) ? "stale" : "fresh";
47
+ const refreshScheduled = state === "fresh" && !params.forceRefetch ? false : this.scheduleRefresh(params);
48
+ return {
49
+ state,
50
+ key: params.key,
51
+ revision: entry.revision,
52
+ snapshot: stripEntryState(entry),
53
+ refreshScheduled,
54
+ ...entry.lastError ? { diagnostic: entry.lastError } : {}
55
+ };
56
+ }
57
+ /** Forces or joins an immediate refresh for a cache key. */
58
+ refreshNow(params) {
59
+ return this.refresh(params);
60
+ }
61
+ /** Marks a key stale and records the reason as a diagnostic. */
62
+ invalidate(key, reason, nowMs = Date.now()) {
63
+ this.revision += 1;
64
+ const diagnostic = {
65
+ message: reason,
66
+ atMs: nowMs
67
+ };
68
+ const entry = this.entries.get(key);
69
+ if (entry) {
70
+ entry.invalidated = true;
71
+ entry.lastError = diagnostic;
72
+ entry.revision = this.revision;
73
+ } else this.diagnostics.set(key, diagnostic);
74
+ return this.revision;
75
+ }
76
+ /** Clears all cached snapshots, diagnostics, in-flight requests, and revision state. */
77
+ clear() {
78
+ this.entries.clear();
79
+ this.inFlight.clear();
80
+ this.refreshTokens.clear();
81
+ this.diagnostics.clear();
82
+ this.revision = 0;
83
+ }
84
+ /** Returns the monotonically increasing cache revision. */
85
+ getRevision() {
86
+ return this.revision;
87
+ }
88
+ scheduleRefresh(params) {
89
+ if (this.inFlight.has(params.key) && !params.forceRefetch) return true;
90
+ const promise = this.refresh(params);
91
+ this.inFlight.set(params.key, promise);
92
+ promise.catch(() => void 0);
93
+ return true;
38
94
  }
39
- return entries;
95
+ async refresh(params) {
96
+ const existing = this.inFlight.get(params.key);
97
+ if (existing && !params.forceRefetch) return existing;
98
+ const refreshToken = (this.refreshTokens.get(params.key) ?? 0) + 1;
99
+ this.refreshTokens.set(params.key, refreshToken);
100
+ const promise = this.refreshUncoalesced(params, refreshToken);
101
+ this.inFlight.set(params.key, promise);
102
+ try {
103
+ return await promise;
104
+ } finally {
105
+ if (this.inFlight.get(params.key) === promise) this.inFlight.delete(params.key);
106
+ }
107
+ }
108
+ async refreshUncoalesced(params, refreshToken) {
109
+ const nowMs = resolveDateTimestampMs(params.nowMs);
110
+ try {
111
+ const apps = await listAllApps(params.request, params.forceRefetch ?? false, params.targetAppIds);
112
+ this.revision += 1;
113
+ const expiresAtMs = resolveExpiresAtMsFromDurationMs(this.ttlMs, { nowMs }) ?? 0;
114
+ const snapshot = {
115
+ key: params.key,
116
+ apps,
117
+ fetchedAtMs: nowMs,
118
+ expiresAtMs,
119
+ revision: this.revision
120
+ };
121
+ if (this.refreshTokens.get(params.key) === refreshToken) {
122
+ this.entries.set(params.key, {
123
+ ...snapshot,
124
+ invalidated: false
125
+ });
126
+ this.diagnostics.delete(params.key);
127
+ }
128
+ return snapshot;
129
+ } catch (error) {
130
+ const diagnostic = {
131
+ message: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
132
+ atMs: nowMs
133
+ };
134
+ this.diagnostics.set(params.key, diagnostic);
135
+ const entry = this.entries.get(params.key);
136
+ if (entry) entry.lastError = diagnostic;
137
+ embeddedAgentLog.warn("codex app inventory refresh failed", {
138
+ forceRefetch: params.forceRefetch === true,
139
+ keyFingerprint: fingerprintInventoryCacheKey(params.key),
140
+ error: serializeCodexAppInventoryError(error)
141
+ });
142
+ throw error;
143
+ }
144
+ }
145
+ };
146
+ /** Serializes a refresh failure without leaking large or sensitive error data. */
147
+ function serializeCodexAppInventoryError(error) {
148
+ const record = isRecord(error) ? error : void 0;
149
+ const data = record && "data" in record ? redactErrorData(record.data) : void 0;
150
+ return {
151
+ name: error instanceof Error ? error.name : typeof record?.name === "string" ? record.name : void 0,
152
+ message: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
153
+ ...typeof record?.code === "number" ? { code: record.code } : {},
154
+ ...data !== void 0 ? { data } : {}
155
+ };
40
156
  }
41
- function readNullableString(record, key) {
42
- const value = record[key];
43
- if (value === null) return null;
44
- return typeof value === "string" ? value : void 0;
45
- }
46
- function readNumber(record, key) {
47
- const value = record[key];
48
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
49
- }
50
- function readNonNegativeInteger(record, key) {
51
- const value = readNumber(record, key);
52
- return value !== void 0 && Number.isInteger(value) && value >= 0 ? value : void 0;
53
- }
54
- function readCodexErrorNotificationMessage(record) {
55
- const error = record.error;
56
- return isJsonObject(error) ? readString(error, "message") : void 0;
57
- }
58
- function readHookOutputEntries(value) {
59
- if (!Array.isArray(value)) return [];
60
- return value.flatMap((entry) => {
61
- if (!isJsonObject(entry)) return [];
62
- const text = readString(entry, "text");
63
- if (!text) return [];
64
- const kind = readString(entry, "kind");
65
- return [{
66
- ...kind ? { kind } : {},
67
- text
68
- }];
157
+ /** Shared app inventory cache used by Codex app-server runtime paths. */
158
+ const defaultCodexAppInventoryCache = new CodexAppInventoryCache();
159
+ /** Builds a stable cache key from build versions and runtime identity fields. */
160
+ function buildCodexAppInventoryCacheKey(input, openClawVersion, codexPluginVersion) {
161
+ return JSON.stringify({
162
+ openClawVersion,
163
+ codexPluginVersion,
164
+ codexHome: input.codexHome ?? null,
165
+ endpoint: input.endpoint ?? null,
166
+ runtimeIdentity: normalizeRuntimeIdentityForCacheKey(input.runtimeIdentity),
167
+ authProfileId: input.authProfileId ?? null,
168
+ accountId: input.accountId ?? null,
169
+ envApiKeyFingerprint: input.envApiKeyFingerprint ?? null,
170
+ appServerVersion: input.appServerVersion ?? null
69
171
  });
70
172
  }
71
- function splitPlanText(text) {
72
- return text.split(/\r?\n/).map((line) => line.trim().replace(/^[-*]\s+/, "")).filter((line) => line.length > 0);
73
- }
74
- function extractRawAssistantText(item) {
75
- return (Array.isArray(item.content) ? item.content : []).flatMap((entry) => {
76
- if (!isJsonObject(entry)) return [];
77
- const type = readString(entry, "type");
78
- if (type !== "output_text" && type !== "text") return [];
79
- const value = readString(entry, "text");
80
- return value ? [value] : [];
81
- }).join("").trim() || void 0;
82
- }
83
- function readItemString(item, key) {
84
- const value = item[key];
85
- return typeof value === "string" ? value : void 0;
86
- }
87
- function readItem(value) {
88
- if (!isJsonObject(value)) return;
89
- const type = typeof value.type === "string" ? value.type : void 0;
90
- const id = typeof value.id === "string" ? value.id : void 0;
91
- if (!type || !id) return;
92
- return value;
173
+ function normalizeRuntimeIdentityForCacheKey(value) {
174
+ if (!value) return null;
175
+ const entries = Object.entries(value).flatMap(([key, rawValue]) => {
176
+ const normalized = rawValue?.trim();
177
+ return normalized ? [[key, normalized]] : [];
178
+ }).toSorted(([left], [right]) => left.localeCompare(right));
179
+ return entries.length > 0 ? Object.fromEntries(entries) : null;
180
+ }
181
+ async function listAllApps(request, forceRefetch, targetAppIds = []) {
182
+ const apps = [];
183
+ const targetIds = new Set(targetAppIds.filter(Boolean));
184
+ const remainingTargetIds = new Set(targetIds);
185
+ const seenCursors = /* @__PURE__ */ new Set();
186
+ let cursor;
187
+ do {
188
+ const response = await request("app/list", {
189
+ cursor,
190
+ limit: targetIds.size > 0 ? CODEX_TARGETED_APP_INVENTORY_LIMIT : 100,
191
+ forceRefetch
192
+ });
193
+ apps.push(...response.data);
194
+ for (const app of response.data) remainingTargetIds.delete(app.id);
195
+ if (targetIds.size > 0 && remainingTargetIds.size === 0) break;
196
+ cursor = response.nextCursor;
197
+ if (cursor && seenCursors.has(cursor)) throw new Error(`app/list returned repeated cursor ${cursor}`);
198
+ if (cursor) seenCursors.add(cursor);
199
+ } while (cursor);
200
+ return apps;
201
+ }
202
+ function stripEntryState(entry) {
203
+ const { invalidated: _invalidated, ...snapshot } = entry;
204
+ return snapshot;
205
+ }
206
+ function fingerprintInventoryCacheKey(key) {
207
+ let hash = 0;
208
+ for (let index = 0; index < key.length; index += 1) hash = hash * 31 + key.charCodeAt(index) >>> 0;
209
+ return hash.toString(16).padStart(8, "0");
210
+ }
211
+ function redactErrorData(value, depth = 0) {
212
+ if (value === void 0) return;
213
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
214
+ if (depth > 6) return "[truncated]";
215
+ if (Array.isArray(value)) return value.map((entry) => redactErrorData(entry, depth + 1) ?? null);
216
+ if (isRecord(value)) {
217
+ const redacted = {};
218
+ for (const [key, entry] of Object.entries(value)) redacted[key] = isSensitiveErrorDataKey(key) ? "<redacted>" : redactErrorData(entry, depth + 1) ?? null;
219
+ return redacted;
220
+ }
221
+ if (typeof value === "string" && value.length > 500) return `${truncateUtf16Safe(value, 500)}...`;
222
+ if (typeof value === "string") return value;
223
+ if (typeof value === "bigint") return value.toString();
224
+ if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol()";
225
+ if (typeof value === "function") return value.name ? `[function ${value.name}]` : "[function]";
226
+ return "[unserializable]";
227
+ }
228
+ function sanitizeErrorMessage(message) {
229
+ const htmlStart = message.search(/<html[\s>]/i);
230
+ const redacted = (htmlStart >= 0 ? `${message.slice(0, htmlStart).trimEnd()} [HTML response body omitted]` : message).replace(/([?&][^=\s"'<>]*(?:api[_-]?key|authorization|cookie|credential|password|secret|token|tk)[^=\s"'<>]*=)[^&\s"'<>]+/gi, "$1<redacted>");
231
+ return redacted.length > MAX_SERIALIZED_ERROR_MESSAGE_LENGTH ? `${redacted.slice(0, MAX_SERIALIZED_ERROR_MESSAGE_LENGTH)}...` : redacted;
232
+ }
233
+ function isSensitiveErrorDataKey(key) {
234
+ return /api[_-]?key|authorization|cookie|credential|password|secret|token/i.test(key);
93
235
  }
94
236
  //#endregion
95
237
  //#region extensions/codex/src/app-server/plugin-inventory.ts
@@ -110,21 +252,19 @@ async function readCodexPluginInventory(params) {
110
252
  }]
111
253
  };
112
254
  const appInventory = readCachedAppInventory(params);
113
- const curatedListed = await listCodexPluginMetadata(params, "curated-global", {});
114
- const shouldListWorkspacePlugins = policy.pluginPolicies.some((pluginPolicy) => pluginPolicy.enabled && pluginPolicy.marketplaceName === "workspace-directory");
115
- let workspaceListResult;
116
- if (shouldListWorkspacePlugins) try {
117
- workspaceListResult = {
118
- kind: "listed",
119
- response: await listCodexPluginMetadata(params, "workspace-directory", {
120
- cwds: [],
121
- marketplaceKinds: [CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME]
122
- })
123
- };
124
- } catch (error) {
125
- if (!(error instanceof CodexAppServerRpcError)) throw error;
126
- workspaceListResult = { kind: "rejected" };
127
- }
255
+ const listed = await params.request("plugin/list", { cwds: [] });
256
+ const marketplaceEntry = listed.marketplaces.find(isOpenAiCuratedMarketplace);
257
+ if (!marketplaceEntry) return {
258
+ policy,
259
+ records: [],
260
+ diagnostics: policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled).map((pluginPolicy) => ({
261
+ code: "marketplace_missing",
262
+ plugin: pluginPolicy,
263
+ message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.`
264
+ })),
265
+ ...appInventory ? { appInventory } : {}
266
+ };
267
+ let marketplace = marketplaceRef(marketplaceEntry);
128
268
  const diagnostics = [];
129
269
  const records = [];
130
270
  if (appInventory?.state === "missing") diagnostics.push({
@@ -137,27 +277,19 @@ async function readCodexPluginInventory(params) {
137
277
  });
138
278
  for (const pluginPolicy of policy.pluginPolicies) {
139
279
  if (!pluginPolicy.enabled) continue;
140
- const listed = pluginPolicy.marketplaceName === "workspace-directory" ? workspaceListResult?.kind === "listed" ? workspaceListResult.response : void 0 : curatedListed;
141
- const hasMarketplace = pluginPolicy.marketplaceName === "workspace-directory" ? listed?.marketplaces.some((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME) === true : listed?.marketplaces.some(isOpenAiCuratedMarketplace) === true;
142
- if (!listed || !hasMarketplace) {
143
- diagnostics.push({
144
- code: "marketplace_missing",
145
- plugin: pluginPolicy,
146
- message: `Codex marketplace ${pluginPolicy.marketplaceName} was not found.`
147
- });
148
- continue;
149
- }
150
- const resolvedPlugin = pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(listed, pluginPolicy.pluginName) : findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
280
+ const resolvedPlugin = findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
151
281
  if (!resolvedPlugin) {
152
282
  diagnostics.push({
153
283
  code: "plugin_missing",
154
284
  plugin: pluginPolicy,
155
- message: `${pluginPolicy.pluginName} was not found in ${pluginPolicy.marketplaceName}.`
285
+ message: `${pluginPolicy.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.`
156
286
  });
157
287
  continue;
158
288
  }
159
289
  const { summary } = resolvedPlugin;
160
- const detail = await readPluginDetail(params, marketplaceRef(resolvedPlugin.marketplace, pluginPolicy.marketplaceName), pluginPolicy, summary, diagnostics);
290
+ const pluginMarketplace = marketplaceRef(resolvedPlugin.marketplace);
291
+ if (records.length === 0) marketplace = pluginMarketplace;
292
+ const detail = await readPluginDetail(params, pluginMarketplace, pluginPolicy, summary, diagnostics);
161
293
  const ownedAppIds = detail?.apps.map((app) => app.id).filter(Boolean).toSorted() ?? [];
162
294
  const appOwnership = resolveAppOwnership({
163
295
  detail,
@@ -192,6 +324,7 @@ async function readCodexPluginInventory(params) {
192
324
  }
193
325
  return {
194
326
  policy,
327
+ marketplace,
195
328
  records,
196
329
  diagnostics,
197
330
  ...appInventory ? { appInventory } : {}
@@ -201,7 +334,7 @@ async function readCodexPluginInventory(params) {
201
334
  function findOpenAiCuratedPluginSummary(listed, pluginName) {
202
335
  const resolved = findOpenAiCuratedMarketplacePlugin(listed, pluginName);
203
336
  return resolved ? {
204
- marketplace: marketplaceRef(resolved.marketplace, CODEX_PLUGINS_MARKETPLACE_NAME),
337
+ marketplace: marketplaceRef(resolved.marketplace),
205
338
  summary: resolved.summary
206
339
  } : void 0;
207
340
  }
@@ -213,31 +346,6 @@ function pluginReadParams(marketplace, pluginName) {
213
346
  pluginName
214
347
  };
215
348
  }
216
- /** Returns configured plugin keys whose current metadata may still recover. */
217
- function resolveRecoverableCodexPluginConfigKeys(params) {
218
- return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
219
- pluginPolicy,
220
- metadataCache: params.metadataCache,
221
- appCacheKey: params.appCacheKey
222
- })).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
223
- }
224
- async function listCodexPluginMetadata(params, queryKind, requestParams) {
225
- if (!params.metadataCache || !params.appCacheKey || queryKind === "workspace-directory") return await params.request("plugin/list", requestParams);
226
- return (await params.metadataCache.load({
227
- appCacheKey: params.appCacheKey,
228
- queryKind,
229
- requestParams,
230
- request: async (method, listedParams) => await params.request(method, listedParams),
231
- cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
232
- })).response;
233
- }
234
- function isSettledMissingPluginPolicy(params) {
235
- const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "workspace-directory" : "curated-global";
236
- const listed = params.metadataCache.read(params.appCacheKey, queryKind)?.response;
237
- if (!listed) return false;
238
- if (queryKind === "workspace-directory") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
239
- return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
240
- }
241
349
  function readCachedAppInventory(params) {
242
350
  if (!params.appCache || !params.appCacheKey) return;
243
351
  const request = async (method, requestParams) => await params.request(method, requestParams);
@@ -250,14 +358,6 @@ function readCachedAppInventory(params) {
250
358
  }
251
359
  async function readPluginDetail(params, marketplace, pluginPolicy, summary, diagnostics) {
252
360
  if (params.readPluginDetails === false) return;
253
- if (marketplace.name === "workspace-directory" && marketplace.remoteMarketplaceName && !summary.remotePluginId) {
254
- diagnostics.push({
255
- code: "plugin_detail_unavailable",
256
- plugin: pluginPolicy,
257
- message: `${pluginPolicy.pluginName} detail unavailable: Codex did not return a remote plugin id.`
258
- });
259
- return;
260
- }
261
361
  try {
262
362
  return (await params.request("plugin/read", pluginReadParams(marketplace, marketplace.remoteMarketplaceName && summary.remotePluginId ? summary.remotePluginId : pluginPolicy.pluginName))).plugin;
263
363
  } catch (error) {
@@ -316,40 +416,31 @@ function findOpenAiCuratedMarketplacePlugin(listed, pluginName) {
316
416
  };
317
417
  }
318
418
  }
319
- function findWorkspaceMarketplacePlugin(listed, pluginName) {
320
- const marketplace = listed.marketplaces.find((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME);
321
- const summary = marketplace?.plugins.find((plugin) => plugin.id === pluginName);
322
- return marketplace && summary ? {
323
- marketplace,
324
- summary
325
- } : void 0;
326
- }
327
419
  function pluginNameFromPluginId(pluginId, marketplaceName) {
328
420
  const trimmed = pluginId.trim();
329
421
  if (!trimmed) return;
330
422
  const marketplaceSuffix = `@${marketplaceName}`;
331
423
  return (trimmed.endsWith(marketplaceSuffix) ? trimmed.slice(0, -marketplaceSuffix.length) : trimmed).split("/").at(-1)?.trim() || void 0;
332
424
  }
333
- function marketplaceRef(marketplace, name) {
425
+ function marketplaceRef(marketplace) {
334
426
  return {
335
- name,
427
+ name: CODEX_PLUGINS_MARKETPLACE_NAME,
336
428
  ...marketplace.path ? { path: marketplace.path } : {},
337
429
  ...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
338
430
  };
339
431
  }
340
- /** True for either supported OpenAI curated marketplace wire name. */
341
432
  function isOpenAiCuratedMarketplace(marketplace) {
342
433
  return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME;
343
434
  }
344
435
  //#endregion
345
436
  //#region extensions/codex/src/app-server/plugin-activation.ts
346
- /** Activates a curated plugin or rejects a workspace plugin that is not already active. */
437
+ /** Installs/enables a configured Codex plugin and refreshes plugin/app state. */
347
438
  async function ensureCodexPluginActivation(params) {
348
- if (params.identity.marketplaceName === "workspace-directory") return activationFailure(params.identity, "disabled", { message: "workspace-directory plugins must be installed and enabled outside OpenClaw before use." });
349
- const listed = await listCuratedCodexPluginMetadata(params);
439
+ if (params.identity.marketplaceName !== "openai-curated") return activationFailure(params.identity, "marketplace_missing", { message: "Only openai-curated plugins can be activated." });
440
+ const listed = await params.request("plugin/list", { cwds: [] });
350
441
  const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName);
351
442
  if (!resolved) {
352
- if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
443
+ 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.` });
353
444
  return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
354
445
  }
355
446
  if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
@@ -361,7 +452,6 @@ async function ensureCodexPluginActivation(params) {
361
452
  diagnostics: []
362
453
  };
363
454
  const installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, resolved.marketplace.remoteMarketplaceName && resolved.summary.remotePluginId ? resolved.summary.remotePluginId : params.identity.pluginName));
364
- if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
365
455
  const refreshDiagnostics = [];
366
456
  let refreshFailed = false;
367
457
  try {
@@ -369,7 +459,6 @@ async function ensureCodexPluginActivation(params) {
369
459
  request: params.request,
370
460
  appCache: params.appCache,
371
461
  appCacheKey: params.appCacheKey,
372
- metadataCache: params.metadataCache,
373
462
  targetAppIds: params.targetAppIds
374
463
  });
375
464
  refreshDiagnostics.push(...refreshResult.diagnostics);
@@ -391,7 +480,7 @@ async function ensureCodexPluginActivation(params) {
391
480
  /** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
392
481
  async function refreshCodexPluginRuntimeState(params) {
393
482
  const diagnostics = [];
394
- await listCuratedCodexPluginMetadata(params);
483
+ await params.request("plugin/list", { cwds: [] });
395
484
  await params.request("skills/list", {
396
485
  cwds: [],
397
486
  forceReload: true
@@ -418,17 +507,6 @@ async function refreshCodexPluginRuntimeState(params) {
418
507
  }
419
508
  return { diagnostics };
420
509
  }
421
- async function listCuratedCodexPluginMetadata(params) {
422
- const requestParams = {};
423
- if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
424
- return (await params.metadataCache.load({
425
- appCacheKey: params.appCacheKey,
426
- queryKind: "curated-global",
427
- requestParams,
428
- request: async (method, listedParams) => await params.request(method, listedParams),
429
- cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
430
- })).response;
431
- }
432
510
  function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
433
511
  return {
434
512
  identity,
@@ -459,20 +537,6 @@ function buildCodexPluginThreadConfigInputFingerprint(params) {
459
537
  appCacheKey: params.appCacheKey ?? null
460
538
  });
461
539
  }
462
- /** Builds the deny-all app patch used when plugin discovery exceeds its turn budget. */
463
- function buildCodexPluginThreadConfigTimeoutFallback(params) {
464
- return {
465
- ...emptyPluginThreadConfig({
466
- enabled: true,
467
- inputFingerprint: buildCodexPluginThreadConfigInputFingerprint(params),
468
- configPatch: buildDisabledAppsConfigPatch()
469
- }),
470
- diagnostics: [{
471
- code: "plugin_config_timeout",
472
- message: params.message
473
- }]
474
- };
475
- }
476
540
  /** Builds the Codex apps config patch and policy context for a native thread. */
477
541
  async function buildCodexPluginThreadConfig(params) {
478
542
  const appCache = params.appCache ?? defaultCodexAppInventoryCache;
@@ -492,7 +556,6 @@ async function buildCodexPluginThreadConfig(params) {
492
556
  request: params.request,
493
557
  appCache,
494
558
  appCacheKey: params.appCacheKey,
495
- metadataCache: params.metadataCache,
496
559
  nowMs: params.nowMs,
497
560
  suppressAppInventoryRefresh: true
498
561
  }) : emptyCodexPluginInventory(policy);
@@ -509,7 +572,6 @@ async function buildCodexPluginThreadConfig(params) {
509
572
  request: params.request,
510
573
  appCache,
511
574
  appCacheKey: params.appCacheKey,
512
- metadataCache: params.metadataCache,
513
575
  nowMs: params.nowMs
514
576
  });
515
577
  inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
@@ -526,7 +588,6 @@ async function buildCodexPluginThreadConfig(params) {
526
588
  request: params.request,
527
589
  appCache,
528
590
  appCacheKey: params.appCacheKey,
529
- metadataCache: params.metadataCache,
530
591
  targetAppIds: record.ownedAppIds
531
592
  });
532
593
  activationResults.push(activation);
@@ -550,7 +611,6 @@ async function buildCodexPluginThreadConfig(params) {
550
611
  request: params.request,
551
612
  appCache,
552
613
  appCacheKey: params.appCacheKey,
553
- metadataCache: params.metadataCache,
554
614
  nowMs: params.nowMs
555
615
  });
556
616
  inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
@@ -570,7 +630,6 @@ async function buildCodexPluginThreadConfig(params) {
570
630
  request: params.request,
571
631
  appCache,
572
632
  appCacheKey: params.appCacheKey,
573
- metadataCache: params.metadataCache,
574
633
  nowMs: params.nowMs
575
634
  });
576
635
  inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
@@ -888,6 +947,127 @@ function stableStringify(value) {
888
947
  return JSON.stringify(value);
889
948
  }
890
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
891
1071
  //#region extensions/codex/src/app-server/context-engine-projection.ts
892
1072
  const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
893
1073
  const CONTEXT_OPEN = "<conversation_context>";
@@ -943,9 +1123,13 @@ function resolveCodexContextEngineProjectionMaxChars(params) {
943
1123
  reserveTokens: params.reserveTokens
944
1124
  }) * APPROX_RENDERED_CHARS_PER_TOKEN);
945
1125
  }
946
- /** Returns the fixed reserve used for Codex context-engine projections. */
947
- function resolveCodexContextEngineProjectionReserveTokens() {
948
- return DEFAULT_CODEX_PROJECTION_RESERVE_TOKENS;
1126
+ /** Reads Codex projection reserve tokens from compaction config. */
1127
+ function resolveCodexContextEngineProjectionReserveTokens(params) {
1128
+ const compaction = asRecord(asRecord(asRecord(params.config)?.agents)?.defaults)?.compaction;
1129
+ const configuredReserveTokens = toNonNegativeInt(asRecord(compaction)?.reserveTokens);
1130
+ const configuredReserveTokensFloor = toNonNegativeInt(asRecord(compaction)?.reserveTokensFloor);
1131
+ if (configuredReserveTokens !== void 0) return Math.max(configuredReserveTokens, configuredReserveTokensFloor ?? 2e4);
1132
+ if (configuredReserveTokensFloor !== void 0) return configuredReserveTokensFloor;
949
1133
  }
950
1134
  /** Fits projected context prompts under Codex app-server turn/start text limits. */
951
1135
  function fitCodexProjectedContextForTurnStart(params) {
@@ -993,6 +1177,13 @@ function resolveProjectionPromptBudgetTokens(params) {
993
1177
  const effectiveReserveTokens = Math.min(requestedReserveTokens, Math.max(0, params.contextTokenBudget - minPromptBudget));
994
1178
  return Math.max(1, params.contextTokenBudget - effectiveReserveTokens);
995
1179
  }
1180
+ function asRecord(value) {
1181
+ return value && typeof value === "object" ? value : void 0;
1182
+ }
1183
+ function toNonNegativeInt(value) {
1184
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return;
1185
+ return Math.floor(value);
1186
+ }
996
1187
  function dropDuplicateTrailingPrompt(messages, prompt) {
997
1188
  if (!prompt) return messages;
998
1189
  const trailing = messages.at(-1);
@@ -1120,9 +1311,7 @@ function resolveTextPartMaxChars(maxRenderedContextChars) {
1120
1311
  return Math.min(MAX_TEXT_PART_CHARS, Math.max(DEFAULT_TEXT_PART_CHARS, Math.floor(maxRenderedContextChars / 4)));
1121
1312
  }
1122
1313
  function truncateText(text, maxChars) {
1123
- if (text.length <= maxChars) return text;
1124
- const truncated = truncateUtf16Safe(text, maxChars);
1125
- return `${truncated}\n[truncated ${text.length - truncated.length} chars]`;
1314
+ return text.length > maxChars ? `${text.slice(0, maxChars)}\n[truncated ${text.length - maxChars} chars]` : text;
1126
1315
  }
1127
1316
  function truncateOlderContext(text, maxChars) {
1128
1317
  if (text.length <= maxChars) return text;
@@ -1133,150 +1322,15 @@ function truncateOlderContext(text, maxChars) {
1133
1322
  marker = buildMarker(text.length - tailChars);
1134
1323
  if (marker.length >= maxChars) return marker.slice(0, maxChars);
1135
1324
  tailChars = maxChars - marker.length;
1136
- return `${marker}${sliceUtf16Safe(text, -tailChars).trimStart()}`;
1137
- }
1138
- //#endregion
1139
- //#region extensions/codex/src/app-server/thread-context-engine.ts
1140
- function buildContextEngineBinding(params, projection) {
1141
- const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
1142
- const engineId = contextEngine?.info?.id?.trim();
1143
- if (!contextEngine || !engineId) return;
1144
- return {
1145
- schemaVersion: 1,
1146
- engineId,
1147
- policyFingerprint: JSON.stringify({
1148
- schemaVersion: 1,
1149
- engineId,
1150
- engineVersion: contextEngine.info.version,
1151
- ownsCompaction: contextEngine.info.ownsCompaction === true,
1152
- turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
1153
- citationsMode: resolveContextEngineCitationsMode(params.config),
1154
- contextTokenBudget: params.contextTokenBudget,
1155
- projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
1156
- contextTokenBudget: params.contextTokenBudget,
1157
- reserveTokens: resolveCodexContextEngineProjectionReserveTokens()
1158
- })
1159
- }),
1160
- projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
1161
- };
1162
- }
1163
- function buildContextEngineProjectionBinding(projection) {
1164
- return {
1165
- schemaVersion: 1,
1166
- mode: "thread_bootstrap",
1167
- epoch: projection.epoch,
1168
- fingerprint: projection.fingerprint
1169
- };
1170
- }
1171
- function isContextEngineBindingCompatible(previous, next) {
1172
- return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
1173
- }
1174
- function areContextEngineProjectionBindingsCompatible(previous, next) {
1175
- if (!next) return previous === void 0;
1176
- return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
1177
- }
1178
- function resolveContextEngineCitationsMode(config) {
1179
- const rootConfig = isUnknownRecord(config) ? config : void 0;
1180
- const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
1181
- return isJsonConfigValue(citations) ? citations : void 0;
1182
- }
1183
- function isUnknownRecord(value) {
1184
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
1185
- }
1186
- function isJsonConfigValue(value) {
1187
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1188
- if (typeof value === "number") return Number.isFinite(value);
1189
- if (Array.isArray(value)) return value.every(isJsonConfigValue);
1190
- return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
1191
- }
1192
- //#endregion
1193
- //#region extensions/codex/src/app-server/thread-fingerprints.ts
1194
- function codexDynamicToolsFingerprint(dynamicTools) {
1195
- return fingerprintDynamicTools(dynamicTools);
1196
- }
1197
- function codexLegacyDynamicToolsFingerprint(dynamicTools) {
1198
- return legacyFingerprintDynamicTools(dynamicTools);
1199
- }
1200
- function areCodexDynamicToolFingerprintsCompatible(params) {
1201
- return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
1202
- }
1203
- function fingerprintDynamicTools(dynamicTools) {
1204
- return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
1205
- }
1206
- function legacyFingerprintDynamicTools(dynamicTools) {
1207
- return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
1208
- }
1209
- function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
1210
- return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
1211
- }
1212
- function fingerprintUserMcpServersConfigPatch(configPatch) {
1213
- return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
1325
+ return `${marker}${sliceTailFromCodePointBoundary(text, tailChars).trimStart()}`;
1214
1326
  }
1215
- function redactUserMcpServersFingerprintSecrets(value) {
1216
- if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
1217
- if (!value || typeof value !== "object") return value;
1218
- const next = {};
1219
- for (const [key, entry] of Object.entries(value)) {
1220
- if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
1221
- next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
1222
- continue;
1223
- }
1224
- next[key] = redactUserMcpServersFingerprintSecrets(entry);
1225
- }
1226
- return next;
1227
- }
1228
- function fingerprintUserMcpServersAuthorizationHeader(value) {
1229
- return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
1230
- }
1231
- function fingerprintJsonObject(value) {
1232
- return JSON.stringify(stabilizeJsonValue(value));
1233
- }
1234
- function fingerprintEnvironmentSelection(environments) {
1235
- return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
1236
- }
1237
- function fingerprintDynamicToolSpec(tool) {
1238
- return stabilizeDynamicToolFingerprintValue(tool);
1239
- }
1240
- function stabilizeDynamicToolFingerprintValue(value) {
1241
- if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
1242
- if (!isJsonObject(value)) return value;
1243
- const stable = {};
1244
- for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
1245
- if (key === "description") continue;
1246
- stable[key] = stabilizeDynamicToolFingerprintValue(child);
1327
+ function sliceTailFromCodePointBoundary(text, tailChars) {
1328
+ let start = text.length - tailChars;
1329
+ if (start > 0 && start < text.length) {
1330
+ const code = text.charCodeAt(start);
1331
+ if (code >= 56320 && code <= 57343) start += 1;
1247
1332
  }
1248
- return stable;
1249
- }
1250
- function stabilizeJsonValue(value) {
1251
- if (Array.isArray(value)) return value.map(stabilizeJsonValue);
1252
- if (!isJsonObject(value)) return value;
1253
- const stable = {};
1254
- for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
1255
- return stable;
1256
- }
1257
- function readActiveCodexTurnIds(thread) {
1258
- return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
1259
- }
1260
- function readActiveCodexTurnIdsFromResume(response) {
1261
- const pagedTurns = response.initialTurnsPage?.data;
1262
- return readActiveCodexTurnIds(Array.isArray(pagedTurns) ? { turns: pagedTurns } : response.thread);
1263
- }
1264
- const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
1265
- const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
1266
- function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
1267
- return !previous || previous === next || previous === nextLegacy;
1268
- }
1269
- function areUserMcpServersFingerprintsCompatible(params) {
1270
- return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
1271
- }
1272
- function shouldStartTransientNoToolThread(params) {
1273
- return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
1274
- }
1275
- function isEmptyDynamicToolsFingerprint(fingerprint) {
1276
- return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
1277
- }
1278
- function compareJsonFingerprint(left, right) {
1279
- return JSON.stringify(left).localeCompare(JSON.stringify(right));
1333
+ return text.slice(start);
1280
1334
  }
1281
1335
  //#endregion
1282
1336
  //#region extensions/codex/src/app-server/dynamic-tool-profile.ts
@@ -1294,12 +1348,6 @@ const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
1294
1348
  "tool_search",
1295
1349
  "tool_search_code"
1296
1350
  ];
1297
- const CODEX_NATIVE_GOAL_TOOL_EXCLUDES = [
1298
- "get_goal",
1299
- "create_goal",
1300
- "update_goal"
1301
- ];
1302
- const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
1303
1351
  const DYNAMIC_TOOL_NAME_ALIASES = {
1304
1352
  bash: "exec",
1305
1353
  "apply-patch": "apply_patch"
@@ -1309,10 +1357,6 @@ function normalizeCodexDynamicToolName(name) {
1309
1357
  const normalized = name.trim().toLowerCase();
1310
1358
  return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
1311
1359
  }
1312
- /** True only for the host-scoped OpenClaw run's exact tool contract. */
1313
- function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
1314
- return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
1315
- }
1316
1360
  /** Returns true for private QA runs that force the Codex runtime profile. */
1317
1361
  function isForcedPrivateQaCodexRuntime(env = process.env) {
1318
1362
  return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
@@ -1346,20 +1390,8 @@ function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}
1346
1390
  }
1347
1391
  /** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
1348
1392
  function filterCodexDynamicTools(tools, config, env = process.env) {
1349
- return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: false });
1350
- }
1351
- /** Keeps exec/process only when Codex cannot advertise an environment-backed native shell. */
1352
- function filterCodexDynamicToolsWithOpenClawShell(tools, config, env = process.env) {
1353
- return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: true });
1354
- }
1355
- function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
1356
1393
  const excludes = /* @__PURE__ */ new Set();
1357
- for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
1358
- if (isForcedPrivateQaCodexRuntime(env)) excludes.add("apply_patch");
1359
- else for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
1360
- if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
1361
- excludes.add(name);
1362
- }
1394
+ if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) excludes.add(name);
1363
1395
  for (const name of config.codexDynamicToolsExclude ?? []) {
1364
1396
  const trimmed = normalizeCodexDynamicToolName(name);
1365
1397
  if (trimmed) excludes.add(trimmed);
@@ -1367,268 +1399,98 @@ function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
1367
1399
  return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
1368
1400
  }
1369
1401
  //#endregion
1370
- //#region extensions/codex/src/app-server/profiler-flag.ts
1371
- const PROFILER_FLAGS = ["profiler", "codex.profiler"];
1372
- /** Checks the generic and Codex-specific profiler diagnostic flags. */
1373
- function isCodexAppServerProfilerEnabled(config, env = process.env) {
1374
- return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
1375
- }
1376
- //#endregion
1377
- //#region extensions/codex/src/app-server/thread-binding-policy.ts
1378
- function shouldRotateCodexAppServerBindingForRuntime(params) {
1379
- if (!params.current) return false;
1380
- if (params.binding === params.current) return false;
1381
- return params.connectionClass === "remote" || Boolean(params.binding);
1382
- }
1383
- function resolveCodexGpt56MultiAgentVersion(modelRef) {
1384
- let modelId = modelRef?.trim().toLowerCase();
1385
- if (!modelId) return;
1386
- const slashIndex = modelId.indexOf("/");
1387
- if (slashIndex > 0) {
1388
- const provider = modelId.slice(0, slashIndex);
1389
- if (provider !== "openai" && provider !== "codex") return;
1390
- modelId = modelId.slice(slashIndex + 1);
1391
- }
1392
- if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
1393
- return modelId === "gpt-5.6-luna" ? "v1" : void 0;
1394
- }
1395
- function shouldRotateCodexGpt56MultiAgentBinding(params) {
1396
- const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
1397
- const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
1398
- return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
1399
- }
1400
- function isTransientWebSearchRestriction(params) {
1401
- if (params.nativeProviderWebSearchSupport === "unknown") return true;
1402
- if (params.params.config?.tools?.web?.search?.enabled === false) return false;
1403
- if (params.params.disableTools === true) return true;
1404
- const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
1405
- if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
1406
- if (params.webSearchAllowed !== false) return false;
1407
- if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
1408
- if (params.params.toolsAllow === void 0) return false;
1409
- return !params.params.toolsAllow.some((name) => {
1410
- const normalized = normalizeCodexDynamicToolName(name);
1411
- return normalized === "*" || normalized === "web_search";
1412
- });
1402
+ //#region extensions/codex/src/app-server/image-payload-sanitizer.ts
1403
+ /**
1404
+ * Sanitizes inline image payloads mirrored through Codex history so invalid
1405
+ * base64 data becomes readable text instead of poisoning replayed transcripts.
1406
+ */
1407
+ const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
1408
+ /** Validates and normalizes an inline image data URL for Codex history payloads. */
1409
+ function sanitizeInlineImageDataUrl$1(imageUrl) {
1410
+ return sanitizeInlineImageDataUrl(imageUrl);
1413
1411
  }
1414
- function shouldRecheckRecoverablePluginBinding(params) {
1415
- if (!params.pluginThreadConfig?.enabled) return false;
1416
- if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
1417
- const policyContext = params.binding.pluginAppPolicyContext;
1418
- if (!policyContext) return false;
1419
- const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
1420
- const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
1421
- const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
1422
- 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));
1423
- const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
1424
- return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
1412
+ /** Builds the replacement text inserted when an inline image payload is invalid. */
1413
+ function invalidInlineImageText(label) {
1414
+ return `[${label}] ${IMAGE_OMITTED_TEXT}`;
1425
1415
  }
1426
- //#endregion
1427
- //#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
1428
- var CodexThreadStartRequestError = class extends Error {
1429
- constructor(cause) {
1430
- super(formatErrorMessage(cause), { cause });
1431
- this.name = "CodexThreadStartRequestError";
1432
- }
1433
- };
1434
- var CodexThreadBindingConflictError = class extends Error {
1435
- constructor(threadId, operation) {
1436
- super(`Codex thread binding changed while ${operation}: ${threadId}`);
1437
- this.name = "CodexThreadBindingConflictError";
1416
+ function sanitizeImageContentRecord(record, label) {
1417
+ if (record.type === "image" && typeof record.data === "string") {
1418
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
1419
+ const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
1420
+ if (!imageUrl) return {
1421
+ type: "text",
1422
+ text: invalidInlineImageText(label)
1423
+ };
1424
+ const commaIndex = imageUrl.indexOf(",");
1425
+ const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
1426
+ return {
1427
+ ...record,
1428
+ mimeType: mime,
1429
+ data: imageUrl.slice(commaIndex + 1)
1430
+ };
1438
1431
  }
1439
- };
1440
- var CodexRingZeroAttestationError = class extends Error {
1441
- constructor(cause) {
1442
- super("Codex ring-zero MCP attestation failed", { cause });
1443
- this.name = "CodexRingZeroAttestationError";
1432
+ if (record.type === "inputImage" && typeof record.imageUrl === "string") {
1433
+ const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
1434
+ return imageUrl ? {
1435
+ ...record,
1436
+ imageUrl
1437
+ } : {
1438
+ type: "inputText",
1439
+ text: invalidInlineImageText(label)
1440
+ };
1444
1441
  }
1445
- };
1446
- var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
1447
- var CodexAdoptedThreadActiveError = class extends Error {
1448
- constructor() {
1449
- super("Codex session became active in another runner; wait for it to finish before continuing");
1450
- this.name = "CodexAdoptedThreadActiveError";
1442
+ if (record.type === "input_image" && typeof record.image_url === "string") {
1443
+ const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
1444
+ return imageUrl ? {
1445
+ ...record,
1446
+ image_url: imageUrl
1447
+ } : {
1448
+ type: "input_text",
1449
+ text: invalidInlineImageText(label)
1450
+ };
1451
1451
  }
1452
- };
1453
- //#endregion
1454
- //#region extensions/codex/src/app-server/reasoning-effort.ts
1455
- const CODEX_REASONING_EFFORTS = [
1456
- "minimal",
1457
- "low",
1458
- "medium",
1459
- "high",
1460
- "xhigh",
1461
- "max",
1462
- "ultra"
1463
- ];
1464
- const GPT_56_MAX_REASONING_EFFORTS = [
1465
- "low",
1466
- "medium",
1467
- "high",
1468
- "xhigh",
1469
- "max"
1470
- ];
1471
- const GPT_56_ULTRA_REASONING_EFFORTS = [...GPT_56_MAX_REASONING_EFFORTS, "ultra"];
1472
- const GPT_5_PRO_REASONING_EFFORTS = [
1473
- "medium",
1474
- "high",
1475
- "xhigh"
1476
- ];
1477
- const GPT_56_ULTRA_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.6-sol", "gpt-5.6-terra"]);
1478
- const GPT_56_MAX_MODEL_IDS = /* @__PURE__ */ new Set([...GPT_56_ULTRA_MODEL_IDS, "gpt-5.6-luna"]);
1479
- const MODERN_CODEX_MODEL_IDS = /* @__PURE__ */ new Set([
1480
- ...GPT_56_MAX_MODEL_IDS,
1481
- "gpt-5.5",
1482
- "gpt-5.5-pro",
1483
- "gpt-5.4",
1484
- "gpt-5.4-pro",
1485
- "gpt-5.4-mini",
1486
- "gpt-5.3-codex-spark"
1487
- ]);
1488
- function normalizeCodexReasoningEfforts(efforts) {
1489
- if (!efforts) return [];
1490
- const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
1491
- return CODEX_REASONING_EFFORTS.filter((effort) => supported.has(effort));
1492
- }
1493
- /** Read reasoning metadata after the Codex app-server route has been selected. */
1494
- function readCodexSupportedReasoningEfforts(compat) {
1495
- if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
1496
- const efforts = compat.supportedReasoningEfforts;
1497
- if (!Array.isArray(efforts)) return;
1498
- return efforts.filter((effort) => typeof effort === "string");
1499
- }
1500
- function resolveSupportedReasoningEffort(params) {
1501
- const supported = normalizeCodexReasoningEfforts(params.supportedReasoningEfforts);
1502
- if (supported.includes(params.requested)) return params.requested;
1503
- const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
1504
- const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
1505
- return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
1506
- }
1507
- function resolveFallbackReasoningEfforts(modelId) {
1508
- const normalized = modelId.trim().toLowerCase();
1509
- if (GPT_56_ULTRA_MODEL_IDS.has(normalized)) return GPT_56_ULTRA_REASONING_EFFORTS;
1510
- if (normalized === "gpt-5.6-luna") return GPT_56_MAX_REASONING_EFFORTS;
1511
- if (normalized === "gpt-5.5-pro" || normalized === "gpt-5.4-pro") return GPT_5_PRO_REASONING_EFFORTS;
1512
- }
1513
- /** Resolve a turn effort from app-server metadata, with exact-name offline fallbacks. */
1514
- function resolveCodexAppServerReasoningEffort(params) {
1515
- if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
1516
- const supportedReasoningEfforts = params.supportedReasoningEfforts ?? resolveFallbackReasoningEfforts(params.modelId);
1517
- if (supportedReasoningEfforts) return resolveSupportedReasoningEffort({
1518
- requested: params.thinkLevel,
1519
- supportedReasoningEfforts
1520
- }) ?? null;
1521
- const normalizedModelId = params.modelId.trim().toLowerCase();
1522
- if (params.thinkLevel === "minimal") return MODERN_CODEX_MODEL_IDS.has(normalizedModelId) ? "low" : "minimal";
1523
- if (params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
1524
- return params.thinkLevel === "max" && GPT_56_MAX_MODEL_IDS.has(normalizedModelId) ? "max" : null;
1525
- }
1526
- //#endregion
1527
- //#region extensions/codex/src/app-server/thread-model-selection.ts
1528
- const CODEX_NATIVE_PERSONALITY_NONE = "none";
1529
- function resolveCodexBindingModelProviderFallback(params) {
1530
- const provider = params.provider?.trim().toLowerCase();
1531
- if (provider && provider !== "codex") return;
1532
- const currentModel = params.currentModel?.trim();
1533
- const bindingModel = params.bindingModel?.trim();
1534
- if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
1535
- return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
1536
- }
1537
- function resolveCodexAppServerThreadModelSelection(params) {
1538
- const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
1539
- const explicitModelProvider = resolveCodexAppServerModelProvider({
1540
- provider: params.provider,
1541
- authProfileId,
1542
- authProfileStore: params.authProfileStore,
1543
- agentDir: params.agentDir,
1544
- config: params.config
1545
- });
1546
- const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
1547
- provider: params.provider,
1548
- currentModel: params.model,
1549
- bindingModel: params.binding.model,
1550
- bindingModelProvider: params.binding.modelProvider
1551
- }) : void 0;
1552
- return resolveCodexAppServerRequestModelSelection({
1553
- model: params.model,
1554
- modelProvider: explicitModelProvider ?? bindingModelProvider,
1555
- authProfileId,
1556
- authProfileStore: params.authProfileStore,
1557
- agentDir: params.agentDir,
1558
- config: params.config
1559
- });
1560
- }
1561
- function resolveCodexAppServerRequestModelSelection(params) {
1562
- const model = params.model.trim();
1563
- const modelProvider = params.modelProvider?.trim();
1564
- if (modelProvider) return {
1565
- model,
1566
- modelProvider
1567
- };
1568
- const slashIndex = model.indexOf("/");
1569
- if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
1570
- const inferredModelProvider = resolveCodexAppServerModelProvider({
1571
- provider: model.slice(0, slashIndex),
1572
- authProfileId: params.authProfileId,
1573
- authProfileStore: params.authProfileStore,
1574
- agentDir: params.agentDir,
1575
- config: params.config
1576
- });
1577
- return {
1578
- model: model.slice(slashIndex + 1).trim(),
1579
- ...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
1580
- };
1581
- }
1582
- function hasProviderQualifiedModelRef(model) {
1583
- const trimmed = model?.trim();
1584
- const slashIndex = trimmed?.indexOf("/") ?? -1;
1585
- return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
1586
- }
1587
- function resolveCodexAppServerModelProvider(params) {
1588
- const normalized = params.provider.trim();
1589
- const normalizedLower = normalized.toLowerCase();
1590
- if (!normalized || normalizedLower === "codex") return;
1591
- if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
1592
- return normalizedLower === "openai" ? "openai" : normalized;
1593
1452
  }
1594
- function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
1595
- return resolveCodexAppServerReasoningEffort({
1596
- thinkLevel,
1597
- modelId,
1598
- supportedReasoningEfforts
1599
- });
1453
+ /** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
1454
+ function sanitizeCodexHistoryImagePayloads(value, label) {
1455
+ if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
1456
+ if (!isRecord(value)) return value;
1457
+ const imageRecord = sanitizeImageContentRecord(value, label);
1458
+ if (imageRecord) return imageRecord;
1459
+ const next = {};
1460
+ for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
1461
+ return next;
1600
1462
  }
1601
1463
  //#endregion
1602
- //#region extensions/codex/src/app-server/thread-prompt.ts
1603
- function buildDeveloperInstructions(params, options = {}) {
1604
- const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
1605
- surface: "codex_app_server",
1606
- includeLegacyGlobalGuidance: false
1607
- }).join("\n");
1608
- return [
1609
- "You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
1610
- buildDeferredDynamicToolManifest(options.dynamicTools),
1611
- buildSkillWorkshopInstruction(options.dynamicTools),
1612
- "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`.",
1613
- buildVisibleReplyInstruction(params, options.dynamicTools),
1614
- nativeCommandGuidance,
1615
- params.extraSystemPrompt
1616
- ].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
1617
- }
1618
- function buildDeferredDynamicToolManifest(dynamicTools) {
1619
- const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
1620
- if (deferredToolNames.length === 0) return;
1621
- return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
1622
- }
1623
- function buildSkillWorkshopInstruction(dynamicTools) {
1624
- if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
1625
- return buildSkillWorkshopPromptSection().join("\n");
1464
+ //#region extensions/codex/src/app-server/profiler-flag.ts
1465
+ const PROFILER_FLAGS = ["profiler", "codex.profiler"];
1466
+ /** Checks the generic and Codex-specific profiler diagnostic flags. */
1467
+ function isCodexAppServerProfilerEnabled(config, env = process.env) {
1468
+ return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
1626
1469
  }
1627
- function buildVisibleReplyInstruction(params, dynamicTools) {
1628
- const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
1629
- 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.";
1630
- 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.";
1631
- return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
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;
1632
1494
  }
1633
1495
  //#endregion
1634
1496
  //#region extensions/codex/src/app-server/web-search.ts
@@ -1697,589 +1559,32 @@ function resolveCodexWebSearchPlan(params) {
1697
1559
  threadConfig: buildCodexNativeWebSearchThreadConfig(params.config)
1698
1560
  };
1699
1561
  }
1562
+ //#endregion
1563
+ //#region extensions/codex/src/app-server/thread-lifecycle.ts
1564
+ var CodexThreadStartRequestError = class extends Error {
1565
+ constructor(cause) {
1566
+ super(formatErrorMessage(cause), { cause });
1567
+ this.name = "CodexThreadStartRequestError";
1568
+ }
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
+ };
1576
+ const CODEX_NATIVE_PERSONALITY_NONE = "none";
1700
1577
  const CODEX_CODE_MODE_THREAD_CONFIG = {
1701
1578
  "features.code_mode": true,
1702
1579
  "features.code_mode_only": false,
1703
1580
  "features.apply_patch_streaming_events": true
1704
1581
  };
1705
- const CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG = { "features.goals": false };
1706
1582
  const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
1707
1583
  "features.code_mode": false,
1708
1584
  "features.code_mode_only": false
1709
1585
  };
1710
1586
  const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
1711
1587
  const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
1712
- const CODEX_DELEGATION_DISABLED_THREAD_CONFIG = {
1713
- "features.multi_agent": false,
1714
- "features.multi_agent_v2": false
1715
- };
1716
- const CODEX_RING_ZERO_THREAD_CONFIG = {
1717
- "features.apps": false,
1718
- "features.current_time_reminder": false,
1719
- "features.deferred_executor": false,
1720
- "features.enable_fanout": false,
1721
- "features.goals": false,
1722
- "features.hooks": false,
1723
- "features.image_generation": false,
1724
- "features.memories": false,
1725
- "features.multi_agent": false,
1726
- "features.multi_agent_v2": false,
1727
- "features.plugins": false,
1728
- "features.standalone_web_search": false,
1729
- "features.token_budget": false,
1730
- "orchestrator.mcp.enabled": false,
1731
- "orchestrator.skills.enabled": false,
1732
- "tools.experimental_request_user_input.enabled": false,
1733
- hooks: {
1734
- PreToolUse: [],
1735
- PermissionRequest: [],
1736
- PostToolUse: [],
1737
- PreCompact: [],
1738
- PostCompact: [],
1739
- SessionStart: [],
1740
- UserPromptSubmit: [],
1741
- SubagentStart: [],
1742
- SubagentStop: [],
1743
- Stop: []
1744
- },
1745
- project_doc_max_bytes: 0,
1746
- notify: [],
1747
- web_search: "disabled"
1748
- };
1749
- const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
1750
- "apps",
1751
- "code_mode",
1752
- "code_mode_only",
1753
- "current_time_reminder",
1754
- "deferred_executor",
1755
- "enable_fanout",
1756
- "goals",
1757
- "hooks",
1758
- "image_generation",
1759
- "memories",
1760
- "multi_agent",
1761
- "multi_agent_v2",
1762
- "plugins",
1763
- "standalone_web_search",
1764
- "token_budget"
1765
- ]);
1766
- const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
1767
- "mdm",
1768
- "system",
1769
- "enterpriseManaged",
1770
- "user",
1771
- "project",
1772
- "sessionFlags"
1773
- ]);
1774
- function buildThreadStartParams(params, options) {
1775
- const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
1776
- const resolvedModelProvider = resolveCodexAppServerModelProvider({
1777
- provider: params.provider,
1778
- authProfileId: params.authProfileId,
1779
- authProfileStore: params.authProfileStore,
1780
- agentDir: params.agentDir,
1781
- config: params.config
1782
- });
1783
- const modelSelection = resolveCodexAppServerRequestModelSelection({
1784
- model: options.model ?? params.modelId,
1785
- modelProvider: options.modelProvider ?? resolvedModelProvider,
1786
- authProfileId: params.authProfileId,
1787
- authProfileStore: params.authProfileStore,
1788
- agentDir: params.agentDir,
1789
- config: params.config
1790
- });
1791
- return {
1792
- model: modelSelection.model,
1793
- ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
1794
- cwd: options.cwd,
1795
- approvalPolicy: options.appServer.approvalPolicy,
1796
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
1797
- ...codexThreadSandboxOrPermissions(options.appServer),
1798
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
1799
- personality: CODEX_NATIVE_PERSONALITY_NONE,
1800
- serviceName: "OpenClaw",
1801
- ...ringZeroActive ? { baseInstructions: "" } : {},
1802
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
1803
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
1804
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
1805
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
1806
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
1807
- webSearchAllowed: options.webSearchAllowed,
1808
- appServer: options.appServer,
1809
- hostSystemAgentActive: options.hostSystemAgentActive,
1810
- ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
1811
- }),
1812
- ...resolveCodexThreadEnvironmentSelection(options),
1813
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
1814
- dynamicTools: [...options.dynamicTools],
1815
- experimentalRawEvents: true,
1816
- ...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
1817
- };
1818
- }
1819
- function buildThreadResumeParams(params, options) {
1820
- const modelSelection = options.preserveNativeModel ? void 0 : resolveCodexAppServerRequestModelSelection({
1821
- model: options.model ?? params.modelId,
1822
- modelProvider: options.modelProvider ?? resolveCodexAppServerModelProvider({
1823
- provider: params.provider,
1824
- authProfileId: options.authProfileId ?? params.authProfileId,
1825
- authProfileStore: params.authProfileStore,
1826
- agentDir: params.agentDir,
1827
- config: params.config
1828
- }),
1829
- authProfileId: options.authProfileId ?? params.authProfileId,
1830
- authProfileStore: params.authProfileStore,
1831
- agentDir: params.agentDir,
1832
- config: params.config
1833
- });
1834
- return {
1835
- threadId: options.threadId,
1836
- excludeTurns: true,
1837
- initialTurnsPage: {
1838
- limit: 1,
1839
- sortDirection: "desc",
1840
- itemsView: "notLoaded"
1841
- },
1842
- ...modelSelection ? {
1843
- model: modelSelection.model,
1844
- ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
1845
- } : {},
1846
- approvalPolicy: options.appServer.approvalPolicy,
1847
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
1848
- ...codexThreadSandboxOrPermissions(options.appServer),
1849
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
1850
- personality: CODEX_NATIVE_PERSONALITY_NONE,
1851
- config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
1852
- nativeCodeModeEnabled: options.nativeCodeModeEnabled,
1853
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
1854
- nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
1855
- directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
1856
- webSearchAllowed: options.webSearchAllowed,
1857
- appServer: options.appServer,
1858
- hostSystemAgentActive: options.hostSystemAgentActive,
1859
- ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
1860
- }),
1861
- developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
1862
- };
1863
- }
1864
- function buildCodexRuntimeThreadConfig(config, options = {}) {
1865
- const codeModeConfig = {
1866
- ...CODEX_CODE_MODE_THREAD_CONFIG,
1867
- "features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
1868
- };
1869
- if (options.nativeCodeModeEnabled === false) {
1870
- const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
1871
- delete disabledConfig["features.apply_patch_streaming_events"];
1872
- return disabledConfig;
1873
- }
1874
- if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true }) ?? {
1875
- ...codeModeConfig,
1876
- ...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
1877
- "features.code_mode_only": true
1878
- }, options.directOnlyToolNamespaces);
1879
- return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG) ?? {
1880
- ...codeModeConfig,
1881
- ...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG
1882
- }, options.directOnlyToolNamespaces);
1883
- }
1884
- function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
1885
- if (!requiredNamespaces?.length) return config;
1886
- const configured = config["code_mode.direct_only_tool_namespaces"];
1887
- const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
1888
- return {
1889
- ...config,
1890
- "code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
1891
- };
1892
- }
1893
- function resolveDirectOnlyToolNamespaces(dynamicTools) {
1894
- return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
1895
- }
1896
- function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
1897
- const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
1898
- const configMcpServers = config?.mcp_servers;
1899
- if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
1900
- const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
1901
- const webSearchConfig = resolveCodexWebSearchPlan({
1902
- config: params.config,
1903
- disableTools: params.disableTools,
1904
- nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
1905
- nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
1906
- webSearchAllowed: options.webSearchAllowed
1907
- }).threadConfig;
1908
- const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
1909
- const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
1910
- if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
1911
- return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
1912
- ...runtimeConfig,
1913
- ...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
1914
- };
1915
- }
1916
- function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
1917
- if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
1918
- const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
1919
- return {
1920
- ...CODEX_RING_ZERO_THREAD_CONFIG,
1921
- ...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
1922
- };
1923
- }
1924
- async function readCodexInheritedMcpServerNames(client, cwd, signal) {
1925
- const response = await client.request("config/read", {
1926
- cwd,
1927
- includeLayers: true
1928
- }, { signal });
1929
- if (!isJsonObject(response) || !isJsonObject(response.config)) throw new Error("Codex config/read returned an invalid effective config");
1930
- if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
1931
- for (const layer of response.layers) {
1932
- if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
1933
- if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex ring-zero cannot override config layer ${layer.name.type}`);
1934
- 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}`);
1935
- }
1936
- const configuredServers = response.config.mcp_servers;
1937
- if (configuredServers === void 0) return [];
1938
- if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
1939
- return Object.keys(configuredServers).toSorted();
1940
- }
1941
- async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
1942
- const response = await client.request("configRequirements/read", void 0, { signal });
1943
- if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
1944
- if (response.requirements === null) return;
1945
- if (!isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
1946
- for (const key of [
1947
- "hooks",
1948
- "managedHooks",
1949
- "managed_hooks"
1950
- ]) {
1951
- const hooks = response.requirements[key];
1952
- if (hooks === void 0 || hooks === null) continue;
1953
- if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
1954
- if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex ring-zero cannot override managed hooks");
1955
- }
1956
- for (const key of ["featureRequirements", "feature_requirements"]) {
1957
- const requirements = response.requirements[key];
1958
- if (requirements === void 0 || requirements === null) continue;
1959
- if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
1960
- for (const [feature, enabled] of Object.entries(requirements)) {
1961
- if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
1962
- if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(feature)) throw new Error(`Codex ring-zero cannot override required feature ${feature}`);
1963
- }
1964
- }
1965
- }
1966
- async function attestCodexRingZeroThreadHasNoMcpServers(client, threadId, signal) {
1967
- const response = await client.request("mcpServerStatus/list", {
1968
- threadId,
1969
- limit: 1,
1970
- detail: "toolsAndAuthOnly"
1971
- }, { signal });
1972
- if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid ring-zero attestation");
1973
- if (response.data.length > 0) {
1974
- const first = response.data[0];
1975
- const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
1976
- throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
1977
- }
1978
- if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
1979
- }
1980
- function hasNonEmptyJsonValue(value) {
1981
- if (value === null || value === false || value === "") return false;
1982
- if (Array.isArray(value)) return value.length > 0;
1983
- if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
1984
- return true;
1985
- }
1986
- function resolveCodexThreadApprovalsReviewer(appServer, config) {
1987
- return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
1988
- }
1989
- function codexThreadSandboxOrPermissions(appServer) {
1990
- if (appServer.networkProxy) return {};
1991
- return { sandbox: appServer.sandbox };
1992
- }
1993
- function resolveCodexThreadEnvironmentSelection(options) {
1994
- if (options.nativeCodeModeEnabled === false) return { environments: [] };
1995
- if (options.environmentSelection) return { environments: options.environmentSelection };
1996
- return {};
1997
- }
1998
- //#endregion
1999
- //#region extensions/codex/src/app-server/thread-resume.ts
2000
- /** Owns Codex thread/resume subscription safety. */
2001
- /** Resumes one thread and retires the physical client when acceptance is indeterminate. */
2002
- async function resumeCodexAppServerThread(params) {
2003
- const threadId = params.request.threadId;
2004
- let response;
2005
- try {
2006
- response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
2007
- ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
2008
- ...params.signal ? { signal: params.signal } : {}
2009
- }));
2010
- assertCodexThreadResumeSubscription(threadId, response.thread.id);
2011
- } catch (error) {
2012
- if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
2013
- if (error instanceof CodexAppServerRpcError) throw error;
2014
- try {
2015
- await params.abandonClient();
2016
- } catch (abandonError) {
2017
- throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
2018
- }
2019
- if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
2020
- throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
2021
- }
2022
- return response;
2023
- }
2024
- //#endregion
2025
- //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
2026
- async function resumeExistingCodexThread(params, context) {
2027
- const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
2028
- let resumeReservation;
2029
- try {
2030
- const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
2031
- const finalConfigPatch = params.buildFinalConfigPatch?.({
2032
- action: "resume",
2033
- binding: resumeBinding
2034
- }) ?? {
2035
- configPatch: params.finalConfigPatch,
2036
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
2037
- };
2038
- const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
2039
- const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
2040
- const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
2041
- threadId: resumeBinding.threadId,
2042
- authProfileId,
2043
- model: startModelSelection.model,
2044
- modelProvider: startModelProvider,
2045
- preserveNativeModel: resumeBinding.preserveNativeModel === true,
2046
- appServer: params.appServer,
2047
- dynamicTools: params.dynamicTools,
2048
- developerInstructions: params.developerInstructions,
2049
- config: resumeConfig,
2050
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2051
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2052
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2053
- webSearchAllowed: params.webSearchAllowed,
2054
- hostSystemAgentActive,
2055
- ringZeroInheritedMcpServerNames
2056
- }));
2057
- const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
2058
- throwIfAborted();
2059
- if (resumeBinding.preserveNativeModel === true) {
2060
- const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
2061
- threadId: resumeBinding.threadId,
2062
- includeTurns: false
2063
- }, { signal: params.signal }));
2064
- throwIfAborted();
2065
- if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
2066
- }
2067
- resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
2068
- const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
2069
- client: params.client,
2070
- abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
2071
- request: resumeParams,
2072
- signal: params.signal
2073
- }));
2074
- if (ringZeroActive) try {
2075
- await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
2076
- } catch (error) {
2077
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2078
- throw new CodexRingZeroAttestationError(error);
2079
- }
2080
- throwIfAborted();
2081
- const boundAuthProfileId = authProfileId;
2082
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
2083
- const resumePatch = {
2084
- cwd: params.cwd,
2085
- authProfileId: boundAuthProfileId,
2086
- model: response.model ?? resumeParams.model ?? params.params.modelId,
2087
- preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
2088
- modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
2089
- dynamicToolsFingerprint,
2090
- dynamicToolsContainDeferred,
2091
- webSearchThreadConfigFingerprint,
2092
- userMcpServersFingerprint,
2093
- mcpServersFingerprint: nextMcpServersFingerprint,
2094
- ringZeroConfigFingerprint,
2095
- ringZeroClientInstanceId,
2096
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2097
- networkProxyConfigFingerprint,
2098
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
2099
- appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
2100
- pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
2101
- pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
2102
- pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
2103
- contextEngine: contextEngineBinding,
2104
- environmentSelectionFingerprint
2105
- };
2106
- if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2107
- kind: "patch",
2108
- threadId: resumeBinding.threadId,
2109
- patch: resumePatch
2110
- }))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
2111
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2112
- sessionId: params.params.sessionId,
2113
- sessionKey: params.params.sessionKey,
2114
- threadId: response.thread.id,
2115
- engineId: contextEngineBinding.engineId,
2116
- epoch: contextEngineBinding.projection?.epoch,
2117
- fingerprint: contextEngineBinding.projection?.fingerprint,
2118
- action: "resumed"
2119
- });
2120
- lifecycleTiming.mark("thread-ready");
2121
- lifecycleTiming.logSummary({
2122
- runId: params.params.runId,
2123
- sessionId: params.params.sessionId,
2124
- sessionKey: params.params.sessionKey,
2125
- threadId: response.thread.id,
2126
- action: "resumed"
2127
- });
2128
- const activeTurnIds = readActiveCodexTurnIdsFromResume(response);
2129
- return {
2130
- ...resumeBinding,
2131
- threadId: response.thread.id,
2132
- ...resumePatch,
2133
- lifecycle: {
2134
- action: "resumed",
2135
- ...activeTurnIds.length ? { activeTurnIds } : {}
2136
- }
2137
- };
2138
- } catch (error) {
2139
- resumeReservation?.release();
2140
- if (isCodexAppServerStartSelectionChangedError(error)) throw error;
2141
- if (error instanceof CodexRingZeroAttestationError) {
2142
- await clearCurrentBinding("retiring a failed ring-zero thread attestation");
2143
- throw error;
2144
- }
2145
- if (error instanceof CodexAdoptedThreadActiveError) throw error;
2146
- if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
2147
- const resumeRejected = error instanceof CodexAppServerRpcError;
2148
- if (!await unsubscribeCodexThreadBestEffort(params.client, {
2149
- threadId: resumeBinding.threadId,
2150
- timeoutMs: 5e3
2151
- }) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
2152
- if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
2153
- embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
2154
- await clearCurrentBinding("rotating a stale thread binding");
2155
- }
2156
- }
2157
- async function startFreshCodexThread(params, context) {
2158
- const clientId = resolveCodexAppServerClientInstanceId(params.client);
2159
- const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding } = context;
2160
- const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2161
- const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2162
- configPatch: params.finalConfigPatch,
2163
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
2164
- };
2165
- const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
2166
- const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
2167
- cwd: params.cwd,
2168
- dynamicTools: params.dynamicTools,
2169
- appServer: params.appServer,
2170
- developerInstructions: params.developerInstructions,
2171
- config,
2172
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2173
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2174
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2175
- webSearchAllowed: params.webSearchAllowed,
2176
- environmentSelection: params.environmentSelection,
2177
- model: startModelSelection.model,
2178
- modelProvider: startModelProvider,
2179
- hostSystemAgentActive,
2180
- ringZeroInheritedMcpServerNames
2181
- }));
2182
- const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
2183
- const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
2184
- try {
2185
- return await params.client.request("thread/start", startParams, { signal: params.signal });
2186
- } catch (error) {
2187
- if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
2188
- throw error;
2189
- }
2190
- }));
2191
- if (ringZeroActive) try {
2192
- await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
2193
- } catch (error) {
2194
- await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2195
- throw error;
2196
- }
2197
- throwIfAborted();
2198
- const modelProvider = resolveCodexAppServerModelProvider({
2199
- provider: params.params.provider,
2200
- authProfileId: params.params.authProfileId,
2201
- authProfileStore: params.params.authProfileStore,
2202
- agentDir: params.params.agentDir,
2203
- config: params.params.config
2204
- });
2205
- const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2206
- if (!preserveExistingBinding) {
2207
- if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2208
- kind: "set",
2209
- if: { kind: "absent" },
2210
- binding: {
2211
- threadId: response.thread.id,
2212
- ...clientId ? { clientId } : {},
2213
- cwd: params.cwd,
2214
- authProfileId: params.params.authProfileId,
2215
- model: response.model ?? startParams.model ?? params.params.modelId,
2216
- modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
2217
- dynamicToolsFingerprint,
2218
- dynamicToolsContainDeferred,
2219
- webSearchThreadConfigFingerprint,
2220
- userMcpServersFingerprint,
2221
- mcpServersFingerprint: nextMcpServersFingerprint,
2222
- ringZeroConfigFingerprint,
2223
- ringZeroClientInstanceId,
2224
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2225
- networkProxyConfigFingerprint,
2226
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2227
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2228
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2229
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2230
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2231
- contextEngine: contextEngineBinding,
2232
- environmentSelectionFingerprint
2233
- }
2234
- }))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
2235
- if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2236
- sessionId: params.params.sessionId,
2237
- sessionKey: params.params.sessionKey,
2238
- threadId: response.thread.id,
2239
- engineId: contextEngineBinding.engineId,
2240
- epoch: contextEngineBinding.projection?.epoch,
2241
- fingerprint: contextEngineBinding.projection?.fingerprint,
2242
- action: rotatedContextEngineBinding ? "rotated" : "started"
2243
- });
2244
- }
2245
- lifecycleTiming.mark("thread-ready");
2246
- lifecycleTiming.logSummary({
2247
- runId: params.params.runId,
2248
- sessionId: params.params.sessionId,
2249
- sessionKey: params.params.sessionKey,
2250
- threadId: response.thread.id,
2251
- action: rotatedContextEngineBinding ? "rotated" : "started"
2252
- });
2253
- return {
2254
- threadId: response.thread.id,
2255
- ...clientId ? { clientId } : {},
2256
- cwd: params.cwd,
2257
- authProfileId: params.params.authProfileId,
2258
- model: response.model ?? startParams.model ?? params.params.modelId,
2259
- modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
2260
- dynamicToolsFingerprint,
2261
- dynamicToolsContainDeferred,
2262
- userMcpServersFingerprint,
2263
- mcpServersFingerprint: nextMcpServersFingerprint,
2264
- ringZeroConfigFingerprint,
2265
- ringZeroClientInstanceId,
2266
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2267
- networkProxyConfigFingerprint,
2268
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2269
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2270
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2271
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2272
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2273
- contextEngine: contextEngineBinding,
2274
- environmentSelectionFingerprint,
2275
- lifecycle: {
2276
- action: "started",
2277
- ...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
2278
- }
2279
- };
2280
- }
2281
- //#endregion
2282
- //#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
2283
1588
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
2284
1589
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
2285
1590
  function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
@@ -2367,378 +1672,34 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
2367
1672
  }
2368
1673
  };
2369
1674
  }
2370
- //#endregion
2371
- //#region extensions/codex/src/app-server/thread-supervision.ts
2372
- async function materializePendingSupervisionBranch(params) {
2373
- let pending = params.binding.pendingSupervisionBranch;
2374
- const connectionFingerprint = buildCodexAppServerConnectionFingerprint(params.appServer, params.attempt.agentDir);
2375
- if (!pending.connectionFingerprint || pending.connectionFingerprint !== connectionFingerprint) throw new Error("Codex supervision source connection changed before branch materialization");
2376
- pending = await recoverPendingSupervisionArtifacts(params, pending);
2377
- params.throwIfAborted();
2378
- const sourceResponse = await params.lifecycleTiming.measure("supervision-source-read", () => params.client.request("thread/read", {
2379
- threadId: pending.sourceThreadId,
2380
- includeTurns: true
2381
- }, { signal: params.signal }));
2382
- params.throwIfAborted();
2383
- const sourceThread = sourceResponse.thread;
2384
- if (sourceThread.id !== pending.sourceThreadId) throw new Error(`Codex supervision source read returned ${sourceThread.id} for ${pending.sourceThreadId}`);
2385
- assertPendingSupervisionSnapshotUnchanged(sourceThread, pending);
2386
- const history = projectBoundedCodexThreadHistory({
2387
- thread: sourceThread,
2388
- throughTurnId: pending.lastTurnId ?? null,
2389
- importedAt: Date.now(),
2390
- modelProvider: sourceThread.modelProvider
1675
+ async function startOrResumeThread(params) {
1676
+ const bindingIdentity = sessionBindingIdentity({
1677
+ sessionId: params.params.sessionId,
1678
+ sessionKey: params.params.sessionKey,
1679
+ agentId: params.agentId ?? params.params.agentId,
1680
+ config: params.params.config
2391
1681
  });
2392
- let bindingCommitted = false;
2393
- let provisionalCleanupSafe = true;
2394
- try {
2395
- const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
2396
- const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
2397
- try {
2398
- return await params.client.request("thread/fork", probeParams, { signal: params.signal });
2399
- } catch (error) {
2400
- if (!(error instanceof CodexAppServerRpcError)) throw new CodexAppServerUnsafeSubscriptionError("Codex model probe fork may have materialized without a response", { cause: error });
2401
- throw error;
2402
- }
2403
- });
2404
- const probeThreadId = requireDistinctSupervisionThreadId({
2405
- threadId: readSupervisionResponseThreadId(rawProbeResponse),
2406
- sourceThreadId: pending.sourceThreadId,
2407
- role: "model probe"
2408
- });
2409
- pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId]);
2410
- params.throwIfAborted();
2411
- const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
2412
- const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
2413
- const nativeModelProvider = requireNativeSupervisionModelProvider({
2414
- responseModelProvider: probeResponse.modelProvider,
2415
- responseThreadModelProvider: probeResponse.thread.modelProvider
1682
+ return await params.bindingStore.withLease(bindingIdentity, async () => {
1683
+ const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
1684
+ ...params.timing,
1685
+ enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
2416
1686
  });
2417
- const startParams = buildThreadStartParams({
2418
- ...params.attempt,
2419
- modelId: nativeModel
2420
- }, {
2421
- cwd: params.cwd,
2422
- dynamicTools: params.dynamicTools,
2423
- appServer: params.appServer,
2424
- developerInstructions: params.developerInstructions,
2425
- config: params.config,
2426
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2427
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2428
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2429
- webSearchAllowed: params.webSearchAllowed,
2430
- environmentSelection: params.environmentSelection,
2431
- model: nativeModel,
2432
- modelProvider: nativeModelProvider
2433
- });
2434
- assertExactSupervisionModelSelection(startParams, {
2435
- model: nativeModel,
2436
- modelProvider: nativeModelProvider,
2437
- operation: "thread/start request"
2438
- });
2439
- const rawStartResponse = await params.lifecycleTiming.measure("supervision-thread-start", async () => {
2440
- try {
2441
- return await params.client.request("thread/start", startParams, { signal: params.signal });
2442
- } catch (error) {
2443
- if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
2444
- throw new CodexAppServerUnsafeSubscriptionError("Canonical Codex branch may have started without a response", { cause: error });
2445
- }
2446
- });
2447
- const finalThreadId = requireDistinctSupervisionThreadId({
2448
- threadId: readSupervisionResponseThreadId(rawStartResponse),
2449
- sourceThreadId: pending.sourceThreadId,
2450
- otherThreadId: probeThreadId,
2451
- role: "canonical branch"
2452
- });
2453
- pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId, finalThreadId]);
2454
- params.throwIfAborted();
2455
- assertExactSupervisionModelSelection(assertCodexThreadStartResponse(rawStartResponse), {
2456
- model: nativeModel,
2457
- modelProvider: nativeModelProvider,
2458
- operation: "thread/start response"
2459
- });
2460
- if (history.responseItems.length > 0) {
2461
- await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
2462
- threadId: finalThreadId,
2463
- items: history.responseItems
2464
- }, { signal: params.signal }));
2465
- params.throwIfAborted();
2466
- }
2467
- if (!await archiveSupervisionArtifact(params.client, probeThreadId)) throw new Error(`Failed to archive temporary Codex model probe: ${probeThreadId}`);
2468
- pending = await trackPendingSupervisionArtifacts(params, pending, [finalThreadId]);
2469
- const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
2470
- const bindingModelProvider = params.normalizeBindingModelProvider(params.attempt.authProfileId, nativeModelProvider);
2471
- let committed = false;
2472
- try {
2473
- committed = await params.bindingStore.mutate(params.bindingIdentity, {
2474
- kind: "commit-pending-supervision-branch",
2475
- expected: pending,
2476
- threadId: finalThreadId,
2477
- patch: {
2478
- ...params.bindingPatch,
2479
- model: nativeModel,
2480
- modelProvider: bindingModelProvider,
2481
- historyCoveredThrough
2482
- }
2483
- });
2484
- } catch (error) {
2485
- let current;
2486
- try {
2487
- current = await params.bindingStore.read(params.bindingIdentity);
2488
- } catch (readError) {
2489
- provisionalCleanupSafe = false;
2490
- throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding could not be verified: ${finalThreadId}`, { cause: new AggregateError([error, readError]) });
2491
- }
2492
- if (matchesMaterializedSupervisionBranch(current, {
2493
- sourceThreadId: pending.sourceThreadId,
2494
- connectionFingerprint,
2495
- threadId: finalThreadId,
2496
- model: nativeModel,
2497
- modelProvider: bindingModelProvider,
2498
- historyCoveredThrough
2499
- })) committed = true;
2500
- else {
2501
- if (!matchesPendingSupervisionState(current, pending)) {
2502
- provisionalCleanupSafe = false;
2503
- throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding changed while commit was uncertain: ${finalThreadId}`, { cause: error });
2504
- }
2505
- throw error;
2506
- }
2507
- }
2508
- if (!committed) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "committing a supervised Codex branch");
2509
- bindingCommitted = true;
2510
- params.lifecycleTiming.mark("thread-ready");
2511
- params.lifecycleTiming.logSummary({
2512
- runId: params.attempt.runId,
2513
- sessionId: params.attempt.sessionId,
2514
- sessionKey: params.attempt.sessionKey,
2515
- threadId: finalThreadId,
2516
- action: "forked"
2517
- });
2518
- return {
2519
- ...params.binding,
2520
- ...params.bindingPatch,
2521
- threadId: finalThreadId,
2522
- pendingSupervisionBranch: void 0,
2523
- model: nativeModel,
2524
- modelProvider: bindingModelProvider,
2525
- historyCoveredThrough,
2526
- lifecycle: { action: "forked" }
2527
- };
2528
- } catch (error) {
2529
- if (bindingCommitted) throw error;
2530
- if (error instanceof CodexThreadBindingConflictAfterCleanupError) throw error;
2531
- if (!provisionalCleanupSafe) {
2532
- await params.abandonClient();
2533
- throw error;
2534
- }
2535
- const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
2536
- let cleanupStateError;
2537
- if (cleanup.remaining.length !== (pending.cleanupThreadIds?.length ?? 0)) {
2538
- const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
2539
- try {
2540
- if (await params.bindingStore.mutate(params.bindingIdentity, {
2541
- kind: "patch-pending-supervision-branch",
2542
- expected: pending,
2543
- pending: nextPending
2544
- })) pending = nextPending;
2545
- } catch (stateError) {
2546
- cleanupStateError = stateError;
2547
- }
2548
- }
2549
- const unsafeCleanup = cleanup.remaining.length > 0 || isCodexAppServerUnsafeSubscriptionError(error);
2550
- if (unsafeCleanup) await params.abandonClient();
2551
- if (cleanupStateError) {
2552
- const cause = new AggregateError([error, cleanupStateError]);
2553
- if (unsafeCleanup) throw new CodexAppServerUnsafeSubscriptionError("Codex supervised branch cleanup state could not be recorded", { cause });
2554
- throw new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
2555
- }
2556
- if (cleanup.remaining.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup remains pending: ${cleanup.remaining.join(", ")}`, { cause: error });
2557
- throw error;
2558
- }
2559
- }
2560
- function buildPendingSupervisionProbeForkParams(params, pending) {
2561
- const runtimeConfig = buildCodexRuntimeThreadConfigForRun(params.attempt, params.config, {
2562
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2563
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2564
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2565
- webSearchAllowed: params.webSearchAllowed,
2566
- appServer: params.appServer
2567
- });
2568
- return {
2569
- threadId: pending.sourceThreadId,
2570
- ...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
2571
- cwd: params.cwd,
2572
- approvalPolicy: params.appServer.approvalPolicy,
2573
- approvalsReviewer: resolveCodexThreadApprovalsReviewer(params.appServer, runtimeConfig),
2574
- ...codexThreadSandboxOrPermissions(params.appServer),
2575
- ...params.appServer.serviceTier !== void 0 ? { serviceTier: params.appServer.serviceTier } : {},
2576
- config: runtimeConfig,
2577
- developerInstructions: params.developerInstructions ?? buildDeveloperInstructions(params.attempt, { dynamicTools: params.dynamicTools }),
2578
- ephemeral: isIncognitoSessionKey(params.attempt.sessionKey),
2579
- threadSource: "appServer",
2580
- excludeTurns: true
2581
- };
2582
- }
2583
- function assertPendingSupervisionSnapshotUnchanged(thread, pending) {
2584
- if (pending.lastTurnId) return;
2585
- if (thread.status?.type === "active" || (thread.turns?.length ?? 0) > 0) throw new Error("Codex source changed after Continue; reopen the source session before sending a message");
2586
- }
2587
- function requireNonBlankSupervisionValue(value, label) {
2588
- if (typeof value !== "string" || !value.trim()) throw new Error(`Codex supervision ${label} is missing`);
2589
- return value.trim();
2590
- }
2591
- function requireNativeSupervisionModelProvider(params) {
2592
- const responseProvider = requireNonBlankSupervisionValue(params.responseModelProvider, "native model provider");
2593
- const threadProvider = params.responseThreadModelProvider?.trim();
2594
- if (threadProvider && threadProvider !== responseProvider) throw new Error(`Codex supervision model provider mismatch: ${responseProvider} != ${threadProvider}`);
2595
- return responseProvider;
2596
- }
2597
- function assertExactSupervisionModelSelection(value, expected) {
2598
- if (value.model !== expected.model || value.modelProvider !== expected.modelProvider) throw new Error(`Codex supervision ${expected.operation} changed native model selection: ${value.modelProvider ?? "unknown"}/${value.model ?? "unknown"}`);
2599
- }
2600
- function matchesPendingSupervisionState(binding, expected) {
2601
- const pending = binding?.pendingSupervisionBranch;
2602
- const cleanupThreadIds = pending?.cleanupThreadIds ?? [];
2603
- const expectedCleanupThreadIds = expected.cleanupThreadIds ?? [];
2604
- return binding?.threadId === expected.sourceThreadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && pending?.sourceThreadId === expected.sourceThreadId && pending.connectionFingerprint === expected.connectionFingerprint && pending.lastTurnId === expected.lastTurnId && cleanupThreadIds.length === expectedCleanupThreadIds.length && cleanupThreadIds.every((threadId, index) => threadId === expectedCleanupThreadIds[index]);
2605
- }
2606
- function matchesMaterializedSupervisionBranch(binding, expected) {
2607
- return binding?.threadId === expected.threadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && binding.appServerRuntimeFingerprint === expected.connectionFingerprint && binding.pendingSupervisionBranch === void 0 && binding.model === expected.model && binding.modelProvider === expected.modelProvider && binding.historyCoveredThrough === expected.historyCoveredThrough;
2608
- }
2609
- function requireDistinctSupervisionThreadId(params) {
2610
- let threadId;
2611
- try {
2612
- threadId = requireNonBlankSupervisionValue(params.threadId, `${params.role} thread id`);
2613
- } catch (error) {
2614
- throw new CodexAppServerUnsafeSubscriptionError(`Codex supervision ${params.role} may have materialized without a safe thread id`, { cause: error });
2615
- }
2616
- if (threadId === params.sourceThreadId || threadId === params.otherThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervision ${params.role} reused an existing thread: ${threadId}`);
2617
- return threadId;
2618
- }
2619
- function readSupervisionResponseThreadId(value) {
2620
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
2621
- const thread = value.thread;
2622
- if (!thread || typeof thread !== "object" || Array.isArray(thread)) return;
2623
- return thread.id;
2624
- }
2625
- async function recoverPendingSupervisionArtifacts(params, pending) {
2626
- if (!pending.cleanupThreadIds?.length) return pending;
2627
- const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
2628
- const next = withPendingSupervisionCleanup(pending, cleanup.remaining);
2629
- if (cleanup.remaining.length > 0) {
2630
- if (cleanup.remaining.length !== pending.cleanupThreadIds.length) {
2631
- if (!await params.bindingStore.mutate(params.bindingIdentity, {
2632
- kind: "patch-pending-supervision-branch",
2633
- expected: pending,
2634
- pending: next
2635
- })) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recording supervised Codex cleanup recovery");
2636
- }
2637
- throw new Error(`Codex supervised branch cleanup must finish before retry: ${cleanup.remaining.join(", ")}`);
2638
- }
2639
- if (!await params.bindingStore.mutate(params.bindingIdentity, {
2640
- kind: "patch-pending-supervision-branch",
2641
- expected: pending,
2642
- pending: next
2643
- })) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
2644
- return next;
2645
- }
2646
- async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
2647
- const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
2648
- if (!await params.bindingStore.mutate(params.bindingIdentity, {
2649
- kind: "patch-pending-supervision-branch",
2650
- expected: pending,
2651
- pending: next
2652
- })) {
2653
- const cleanupFailed = [];
2654
- for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
2655
- if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
2656
- throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
2657
- }
2658
- return next;
2659
- }
2660
- function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
2661
- return {
2662
- sourceThreadId: pending.sourceThreadId,
2663
- ...pending.connectionFingerprint ? { connectionFingerprint: pending.connectionFingerprint } : {},
2664
- ...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
2665
- ...cleanupThreadIds.length > 0 ? { cleanupThreadIds } : {}
2666
- };
2667
- }
2668
- async function cleanPendingSupervisionArtifacts(client, pending) {
2669
- const remaining = [];
2670
- for (const threadId of pending.cleanupThreadIds ?? []) if (!await archiveSupervisionArtifact(client, threadId)) remaining.push(threadId);
2671
- return { remaining };
2672
- }
2673
- async function archiveSupervisionArtifact(client, threadId) {
2674
- try {
2675
- await client.request("thread/archive", { threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
2676
- return true;
2677
- } catch (error) {
2678
- const message = formatErrorMessage(error).toLowerCase();
2679
- if (message.includes("no rollout found for thread id") || message.includes("thread not found") || message.includes("already archived")) return true;
2680
- await unsubscribeCodexThreadBestEffort(client, {
2681
- threadId,
2682
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
2683
- });
2684
- embeddedAgentLog.warn("failed to archive temporary Codex supervision thread", {
2685
- threadId,
2686
- error
2687
- });
2688
- return false;
2689
- }
2690
- }
2691
- //#endregion
2692
- //#region extensions/codex/src/app-server/thread-lifecycle-run.ts
2693
- async function startOrResumeThread(params) {
2694
- const incognito = isIncognitoSessionKey(params.params.sessionKey);
2695
- const clientId = resolveCodexAppServerClientInstanceId(params.client);
2696
- const bindingIdentity = sessionBindingIdentity({
2697
- sessionId: params.params.sessionId,
2698
- sessionKey: params.params.sessionKey,
2699
- agentId: params.agentId ?? params.params.agentId,
2700
- config: params.params.config
2701
- });
2702
- return await params.bindingStore.withLease(bindingIdentity, async () => {
2703
- const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
2704
- ...params.timing,
2705
- enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
2706
- });
2707
- const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
2708
- const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
2709
- const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
2710
- const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
2711
- config: params.params.config,
2712
- disableTools: params.params.disableTools,
2713
- nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
1687
+ const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => legacyFingerprintDynamicTools(params.dynamicTools));
1688
+ const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
1689
+ const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
1690
+ const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
1691
+ config: params.params.config,
1692
+ disableTools: params.params.disableTools,
1693
+ nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
2714
1694
  nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2715
1695
  webSearchAllowed: params.webSearchAllowed
2716
1696
  })).threadConfig);
2717
1697
  const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
2718
1698
  const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
2719
- const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
2720
- agentId: params.agentId ?? params.params.agentId,
2721
- agentDir: params.params.agentDir,
2722
- allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
2723
- onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
2724
- serverName,
2725
- error: formatErrorMessage(error)
2726
- })
2727
- });
1699
+ const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : buildCodexUserMcpServersThreadConfigPatch(params.params.config, { agentId: params.agentId ?? params.params.agentId });
2728
1700
  const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2729
1701
  const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2730
1702
  const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
2731
- const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
2732
- const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
2733
- if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
2734
- const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
2735
- if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
2736
- const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
2737
- version: 1,
2738
- baseInstructions: "",
2739
- config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
2740
- }) : void 0;
2741
- const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
2742
1703
  let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
2743
1704
  const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
2744
1705
  authProfileId,
@@ -2747,90 +1708,25 @@ async function startOrResumeThread(params) {
2747
1708
  agentDir: params.params.agentDir,
2748
1709
  config: params.params.config
2749
1710
  });
2750
- const throwIfAborted = () => {
2751
- if (!params.signal?.aborted) return;
2752
- const reason = params.signal.reason;
2753
- if (reason instanceof Error) throw reason;
2754
- const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
2755
- error.name = "AbortError";
2756
- throw error;
2757
- };
2758
1711
  if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
2759
1712
  if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
2760
1713
  bindingStore: params.bindingStore,
2761
1714
  identity: bindingIdentity,
2762
1715
  config: params.params.config
2763
- }))) throw createCodexSessionGenerationSupersededError(bindingIdentity.sessionId);
2764
- }
2765
- if (binding?.pendingSupervisionBranch) {
2766
- const pendingBinding = binding;
2767
- const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2768
- const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2769
- configPatch: params.finalConfigPatch,
2770
- nativeHookRelayGeneration: params.nativeHookRelayGeneration
2771
- };
2772
- const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
2773
- return await materializePendingSupervisionBranch({
2774
- client: params.client,
2775
- abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
2776
- bindingStore: params.bindingStore,
2777
- bindingIdentity,
2778
- binding: pendingBinding,
2779
- attempt: params.params,
2780
- cwd: params.cwd,
2781
- dynamicTools: params.dynamicTools,
2782
- appServer: params.appServer,
2783
- developerInstructions: params.developerInstructions,
2784
- config,
2785
- nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2786
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2787
- nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2788
- webSearchAllowed: params.webSearchAllowed,
2789
- environmentSelection: params.environmentSelection,
2790
- signal: params.signal,
2791
- throwIfAborted,
2792
- lifecycleTiming,
2793
- normalizeBindingModelProvider,
2794
- bindingPatch: {
2795
- cwd: params.cwd,
2796
- ...clientId ? { clientId } : {},
2797
- authProfileId: void 0,
2798
- preserveNativeModel: true,
2799
- dynamicToolsFingerprint,
2800
- dynamicToolsContainDeferred,
2801
- webSearchThreadConfigFingerprint,
2802
- userMcpServersFingerprint,
2803
- mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
2804
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2805
- networkProxyConfigFingerprint,
2806
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2807
- appServerRuntimeFingerprint: buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir),
2808
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2809
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2810
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2811
- contextEngine: contextEngineBinding,
2812
- environmentSelectionFingerprint,
2813
- conversationSourceTransferComplete: true
2814
- }
2815
- });
1716
+ }))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
2816
1717
  }
2817
1718
  const clearCurrentBinding = async (operation) => {
2818
1719
  const current = binding;
2819
1720
  if (!current?.threadId) return;
2820
- assertCodexBindingMayBeReplaced(current, operation);
2821
1721
  if (!await params.bindingStore.mutate(bindingIdentity, {
2822
1722
  kind: "clear",
2823
1723
  threadId: current.threadId
2824
1724
  })) throw new CodexThreadBindingConflictError(current.threadId, operation);
2825
1725
  binding = void 0;
2826
1726
  };
2827
- if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
2828
- embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
2829
- await clearCurrentBinding("rotating a ring-zero thread binding");
2830
- }
2831
1727
  if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
2832
1728
  connectionClass: params.appServer.connectionClass,
2833
- current: binding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
1729
+ current: params.appServerRuntimeFingerprint,
2834
1730
  binding: binding.appServerRuntimeFingerprint
2835
1731
  })) {
2836
1732
  embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
@@ -2862,10 +1758,17 @@ async function startOrResumeThread(params) {
2862
1758
  config: params.params.config
2863
1759
  });
2864
1760
  const startModelProvider = startModelSelection.modelProvider;
2865
- const transientDelegationRestriction = params.params.delegationCapability === "report_only";
2866
- let preserveExistingBinding = transientDelegationRestriction || !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
1761
+ let preserveExistingBinding = params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
2867
1762
  let rotatedContextEngineBinding = false;
2868
1763
  let prebuiltPluginThreadConfig;
1764
+ const throwIfAborted = () => {
1765
+ if (!params.signal?.aborted) return;
1766
+ const reason = params.signal.reason;
1767
+ if (reason instanceof Error) throw reason;
1768
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
1769
+ error.name = "AbortError";
1770
+ throw error;
1771
+ };
2869
1772
  const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
2870
1773
  const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
2871
1774
  const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
@@ -2873,8 +1776,7 @@ async function startOrResumeThread(params) {
2873
1776
  const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
2874
1777
  const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
2875
1778
  if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
2876
- assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
2877
- if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
1779
+ if (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport)) {
2878
1780
  embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
2879
1781
  preserveExistingBinding = true;
2880
1782
  } else {
@@ -2885,8 +1787,7 @@ async function startOrResumeThread(params) {
2885
1787
  }
2886
1788
  const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
2887
1789
  if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
2888
- assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
2889
- if (!ringZeroActive && transientWebSearchRestriction) {
1790
+ if (transientWebSearchRestriction) {
2890
1791
  embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
2891
1792
  preserveExistingBinding = true;
2892
1793
  } else {
@@ -2895,17 +1796,11 @@ async function startOrResumeThread(params) {
2895
1796
  }
2896
1797
  binding = void 0;
2897
1798
  }
2898
- if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
2899
- assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
1799
+ if (binding?.threadId && transientNativeToolRestriction) {
2900
1800
  embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
2901
1801
  preserveExistingBinding = true;
2902
1802
  binding = void 0;
2903
1803
  }
2904
- if (binding?.threadId && transientDelegationRestriction) {
2905
- assertCodexBindingMayBeReplaced(binding, "starting a delegation-restricted turn");
2906
- embeddedAgentLog.debug("codex app-server delegation restricted for turn; starting transient thread", { threadId: binding.threadId });
2907
- binding = void 0;
2908
- }
2909
1804
  if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
2910
1805
  if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
2911
1806
  embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
@@ -2976,178 +1871,494 @@ async function startOrResumeThread(params) {
2976
1871
  binding = void 0;
2977
1872
  }
2978
1873
  }
2979
- if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
2980
- assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
2981
- preserveExistingBinding = shouldStartTransientNoToolThread({
2982
- previous: binding.dynamicToolsFingerprint,
2983
- nextHasDynamicTools: params.dynamicTools.length > 0
2984
- });
2985
- if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
2986
- else {
2987
- embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
2988
- await clearCurrentBinding("rotating a stale thread binding");
2989
- }
2990
- } else if (incognito) {
2991
- if (binding.clientId && binding.clientId === clientId) {
2992
- params.buildFinalConfigPatch?.({
2993
- action: "resume",
2994
- binding
2995
- });
2996
- throwIfAborted();
2997
- lifecycleTiming.mark("thread-ready");
2998
- lifecycleTiming.logSummary({
2999
- runId: params.params.runId,
3000
- sessionId: params.params.sessionId,
3001
- sessionKey: params.params.sessionKey,
3002
- threadId: binding.threadId,
3003
- action: "resumed"
1874
+ if (binding?.threadId) {
1875
+ if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
1876
+ preserveExistingBinding = shouldStartTransientNoToolThread({
1877
+ previous: binding.dynamicToolsFingerprint,
1878
+ nextHasDynamicTools: params.dynamicTools.length > 0
3004
1879
  });
3005
- return {
3006
- ...binding,
3007
- lifecycle: { action: "resumed" }
3008
- };
1880
+ if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
1881
+ else {
1882
+ embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
1883
+ await clearCurrentBinding("rotating a stale thread binding");
1884
+ }
1885
+ } else {
1886
+ const resumeBinding = binding;
1887
+ let resumeReservation;
1888
+ try {
1889
+ const authProfileId = params.params.authProfileId ?? resumeBinding.authProfileId;
1890
+ const finalConfigPatch = params.buildFinalConfigPatch?.({
1891
+ action: "resume",
1892
+ binding: resumeBinding
1893
+ }) ?? {
1894
+ configPatch: params.finalConfigPatch,
1895
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
1896
+ };
1897
+ const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
1898
+ const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
1899
+ const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
1900
+ threadId: resumeBinding.threadId,
1901
+ authProfileId,
1902
+ model: startModelSelection.model,
1903
+ modelProvider: startModelProvider,
1904
+ appServer: params.appServer,
1905
+ dynamicTools: params.dynamicTools,
1906
+ developerInstructions: params.developerInstructions,
1907
+ config: resumeConfig,
1908
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
1909
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
1910
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
1911
+ webSearchAllowed: params.webSearchAllowed
1912
+ }));
1913
+ const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
1914
+ throwIfAborted();
1915
+ resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
1916
+ const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
1917
+ client: params.client,
1918
+ abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
1919
+ request: resumeParams,
1920
+ signal: params.signal
1921
+ }));
1922
+ throwIfAborted();
1923
+ const boundAuthProfileId = authProfileId;
1924
+ const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
1925
+ const resumePatch = {
1926
+ cwd: params.cwd,
1927
+ authProfileId: boundAuthProfileId,
1928
+ model: response.model ?? resumeParams.model ?? params.params.modelId,
1929
+ modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
1930
+ dynamicToolsFingerprint,
1931
+ dynamicToolsContainDeferred,
1932
+ webSearchThreadConfigFingerprint,
1933
+ userMcpServersFingerprint,
1934
+ mcpServersFingerprint: nextMcpServersFingerprint,
1935
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
1936
+ networkProxyConfigFingerprint,
1937
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
1938
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
1939
+ pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
1940
+ pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
1941
+ pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
1942
+ contextEngine: contextEngineBinding,
1943
+ environmentSelectionFingerprint
1944
+ };
1945
+ if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
1946
+ kind: "patch",
1947
+ threadId: resumeBinding.threadId,
1948
+ patch: resumePatch
1949
+ }))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
1950
+ if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
1951
+ sessionId: params.params.sessionId,
1952
+ sessionKey: params.params.sessionKey,
1953
+ threadId: response.thread.id,
1954
+ engineId: contextEngineBinding.engineId,
1955
+ epoch: contextEngineBinding.projection?.epoch,
1956
+ fingerprint: contextEngineBinding.projection?.fingerprint,
1957
+ action: "resumed"
1958
+ });
1959
+ lifecycleTiming.mark("thread-ready");
1960
+ lifecycleTiming.logSummary({
1961
+ runId: params.params.runId,
1962
+ sessionId: params.params.sessionId,
1963
+ sessionKey: params.params.sessionKey,
1964
+ threadId: response.thread.id,
1965
+ action: "resumed"
1966
+ });
1967
+ const activeTurnIds = readActiveCodexTurnIds(response.thread);
1968
+ return {
1969
+ ...resumeBinding,
1970
+ threadId: response.thread.id,
1971
+ ...resumePatch,
1972
+ lifecycle: {
1973
+ action: "resumed",
1974
+ ...activeTurnIds.length ? { activeTurnIds } : {}
1975
+ }
1976
+ };
1977
+ } catch (error) {
1978
+ resumeReservation?.release();
1979
+ if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
1980
+ const resumeRejected = error instanceof CodexAppServerRpcError;
1981
+ if (!await unsubscribeCodexThreadBestEffort(params.client, {
1982
+ threadId: resumeBinding.threadId,
1983
+ timeoutMs: 5e3
1984
+ }) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
1985
+ if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
1986
+ embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
1987
+ await clearCurrentBinding("rotating a stale thread binding");
1988
+ }
3009
1989
  }
3010
- await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
3011
- binding = void 0;
3012
- } else {
3013
- const resumed = await resumeExistingCodexThread(params, {
3014
- binding,
3015
- bindingIdentity,
3016
- startModelSelection,
3017
- startModelProvider,
3018
- userMcpServersConfigPatch,
3019
- dynamicToolsFingerprint,
3020
- dynamicToolsContainDeferred,
3021
- webSearchThreadConfigFingerprint,
3022
- userMcpServersFingerprint,
3023
- ringZeroConfigFingerprint,
3024
- ringZeroClientInstanceId,
3025
- networkProxyConfigFingerprint,
3026
- contextEngineBinding,
3027
- environmentSelectionFingerprint,
3028
- hostSystemAgentActive,
3029
- ringZeroActive,
3030
- ringZeroInheritedMcpServerNames,
3031
- lifecycleTiming,
3032
- normalizeBindingModelProvider,
3033
- throwIfAborted,
3034
- clearCurrentBinding
1990
+ }
1991
+ const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
1992
+ const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
1993
+ configPatch: params.finalConfigPatch,
1994
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
1995
+ };
1996
+ const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
1997
+ const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
1998
+ cwd: params.cwd,
1999
+ dynamicTools: params.dynamicTools,
2000
+ appServer: params.appServer,
2001
+ developerInstructions: params.developerInstructions,
2002
+ config,
2003
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
2004
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2005
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2006
+ webSearchAllowed: params.webSearchAllowed,
2007
+ environmentSelection: params.environmentSelection,
2008
+ model: startModelSelection.model,
2009
+ modelProvider: startModelProvider
2010
+ }));
2011
+ const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
2012
+ const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
2013
+ try {
2014
+ return await params.client.request("thread/start", startParams, { signal: params.signal });
2015
+ } catch (error) {
2016
+ if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
2017
+ throw error;
2018
+ }
2019
+ });
2020
+ const response = assertCodexThreadStartResponse(threadStartResponse);
2021
+ throwIfAborted();
2022
+ const modelProvider = resolveCodexAppServerModelProvider({
2023
+ provider: params.params.provider,
2024
+ authProfileId: params.params.authProfileId,
2025
+ authProfileStore: params.params.authProfileStore,
2026
+ agentDir: params.params.agentDir,
2027
+ config: params.params.config
2028
+ });
2029
+ const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2030
+ if (!preserveExistingBinding) {
2031
+ if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2032
+ kind: "set",
2033
+ if: { kind: "absent" },
2034
+ binding: {
2035
+ threadId: response.thread.id,
2036
+ cwd: params.cwd,
2037
+ authProfileId: params.params.authProfileId,
2038
+ model: response.model ?? startParams.model ?? params.params.modelId,
2039
+ modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
2040
+ dynamicToolsFingerprint,
2041
+ dynamicToolsContainDeferred,
2042
+ webSearchThreadConfigFingerprint,
2043
+ userMcpServersFingerprint,
2044
+ mcpServersFingerprint: nextMcpServersFingerprint,
2045
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
2046
+ networkProxyConfigFingerprint,
2047
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2048
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2049
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2050
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2051
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2052
+ contextEngine: contextEngineBinding,
2053
+ environmentSelectionFingerprint
2054
+ }
2055
+ }))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
2056
+ if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2057
+ sessionId: params.params.sessionId,
2058
+ sessionKey: params.params.sessionKey,
2059
+ threadId: response.thread.id,
2060
+ engineId: contextEngineBinding.engineId,
2061
+ epoch: contextEngineBinding.projection?.epoch,
2062
+ fingerprint: contextEngineBinding.projection?.fingerprint,
2063
+ action: rotatedContextEngineBinding ? "rotated" : "started"
3035
2064
  });
3036
- if (resumed) return resumed;
3037
2065
  }
3038
- return await startFreshCodexThread(params, {
3039
- bindingIdentity,
3040
- startModelSelection,
3041
- startModelProvider,
3042
- userMcpServersConfigPatch,
2066
+ lifecycleTiming.mark("thread-ready");
2067
+ lifecycleTiming.logSummary({
2068
+ runId: params.params.runId,
2069
+ sessionId: params.params.sessionId,
2070
+ sessionKey: params.params.sessionKey,
2071
+ threadId: response.thread.id,
2072
+ action: rotatedContextEngineBinding ? "rotated" : "started"
2073
+ });
2074
+ return {
2075
+ threadId: response.thread.id,
2076
+ cwd: params.cwd,
2077
+ authProfileId: params.params.authProfileId,
2078
+ model: response.model ?? startParams.model ?? params.params.modelId,
2079
+ modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
3043
2080
  dynamicToolsFingerprint,
3044
2081
  dynamicToolsContainDeferred,
3045
- webSearchThreadConfigFingerprint,
3046
2082
  userMcpServersFingerprint,
3047
- ringZeroConfigFingerprint,
3048
- ringZeroClientInstanceId,
2083
+ mcpServersFingerprint: nextMcpServersFingerprint,
2084
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
3049
2085
  networkProxyConfigFingerprint,
3050
- contextEngineBinding,
2086
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2087
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2088
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2089
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2090
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2091
+ contextEngine: contextEngineBinding,
3051
2092
  environmentSelectionFingerprint,
3052
- hostSystemAgentActive,
3053
- ringZeroActive,
3054
- ringZeroInheritedMcpServerNames,
3055
- lifecycleTiming,
3056
- normalizeBindingModelProvider,
3057
- throwIfAborted,
3058
- prebuiltPluginThreadConfig,
3059
- preserveExistingBinding,
3060
- rotatedContextEngineBinding
3061
- });
2093
+ lifecycle: {
2094
+ action: "started",
2095
+ ...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
2096
+ }
2097
+ };
3062
2098
  });
3063
2099
  }
3064
- //#endregion
3065
- //#region extensions/codex/src/app-server/image-payload-sanitizer.ts
3066
- /**
3067
- * Sanitizes inline image payloads mirrored through Codex history so invalid
3068
- * base64 data becomes readable text instead of poisoning replayed transcripts.
3069
- */
3070
- const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
3071
- /** Validates and normalizes an inline image data URL for Codex history payloads. */
3072
- function sanitizeInlineImageDataUrl$1(imageUrl) {
3073
- return sanitizeInlineImageDataUrl(imageUrl);
3074
- }
3075
- /** Builds the replacement text inserted when an inline image payload is invalid. */
3076
- function invalidInlineImageText(label) {
3077
- return `[${label}] ${IMAGE_OMITTED_TEXT}`;
2100
+ function shouldRotateCodexAppServerBindingForRuntime(params) {
2101
+ if (!params.current) return false;
2102
+ if (params.binding === params.current) return false;
2103
+ return params.connectionClass === "remote" || Boolean(params.binding);
3078
2104
  }
3079
- function sanitizeImageContentRecord(record, label) {
3080
- if (record.type === "image" && typeof record.data === "string") {
3081
- const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
3082
- const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
3083
- if (!imageUrl) return {
3084
- type: "text",
3085
- text: invalidInlineImageText(label)
3086
- };
3087
- const commaIndex = imageUrl.indexOf(",");
3088
- const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
3089
- return {
3090
- ...record,
3091
- mimeType: mime,
3092
- data: imageUrl.slice(commaIndex + 1)
3093
- };
3094
- }
3095
- if (record.type === "inputImage" && typeof record.imageUrl === "string") {
3096
- const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
3097
- return imageUrl ? {
3098
- ...record,
3099
- imageUrl
3100
- } : {
3101
- type: "inputText",
3102
- text: invalidInlineImageText(label)
3103
- };
3104
- }
3105
- if (record.type === "input_image" && typeof record.image_url === "string") {
3106
- const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
3107
- return imageUrl ? {
3108
- ...record,
3109
- image_url: imageUrl
3110
- } : {
3111
- type: "input_text",
3112
- text: invalidInlineImageText(label)
3113
- };
2105
+ function resolveCodexGpt56MultiAgentVersion(modelRef) {
2106
+ let modelId = modelRef?.trim().toLowerCase();
2107
+ if (!modelId) return;
2108
+ const slashIndex = modelId.indexOf("/");
2109
+ if (slashIndex > 0) {
2110
+ const provider = modelId.slice(0, slashIndex);
2111
+ if (provider !== "openai" && provider !== "codex") return;
2112
+ modelId = modelId.slice(slashIndex + 1);
3114
2113
  }
2114
+ if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
2115
+ return modelId === "gpt-5.6-luna" ? "v1" : void 0;
3115
2116
  }
3116
- /** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
3117
- function sanitizeCodexHistoryImagePayloads(value, label) {
3118
- if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
3119
- if (!isRecord(value)) return value;
3120
- const imageRecord = sanitizeImageContentRecord(value, label);
3121
- if (imageRecord) return imageRecord;
3122
- const next = {};
3123
- for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
3124
- return next;
2117
+ function shouldRotateCodexGpt56MultiAgentBinding(params) {
2118
+ const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
2119
+ const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
2120
+ return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
3125
2121
  }
3126
- //#endregion
3127
- //#region extensions/codex/src/app-server/user-input.ts
3128
- /** Builds ordered Codex user input for both new turns and same-turn steering. */
3129
- function buildCodexUserInput(text, images) {
3130
- const imageInputs = (images ?? []).map((image) => {
3131
- const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
3132
- return imageUrl ? {
3133
- type: "image",
3134
- url: imageUrl
3135
- } : {
3136
- type: "text",
3137
- text: invalidInlineImageText("codex user input"),
3138
- text_elements: []
3139
- };
2122
+ function isTransientWebSearchRestriction(params) {
2123
+ if (params.nativeProviderWebSearchSupport === "unknown") return true;
2124
+ if (params.params.config?.tools?.web?.search?.enabled === false) return false;
2125
+ if (params.params.disableTools === true) return true;
2126
+ const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
2127
+ if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
2128
+ if (params.webSearchAllowed !== false) return false;
2129
+ if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
2130
+ if (params.params.toolsAllow === void 0) return false;
2131
+ return !params.params.toolsAllow.some((name) => {
2132
+ const normalized = normalizeCodexDynamicToolName(name);
2133
+ return normalized === "*" || normalized === "web_search";
3140
2134
  });
3141
- return [...text === void 0 ? [] : [{
3142
- type: "text",
3143
- text,
3144
- text_elements: []
3145
- }], ...imageInputs];
3146
2135
  }
3147
- //#endregion
3148
- //#region extensions/codex/src/app-server/turn-params.ts
2136
+ function buildContextEngineBinding(params, projection) {
2137
+ const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
2138
+ const engineId = contextEngine?.info?.id?.trim();
2139
+ if (!contextEngine || !engineId) return;
2140
+ return {
2141
+ schemaVersion: 1,
2142
+ engineId,
2143
+ policyFingerprint: JSON.stringify({
2144
+ schemaVersion: 1,
2145
+ engineId,
2146
+ engineVersion: contextEngine.info.version,
2147
+ ownsCompaction: contextEngine.info.ownsCompaction === true,
2148
+ turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
2149
+ citationsMode: resolveContextEngineCitationsMode(params.config),
2150
+ contextTokenBudget: params.contextTokenBudget,
2151
+ projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
2152
+ contextTokenBudget: params.contextTokenBudget,
2153
+ reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
2154
+ })
2155
+ }),
2156
+ projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
2157
+ };
2158
+ }
2159
+ function buildContextEngineProjectionBinding(projection) {
2160
+ return {
2161
+ schemaVersion: 1,
2162
+ mode: "thread_bootstrap",
2163
+ epoch: projection.epoch,
2164
+ fingerprint: projection.fingerprint
2165
+ };
2166
+ }
2167
+ function isContextEngineBindingCompatible(previous, next) {
2168
+ return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
2169
+ }
2170
+ function areContextEngineProjectionBindingsCompatible(previous, next) {
2171
+ if (!next) return previous === void 0;
2172
+ return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
2173
+ }
2174
+ function resolveContextEngineCitationsMode(config) {
2175
+ const rootConfig = isUnknownRecord(config) ? config : void 0;
2176
+ const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
2177
+ return isJsonConfigValue(citations) ? citations : void 0;
2178
+ }
2179
+ function isUnknownRecord(value) {
2180
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
2181
+ }
2182
+ function isJsonConfigValue(value) {
2183
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
2184
+ if (typeof value === "number") return Number.isFinite(value);
2185
+ if (Array.isArray(value)) return value.every(isJsonConfigValue);
2186
+ return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
2187
+ }
2188
+ function shouldRecheckRecoverablePluginBinding(params) {
2189
+ if (!params.pluginThreadConfig?.enabled) return false;
2190
+ if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
2191
+ const policyContext = params.binding.pluginAppPolicyContext;
2192
+ if (!policyContext) return false;
2193
+ const expectedPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
2194
+ return Object.keys(policyContext.apps).length === 0 || expectedPluginConfigKeys.length > 0;
2195
+ }
2196
+ function buildThreadStartParams(params, options) {
2197
+ const resolvedModelProvider = resolveCodexAppServerModelProvider({
2198
+ provider: params.provider,
2199
+ authProfileId: params.authProfileId,
2200
+ authProfileStore: params.authProfileStore,
2201
+ agentDir: params.agentDir,
2202
+ config: params.config
2203
+ });
2204
+ const modelSelection = resolveCodexAppServerRequestModelSelection({
2205
+ model: options.model ?? params.modelId,
2206
+ modelProvider: options.modelProvider ?? resolvedModelProvider,
2207
+ authProfileId: params.authProfileId,
2208
+ authProfileStore: params.authProfileStore,
2209
+ agentDir: params.agentDir,
2210
+ config: params.config
2211
+ });
2212
+ return {
2213
+ model: modelSelection.model,
2214
+ ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
2215
+ cwd: options.cwd,
2216
+ approvalPolicy: options.appServer.approvalPolicy,
2217
+ approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2218
+ ...codexThreadSandboxOrPermissions(options.appServer),
2219
+ ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2220
+ personality: CODEX_NATIVE_PERSONALITY_NONE,
2221
+ serviceName: "OpenClaw",
2222
+ config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2223
+ nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2224
+ nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2225
+ nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2226
+ webSearchAllowed: options.webSearchAllowed,
2227
+ appServer: options.appServer
2228
+ }),
2229
+ ...resolveCodexThreadEnvironmentSelection(options),
2230
+ developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
2231
+ dynamicTools: [...options.dynamicTools],
2232
+ experimentalRawEvents: true
2233
+ };
2234
+ }
2235
+ function buildThreadResumeParams(params, options) {
2236
+ const resolvedModelProvider = resolveCodexAppServerModelProvider({
2237
+ provider: params.provider,
2238
+ authProfileId: options.authProfileId ?? params.authProfileId,
2239
+ authProfileStore: params.authProfileStore,
2240
+ agentDir: params.agentDir,
2241
+ config: params.config
2242
+ });
2243
+ const modelSelection = resolveCodexAppServerRequestModelSelection({
2244
+ model: options.model ?? params.modelId,
2245
+ modelProvider: options.modelProvider ?? resolvedModelProvider,
2246
+ authProfileId: options.authProfileId ?? params.authProfileId,
2247
+ authProfileStore: params.authProfileStore,
2248
+ agentDir: params.agentDir,
2249
+ config: params.config
2250
+ });
2251
+ return {
2252
+ threadId: options.threadId,
2253
+ model: modelSelection.model,
2254
+ ...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
2255
+ approvalPolicy: options.appServer.approvalPolicy,
2256
+ approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
2257
+ ...codexThreadSandboxOrPermissions(options.appServer),
2258
+ ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
2259
+ personality: CODEX_NATIVE_PERSONALITY_NONE,
2260
+ config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
2261
+ nativeCodeModeEnabled: options.nativeCodeModeEnabled,
2262
+ nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2263
+ nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
2264
+ webSearchAllowed: options.webSearchAllowed,
2265
+ appServer: options.appServer
2266
+ }),
2267
+ developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
2268
+ };
2269
+ }
2270
+ function resolveCodexBindingModelProviderFallback(params) {
2271
+ const provider = params.provider?.trim().toLowerCase();
2272
+ if (provider && provider !== "codex") return;
2273
+ const currentModel = params.currentModel?.trim();
2274
+ const bindingModel = params.bindingModel?.trim();
2275
+ if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
2276
+ return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
2277
+ }
2278
+ function resolveCodexAppServerThreadModelSelection(params) {
2279
+ const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
2280
+ const explicitModelProvider = resolveCodexAppServerModelProvider({
2281
+ provider: params.provider,
2282
+ authProfileId,
2283
+ authProfileStore: params.authProfileStore,
2284
+ agentDir: params.agentDir,
2285
+ config: params.config
2286
+ });
2287
+ const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
2288
+ provider: params.provider,
2289
+ currentModel: params.model,
2290
+ bindingModel: params.binding.model,
2291
+ bindingModelProvider: params.binding.modelProvider
2292
+ }) : void 0;
2293
+ return resolveCodexAppServerRequestModelSelection({
2294
+ model: params.model,
2295
+ modelProvider: explicitModelProvider ?? bindingModelProvider,
2296
+ authProfileId,
2297
+ authProfileStore: params.authProfileStore,
2298
+ agentDir: params.agentDir,
2299
+ config: params.config
2300
+ });
2301
+ }
2302
+ function resolveCodexAppServerRequestModelSelection(params) {
2303
+ const model = params.model.trim();
2304
+ const modelProvider = params.modelProvider?.trim();
2305
+ if (modelProvider) return {
2306
+ model,
2307
+ modelProvider
2308
+ };
2309
+ const slashIndex = model.indexOf("/");
2310
+ if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
2311
+ const inferredModelProvider = resolveCodexAppServerModelProvider({
2312
+ provider: model.slice(0, slashIndex),
2313
+ authProfileId: params.authProfileId,
2314
+ authProfileStore: params.authProfileStore,
2315
+ agentDir: params.agentDir,
2316
+ config: params.config
2317
+ });
2318
+ return {
2319
+ model: model.slice(slashIndex + 1).trim(),
2320
+ ...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
2321
+ };
2322
+ }
2323
+ function hasProviderQualifiedModelRef(model) {
2324
+ const trimmed = model?.trim();
2325
+ const slashIndex = trimmed?.indexOf("/") ?? -1;
2326
+ return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
2327
+ }
2328
+ function buildCodexRuntimeThreadConfig(config, options = {}) {
2329
+ const codeModeConfig = {
2330
+ ...CODEX_CODE_MODE_THREAD_CONFIG,
2331
+ "features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
2332
+ };
2333
+ if (options.nativeCodeModeEnabled === false) {
2334
+ const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
2335
+ delete disabledConfig["features.apply_patch_streaming_events"];
2336
+ return disabledConfig;
2337
+ }
2338
+ if (options.nativeCodeModeOnlyEnabled === true) return mergeCodexThreadConfigs(codeModeConfig, config, { "features.code_mode_only": true }) ?? {
2339
+ ...codeModeConfig,
2340
+ "features.code_mode_only": true
2341
+ };
2342
+ return mergeCodexThreadConfigs(codeModeConfig, config) ?? { ...codeModeConfig };
2343
+ }
2344
+ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2345
+ const webSearchConfig = resolveCodexWebSearchPlan({
2346
+ config: params.config,
2347
+ disableTools: params.disableTools,
2348
+ nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
2349
+ nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2350
+ webSearchAllowed: options.webSearchAllowed
2351
+ }).threadConfig;
2352
+ const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
2353
+ const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0) ?? baseConfig;
2354
+ if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
2355
+ return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
2356
+ ...runtimeConfig,
2357
+ ...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
2358
+ };
2359
+ }
3149
2360
  function buildTurnStartParams(params, options) {
3150
- const modelSelection = options.preserveNativeTurnSettings ? void 0 : resolveCodexAppServerRequestModelSelection({
2361
+ const modelSelection = resolveCodexAppServerRequestModelSelection({
3151
2362
  model: options.model ?? params.modelId,
3152
2363
  modelProvider: options.modelProvider,
3153
2364
  authProfileId: params.authProfileId,
@@ -3158,26 +2369,37 @@ function buildTurnStartParams(params, options) {
3158
2369
  const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
3159
2370
  return {
3160
2371
  threadId: options.threadId,
3161
- input: buildCodexUserInput(options.promptText ?? params.prompt, params.images),
2372
+ input: buildUserInput(params, options.promptText),
3162
2373
  cwd: options.cwd,
3163
2374
  approvalPolicy: options.appServer.approvalPolicy,
3164
2375
  approvalsReviewer: options.appServer.approvalsReviewer,
3165
- ...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.cwd, options.appServer.start?.args) },
3166
- ...modelSelection ? {
3167
- model: modelSelection.model,
3168
- personality: CODEX_NATIVE_PERSONALITY_NONE
3169
- } : {},
2376
+ ...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.cwd) },
2377
+ model: modelSelection.model,
2378
+ personality: CODEX_NATIVE_PERSONALITY_NONE,
3170
2379
  ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
3171
- ...modelSelection ? { effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)) } : {},
2380
+ effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)),
3172
2381
  ...options.environmentSelection ? { environments: options.environmentSelection } : {},
3173
- ...modelSelection ? { collaborationMode: buildTurnCollaborationMode(params, {
2382
+ collaborationMode: buildTurnCollaborationMode(params, {
3174
2383
  model: modelSelection.model,
3175
2384
  turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
3176
2385
  skillsCollaborationInstructions: options.skillsCollaborationInstructions,
3177
- memoryCollaborationInstructions: options.memoryCollaborationInstructions
3178
- }) } : {}
2386
+ memoryCollaborationInstructions: options.memoryCollaborationInstructions,
2387
+ heartbeatCollaborationInstructions: options.heartbeatCollaborationInstructions
2388
+ })
3179
2389
  };
3180
2390
  }
2391
+ function resolveCodexThreadApprovalsReviewer(appServer, config) {
2392
+ return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
2393
+ }
2394
+ function codexThreadSandboxOrPermissions(appServer) {
2395
+ if (appServer.networkProxy) return {};
2396
+ return { sandbox: appServer.sandbox };
2397
+ }
2398
+ function resolveCodexThreadEnvironmentSelection(options) {
2399
+ if (options.nativeCodeModeEnabled === false) return { environments: [] };
2400
+ if (options.environmentSelection) return { environments: options.environmentSelection };
2401
+ return {};
2402
+ }
3181
2403
  function buildTurnCollaborationMode(params, options = {}) {
3182
2404
  const model = options.model ?? params.modelId;
3183
2405
  return {
@@ -3192,7 +2414,7 @@ function buildTurnCollaborationMode(params, options = {}) {
3192
2414
  function buildTurnScopedCollaborationInstructions(params, options = {}) {
3193
2415
  const contextInstructions = joinPresentSections(options.turnScopedDeveloperInstructions, options.memoryCollaborationInstructions, options.skillsCollaborationInstructions);
3194
2416
  if (params.trigger === "cron") return joinPresentSections(buildCronCollaborationInstructions(), contextInstructions);
3195
- if (params.trigger === "heartbeat" && params.bootstrapContextRunKind !== "commitment-only") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions);
2417
+ if (params.trigger === "heartbeat" && params.bootstrapContextRunKind !== "commitment-only") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions, options.heartbeatCollaborationInstructions);
3196
2418
  if (contextInstructions?.trim()) return joinPresentSections(buildDefaultCollaborationInstructions(), contextInstructions);
3197
2419
  return null;
3198
2420
  }
@@ -3223,11 +2445,149 @@ function buildHeartbeatCollaborationInstructions() {
3223
2445
  return [
3224
2446
  "This is an OpenClaw heartbeat turn. Apply these instructions only to this heartbeat wake; ordinary chat turns should stay in Codex Default mode.",
3225
2447
  "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.",
3226
- GPT5_HEARTBEAT_PROMPT_OVERLAY
2448
+ CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY
3227
2449
  ].join("\n\n");
3228
2450
  }
3229
2451
  function joinPresentSections(...sections) {
3230
2452
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
3231
2453
  }
2454
+ function codexDynamicToolsFingerprint(dynamicTools) {
2455
+ return fingerprintDynamicTools(dynamicTools);
2456
+ }
2457
+ function codexLegacyDynamicToolsFingerprint(dynamicTools) {
2458
+ return legacyFingerprintDynamicTools(dynamicTools);
2459
+ }
2460
+ function areCodexDynamicToolFingerprintsCompatible(params) {
2461
+ return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
2462
+ }
2463
+ function fingerprintDynamicTools(dynamicTools) {
2464
+ return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
2465
+ }
2466
+ function legacyFingerprintDynamicTools(dynamicTools) {
2467
+ return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
2468
+ }
2469
+ function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
2470
+ return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
2471
+ }
2472
+ function fingerprintUserMcpServersConfigPatch(configPatch) {
2473
+ return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(configPatch))) : void 0;
2474
+ }
2475
+ function fingerprintJsonObject(value) {
2476
+ return JSON.stringify(stabilizeJsonValue(value));
2477
+ }
2478
+ function fingerprintEnvironmentSelection(environments) {
2479
+ return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
2480
+ }
2481
+ function fingerprintDynamicToolSpec(tool) {
2482
+ return stabilizeDynamicToolFingerprintValue(tool);
2483
+ }
2484
+ function stabilizeDynamicToolFingerprintValue(value) {
2485
+ if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
2486
+ if (!isJsonObject(value)) return value;
2487
+ const stable = {};
2488
+ for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
2489
+ if (key === "description") continue;
2490
+ stable[key] = stabilizeDynamicToolFingerprintValue(child);
2491
+ }
2492
+ return stable;
2493
+ }
2494
+ function stabilizeJsonValue(value) {
2495
+ if (Array.isArray(value)) return value.map(stabilizeJsonValue);
2496
+ if (!isJsonObject(value)) return value;
2497
+ const stable = {};
2498
+ for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
2499
+ return stable;
2500
+ }
2501
+ function readActiveCodexTurnIds(thread) {
2502
+ return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
2503
+ }
2504
+ const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
2505
+ const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
2506
+ function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
2507
+ return !previous || previous === next || previous === nextLegacy;
2508
+ }
2509
+ function areUserMcpServersFingerprintsCompatible(params) {
2510
+ return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
2511
+ }
2512
+ function shouldStartTransientNoToolThread(params) {
2513
+ return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
2514
+ }
2515
+ function isEmptyDynamicToolsFingerprint(fingerprint) {
2516
+ return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
2517
+ }
2518
+ function compareJsonFingerprint(left, right) {
2519
+ return JSON.stringify(left).localeCompare(JSON.stringify(right));
2520
+ }
2521
+ function buildDeveloperInstructions(params, options = {}) {
2522
+ const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
2523
+ surface: "codex_app_server",
2524
+ includeLegacyGlobalGuidance: false
2525
+ }).join("\n");
2526
+ return [
2527
+ "You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
2528
+ buildDeferredDynamicToolManifest(options.dynamicTools),
2529
+ buildSkillWorkshopInstruction(options.dynamicTools),
2530
+ "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`.",
2531
+ buildVisibleReplyInstruction(params, options.dynamicTools),
2532
+ nativeCommandGuidance,
2533
+ params.extraSystemPrompt
2534
+ ].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
2535
+ }
2536
+ function buildDeferredDynamicToolManifest(dynamicTools) {
2537
+ const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
2538
+ if (deferredToolNames.length === 0) return;
2539
+ return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
2540
+ }
2541
+ function buildSkillWorkshopInstruction(dynamicTools) {
2542
+ if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
2543
+ return buildSkillWorkshopPromptSection().join("\n");
2544
+ }
2545
+ function buildVisibleReplyInstruction(params, dynamicTools) {
2546
+ const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
2547
+ 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. Do not repeat that visible content in your final answer.";
2548
+ 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.";
2549
+ return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
2550
+ }
2551
+ function buildUserInput(params, promptText = params.prompt) {
2552
+ const imageInputs = (params.images ?? []).map((image) => {
2553
+ const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
2554
+ return imageUrl ? {
2555
+ type: "image",
2556
+ url: imageUrl
2557
+ } : {
2558
+ type: "text",
2559
+ text: invalidInlineImageText("codex user input"),
2560
+ text_elements: []
2561
+ };
2562
+ });
2563
+ return [{
2564
+ type: "text",
2565
+ text: promptText,
2566
+ text_elements: []
2567
+ }, ...imageInputs];
2568
+ }
2569
+ function resolveCodexAppServerModelProvider(params) {
2570
+ const normalized = params.provider.trim();
2571
+ const normalizedLower = normalized.toLowerCase();
2572
+ if (!normalized || normalizedLower === "codex") return;
2573
+ if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
2574
+ return normalizedLower === "openai" ? "openai" : normalized;
2575
+ }
2576
+ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
2577
+ if (thinkLevel === "off" || thinkLevel === "adaptive") return null;
2578
+ if (supportedReasoningEfforts) return resolveCodexSupportedReasoningEffort({
2579
+ requested: thinkLevel,
2580
+ supportedReasoningEfforts
2581
+ }) ?? null;
2582
+ const fallbackReasoningEfforts = resolveCodexFallbackReasoningEfforts(modelId);
2583
+ if (fallbackReasoningEfforts) return resolveCodexSupportedReasoningEffort({
2584
+ requested: thinkLevel,
2585
+ supportedReasoningEfforts: fallbackReasoningEfforts
2586
+ }) ?? null;
2587
+ if (thinkLevel === "minimal") return isModernCodexModel(modelId) ? "low" : "minimal";
2588
+ if (thinkLevel === "low" || thinkLevel === "medium" || thinkLevel === "high" || thinkLevel === "xhigh") return thinkLevel;
2589
+ if (thinkLevel === "max" && isMaxReasoningCodexModel(modelId)) return "max";
2590
+ return null;
2591
+ }
3232
2592
  //#endregion
3233
- export { readHookOutputEntries as $, resolveCodexDynamicToolsLoading as A, resolveCodexContextEngineProjectionReserveTokens as B, readCodexSupportedReasoningEfforts as C, isForcedPrivateQaCodexRuntime as D, filterCodexDynamicToolsWithOpenClawShell as E, buildContextEngineBinding as F, mergeCodexThreadConfigs as G, buildCodexPluginThreadConfig as H, isContextEngineBindingCompatible as I, pluginReadParams as J, shouldBuildCodexPluginThreadConfig as K, fitCodexProjectedContextForTurnStart as L, areCodexDynamicToolFingerprintsCompatible as M, codexDynamicToolsFingerprint as N, isSystemAgentOnlyCodexDynamicToolAllowlist as O, codexLegacyDynamicToolsFingerprint as P, readCodexErrorNotificationMessage as Q, projectContextEngineAssemblyForCodex as R, resolveReasoningEffort as S, filterCodexDynamicTools as T, buildCodexPluginThreadConfigInputFingerprint as U, buildCodexPluginAppsConfigPatchFromPolicyContext as V, buildCodexPluginThreadConfigTimeoutFallback as W, extractRawAssistantText as X, resolveRecoverableCodexPluginConfigKeys as Y, normalizeNonEmptyString as Z, CODEX_NATIVE_PERSONALITY_NONE as _, sanitizeCodexHistoryImagePayloads as a, readNullableString as at, resolveCodexAppServerThreadModelSelection as b, resumeCodexAppServerThread as c, splitPlanText as ct, buildCodexRingZeroThreadConfigPatch as d, readItem as et, buildCodexRuntimeThreadConfig as f, buildDeveloperInstructions as g, resolveCodexWebSearchPlan as h, invalidInlineImageText as i, readNonNegativeInteger as it, resolveCodexDynamicToolsLoadingForRuntime as j, normalizeCodexDynamicToolName as k, assertCodexRingZeroHasNoManagedHooks as l, buildCodexNativeWebSearchThreadConfig as m, buildTurnStartParams as n, readNonEmptyString as nt, sanitizeInlineImageDataUrl$1 as o, readNumber as ot, readCodexInheritedMcpServerNames as p, ensureCodexPluginActivation as q, buildCodexUserInput as r, readNonEmptyStringArray as rt, startOrResumeThread as s, readString as st, buildTurnCollaborationMode as t, readItemString as tt, attestCodexRingZeroThreadHasNoMcpServers as u, resolveCodexAppServerModelProvider as v, isCodexAppServerProfilerEnabled as w, resolveCodexBindingModelProviderFallback as x, resolveCodexAppServerRequestModelSelection as y, resolveCodexContextEngineProjectionMaxChars as z };
2593
+ export { resolveCodexContextEngineProjectionMaxChars as A, buildCodexPluginThreadConfig as B, filterCodexDynamicTools as C, resolveCodexDynamicToolsLoadingForRuntime as D, resolveCodexDynamicToolsLoading as E, closeCodexStartupClientBestEffort as F, pluginReadParams as G, mergeCodexThreadConfigs as H, interruptCodexTurnBestEffort as I, buildCodexAppInventoryCacheKey as K, retireCodexAppServerClientAfterTimedOutTurn as L, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as M, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as N, fitCodexProjectedContextForTurnStart as O, CodexAppServerUnsafeSubscriptionError as P, unsubscribeCodexThreadBestEffort as R, sanitizeInlineImageDataUrl$1 as S, normalizeCodexDynamicToolName as T, shouldBuildCodexPluginThreadConfig as U, buildCodexPluginThreadConfigInputFingerprint as V, ensureCodexPluginActivation as W, buildCodexNativeWebSearchThreadConfig as _, buildDeveloperInstructions as a, invalidInlineImageText as b, codexDynamicToolsFingerprint as c, resolveCodexAppServerModelProvider as d, resolveCodexAppServerRequestModelSelection as f, startOrResumeThread as g, resolveReasoningEffort as h, buildContextEngineBinding as i, resolveCodexContextEngineProjectionReserveTokens as j, projectContextEngineAssemblyForCodex as k, codexLegacyDynamicToolsFingerprint as l, resolveCodexBindingModelProviderFallback as m, areCodexDynamicToolFingerprintsCompatible as n, buildTurnCollaborationMode as o, resolveCodexAppServerThreadModelSelection as p, defaultCodexAppInventoryCache as q, buildCodexRuntimeThreadConfig as r, buildTurnStartParams as s, CODEX_NATIVE_PERSONALITY_NONE as t, isContextEngineBindingCompatible as u, resolveCodexWebSearchPlan as v, isForcedPrivateQaCodexRuntime as w, sanitizeCodexHistoryImagePayloads as x, isCodexAppServerProfilerEnabled as y, buildCodexPluginAppsConfigPatchFromPolicyContext as z };