@openclaw/codex 2026.7.1 → 2026.7.2-beta.2

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