@openclaw/codex 2026.7.2-beta.7 → 2026.8.1-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 (55) hide show
  1. package/README.md +2 -0
  2. package/dist/{app-server-policy-BSaO2JIT.js → app-server-policy-zcBDc1JU.js} +1 -1
  3. package/dist/attempt-client-cleanup-DMLXUfa8.js +735 -0
  4. package/dist/{notification-correlation-CU6_AnOb.js → binding-connection-I5Bo-AsX.js} +3 -37
  5. package/dist/{bounded-turn-D2ZAgZVz.js → bounded-turn-qgZmzy_4.js} +50 -49
  6. package/dist/{capabilities-BFDTVYr4.js → capabilities-CybGI1oz.js} +8 -2
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-C336eRVS.js → command-formatters-B1AEz8ZA.js} +18 -18
  9. package/dist/{command-handlers-DAHUSry9.js → command-handlers-Db8pu_ds.js} +484 -95
  10. package/dist/{compact-D_uvHurS.js → compact-LspQaGfl.js} +213 -199
  11. package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
  12. package/dist/doctor-contract-api.js +40 -43
  13. package/dist/{dynamic-tools-D2gYkSBO.js → dynamic-tools-BVdyWkUD.js} +2597 -1221
  14. package/dist/{effective-mcp-catalog-Bbd98Inw.js → effective-mcp-catalog-DIIhAtOZ.js} +20 -15
  15. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  16. package/dist/{attempt-notifications-AP-a-Q5k.js → event-projector-usage-e5huIQWu.js} +113 -29
  17. package/dist/harness.js +80 -14
  18. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  19. package/dist/index.js +591 -240
  20. package/dist/isolated-completion-C58NbL40.js +75 -0
  21. package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
  22. package/dist/media-understanding-provider.js +5 -5
  23. package/dist/{models-BBMKU4jA.js → models-BFlpn1Ex.js} +12 -17
  24. package/dist/native-subagent-monitor-DyTGmTUx.js +3111 -0
  25. package/dist/{plugin-app-cache-key-DztPafOA.js → plugin-app-cache-key-C3H2CVp6.js} +5 -5
  26. package/dist/plugin-marketplace-discovery-CzrGJdCR.js +385 -0
  27. package/dist/{computer-use-CYEc8U3w.js → plugin-metadata-cache-CNJPj4_b.js} +105 -5
  28. package/dist/protocol-validators-B4F7L8Rf.js +2346 -0
  29. package/dist/{rate-limits-CU-gVCCS.js → rate-limits-DZXmOL8-.js} +17 -35
  30. package/dist/{request-qKh331yu.js → request-BRl5_UfX.js} +3 -3
  31. package/dist/{run-attempt-CY9WlDLc.js → run-attempt-P8BdxC1I.js} +4837 -9047
  32. package/dist/{runtime-artifact-CmtG_t1L.js → runtime-artifact-BVSPdd6g.js} +8 -23
  33. package/dist/{sandbox-guard-NuhpXmPB.js → sandbox-guard-Cmwc9eIk.js} +6 -13
  34. package/dist/{session-binding-BXpo9ymA.js → session-binding-DPxIvMgE.js} +246 -265
  35. package/dist/{session-catalog-DyOgR4WH.js → session-catalog-xwNi_SDM.js} +159 -109
  36. package/dist/{session-cli-2PZaFLRh.js → session-cli-7hgXZDV7.js} +1 -1
  37. package/dist/session-retirement-DoMMJFKm.js +36 -0
  38. package/dist/{session-upstream-marker-B6U7tLw5.js → session-upstream-marker-CrIFX06J.js} +1 -1
  39. package/dist/{settled-turn-finalizer-CXY2Z9dT.js → settled-turn-finalizer-BRWBO4MO.js} +21 -13
  40. package/dist/{shared-client-DTbvH-XV.js → shared-client-kIBbGjk4.js} +587 -223
  41. package/dist/{side-question-_ulBk1AL.js → side-question-9HAL26l9.js} +101 -52
  42. package/dist/{thread-lifecycle-DF53ogiA.js → thread-lifecycle-Bu1BTDXr.js} +694 -753
  43. package/dist/thread-ownership-6kOQHlyC.js +92 -0
  44. package/dist/thread-resume-CPrJNQVM.js +30 -0
  45. package/dist/transcript-mirror-B9gGgnJS.js +570 -0
  46. package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DIImwDVS.js} +11 -0
  47. package/dist/upstream-prompt-provenance-ClRBije0.js +41 -0
  48. package/dist/{upstream-session-fork-Dy4Xvuoj.js → upstream-session-fork-CE-_LdnP.js} +5 -5
  49. package/dist/{usage-CQ6GhD5-.js → usage-CHlV0e-o.js} +3 -3
  50. package/dist/{web-search-provider.runtime-CCFhg2z5.js → web-search-provider.runtime-DRw2Vl42.js} +8 -9
  51. package/openclaw.plugin.json +18 -3
  52. package/package.json +5 -5
  53. package/dist/conversation-control-xsIeXbo4.js +0 -287
  54. package/dist/event-projector-usage-Xp4AlR2k.js +0 -86
  55. package/dist/transcript-mirror-DzsdNCf5.js +0 -6924
@@ -1,15 +1,20 @@
1
- import { D as resolveCodexAppServerClientInstanceId, O as consumeCodexAppServerLiveThread, S as isCodexAppServerConnectionClosedError, T as isCodexAppServerPrewriteRequestCancellationError, U as flattenCodexDynamicToolFunctions, W as isJsonObject, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, p as retireSharedCodexAppServerClientIfCurrent, s as isCodexAppServerStartSelectionChangedError, t as clearSharedCodexAppServerClientIfCurrent, v as CodexAppServerRpcError, y as getCodexAppServerClientInstanceId } from "./shared-client-DTbvH-XV.js";
2
- import { M as CODEX_PLUGINS_MARKETPLACE_NAME, N as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, k as resolveCodexPluginsPolicy, m as codexSandboxPolicyForTurn, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-BXpo9ymA.js";
3
- import { _ as assertCodexThreadStartResponse, a as projectBoundedCodexThreadHistory, g as assertCodexThreadResumeResponse, h as assertCodexThreadForkResponse } from "./transcript-mirror-DzsdNCf5.js";
4
- import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-DztPafOA.js";
1
+ import { D as CODEX_PLUGINS_MARKETPLACE_NAME, E as resolveCodexPluginsPolicy, O as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, m as codexSandboxPolicyForTurn, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-DPxIvMgE.js";
2
+ import { M as isCodexAppServerConnectionClosedError, R as resolveCodexAppServerClientInstanceId, V as consumeCodexAppServerLiveThread, ct as isJsonObject, d as retainSharedCodexAppServerClientByInstanceId, k as getCodexAppServerClientInstanceId, s as isCodexAppServerStartSelectionChangedError, st as flattenCodexDynamicToolFunctions, z as CodexAppServerRpcError } from "./shared-client-kIBbGjk4.js";
3
+ import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, d as unsubscribeCodexThreadBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-DMLXUfa8.js";
4
+ import { a as assertCodexThreadStartResponse, r as assertCodexThreadForkResponse } from "./protocol-validators-B4F7L8Rf.js";
5
+ import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-C3H2CVp6.js";
5
6
  import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
7
+ import { t as resumeCodexAppServerThread } from "./thread-resume-CPrJNQVM.js";
8
+ import { u as projectBoundedCodexThreadHistory } from "./transcript-mirror-B9gGgnJS.js";
9
+ import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
10
+ import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
11
  import fs from "node:fs/promises";
7
12
  import path from "node:path";
8
- import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
13
+ import { SKILL_WORKSHOP_TOOL_NAME, TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
9
14
  import * as crypto$1 from "node:crypto";
10
15
  import crypto from "node:crypto";
11
16
  import os from "node:os";
12
- import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
17
+ import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
13
18
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
14
19
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
15
20
  import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
@@ -17,207 +22,6 @@ import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plu
17
22
  import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
18
23
  import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
19
24
  import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
20
- import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
21
- //#region extensions/codex/src/app-server/attempt-client-cleanup.ts
22
- /**
23
- * Best-effort cleanup helpers for Codex app-server startup attempts and turns.
24
- */
25
- /** Timeout for best-effort app-server turn interruption during cleanup. */
26
- const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
27
- /** Timeout for best-effort thread unsubscribe during cleanup. */
28
- const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
29
- /** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
30
- var CodexAppServerUnsafeSubscriptionError = class extends Error {
31
- constructor(message, options) {
32
- super(message, options);
33
- this.name = "CodexAppServerUnsafeSubscriptionError";
34
- }
35
- };
36
- function isCodexAppServerUnsafeSubscriptionError(error) {
37
- return error instanceof CodexAppServerUnsafeSubscriptionError;
38
- }
39
- /** Asserts Codex resumed the exact thread this attempt subscribed to. */
40
- function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
41
- if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
42
- }
43
- async function closeClientAndWaitIfAvailable(client) {
44
- const closeable = client;
45
- if (typeof closeable.closeAndWait === "function") {
46
- await closeable.closeAndWait();
47
- return;
48
- }
49
- closeable.close?.();
50
- }
51
- async function closeCodexStartupClientBestEffort(client) {
52
- if (!client) return;
53
- const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
54
- if (unclaimedSharedClient.closed) {
55
- await closeClientAndWaitIfAvailable(client);
56
- return;
57
- }
58
- if (unclaimedSharedClient.found) {
59
- if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
60
- return;
61
- }
62
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
63
- if (retiredSharedClient) {
64
- if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
65
- return;
66
- }
67
- if (clearSharedCodexAppServerClientIfCurrent(client)) {
68
- await closeClientAndWaitIfAvailable(client);
69
- return;
70
- }
71
- await closeClientAndWaitIfAvailable(client);
72
- }
73
- /** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
74
- function interruptCodexTurnBestEffort(client, params) {
75
- interruptCodexTurnAndWaitBestEffort(client, params);
76
- }
77
- /** Sends a bounded turn interrupt and waits for Codex to confirm terminal abort handling. */
78
- async function interruptCodexTurnAndWaitBestEffort(client, params) {
79
- const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
80
- const requestParams = {
81
- threadId: params.threadId,
82
- turnId: params.turnId
83
- };
84
- try {
85
- await (requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams));
86
- } catch (error) {
87
- embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
88
- }
89
- }
90
- /** Unsubscribes from a thread while swallowing cleanup-only failures. */
91
- async function unsubscribeCodexThreadBestEffort(client, params) {
92
- try {
93
- await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
94
- return true;
95
- } catch (error) {
96
- embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
97
- threadId: params.threadId,
98
- error
99
- });
100
- return false;
101
- }
102
- }
103
- /**
104
- * Retires the shared client after a timed-out turn so later runs do not reuse a
105
- * potentially wedged app-server connection.
106
- */
107
- async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
108
- const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
109
- const detachedSharedClient = Boolean(retiredSharedClient);
110
- if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
111
- interruptCodexTurnBestEffort(client, {
112
- threadId: params.threadId,
113
- turnId: params.turnId,
114
- timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
115
- });
116
- await unsubscribeCodexThreadBestEffort(client, {
117
- threadId: params.threadId,
118
- timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
119
- });
120
- }
121
- let closedClient = retiredSharedClient?.closed ?? false;
122
- if (!detachedSharedClient) {
123
- const close = client.close;
124
- if (typeof close === "function") try {
125
- close.call(client);
126
- closedClient = true;
127
- } catch (error) {
128
- embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
129
- threadId: params.threadId,
130
- turnId: params.turnId,
131
- error
132
- });
133
- }
134
- }
135
- embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
136
- threadId: params.threadId,
137
- turnId: params.turnId,
138
- reason: params.reason,
139
- detachedSharedClient,
140
- closedClient,
141
- activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
142
- });
143
- }
144
- //#endregion
145
- //#region extensions/codex/src/app-server/event-projector-values.ts
146
- function readString(record, key) {
147
- const value = record[key];
148
- return typeof value === "string" ? value : void 0;
149
- }
150
- function normalizeNonEmptyString(value) {
151
- if (typeof value !== "string") return;
152
- return value.trim() || void 0;
153
- }
154
- function readNonEmptyString(record, key) {
155
- return normalizeNonEmptyString(record[key]);
156
- }
157
- function readNonEmptyStringArray(record, key) {
158
- const value = record[key];
159
- if (!Array.isArray(value)) return [];
160
- const entries = [];
161
- for (const entry of value) {
162
- const normalized = normalizeNonEmptyString(entry);
163
- if (normalized) entries.push(normalized);
164
- }
165
- return entries;
166
- }
167
- function readNullableString(record, key) {
168
- const value = record[key];
169
- if (value === null) return null;
170
- return typeof value === "string" ? value : void 0;
171
- }
172
- function readNumber(record, key) {
173
- const value = record[key];
174
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
175
- }
176
- function readNonNegativeInteger(record, key) {
177
- const value = readNumber(record, key);
178
- return value !== void 0 && Number.isInteger(value) && value >= 0 ? value : void 0;
179
- }
180
- function readCodexErrorNotificationMessage(record) {
181
- const error = record.error;
182
- return isJsonObject(error) ? readString(error, "message") : void 0;
183
- }
184
- function readHookOutputEntries(value) {
185
- if (!Array.isArray(value)) return [];
186
- return value.flatMap((entry) => {
187
- if (!isJsonObject(entry)) return [];
188
- const text = readString(entry, "text");
189
- if (!text) return [];
190
- const kind = readString(entry, "kind");
191
- return [{
192
- ...kind ? { kind } : {},
193
- text
194
- }];
195
- });
196
- }
197
- function splitPlanText(text) {
198
- return text.split(/\r?\n/).map((line) => line.trim().replace(/^[-*]\s+/, "")).filter((line) => line.length > 0);
199
- }
200
- function extractRawAssistantText(item) {
201
- return (Array.isArray(item.content) ? item.content : []).flatMap((entry) => {
202
- if (!isJsonObject(entry)) return [];
203
- const type = readString(entry, "type");
204
- if (type !== "output_text" && type !== "text") return [];
205
- const value = readString(entry, "text");
206
- return value ? [value] : [];
207
- }).join("").trim() || void 0;
208
- }
209
- function readItemString(item, key) {
210
- const value = item[key];
211
- return typeof value === "string" ? value : void 0;
212
- }
213
- function readItem(value) {
214
- if (!isJsonObject(value)) return;
215
- const type = typeof value.type === "string" ? value.type : void 0;
216
- const id = typeof value.id === "string" ? value.id : void 0;
217
- if (!type || !id) return;
218
- return value;
219
- }
220
- //#endregion
221
25
  //#region extensions/codex/src/app-server/plugin-inventory.ts
222
26
  /**
223
27
  * Reads Codex plugin marketplace state and app inventory to decide which
@@ -241,7 +45,7 @@ async function readCodexPluginInventory(params) {
241
45
  ...params,
242
46
  policy
243
47
  });
244
- let curatedCatalog;
48
+ const pluginCatalogs = /* @__PURE__ */ new Map();
245
49
  const diagnostics = [];
246
50
  const records = [];
247
51
  if (appInventory?.state === "missing") diagnostics.push({
@@ -255,13 +59,19 @@ async function readCodexPluginInventory(params) {
255
59
  for (const pluginPolicy of policy.pluginPolicies) {
256
60
  if (!pluginPolicy.enabled && !policy.allowAllPlugins) continue;
257
61
  let listed = installedPlugins;
258
- let resolvedPlugin = pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(listed, pluginPolicy.pluginName) : findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
259
- if (!resolvedPlugin && pluginPolicy.enabled && pluginPolicy.marketplaceName === "openai-curated") {
260
- curatedCatalog ??= listCodexPluginMetadata(params);
261
- listed = await curatedCatalog;
262
- resolvedPlugin = findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
62
+ let resolvedPlugin = findConfiguredMarketplacePlugin(listed, pluginPolicy);
63
+ if (!resolvedPlugin && pluginPolicy.enabled && pluginPolicy.marketplaceName !== "workspace-directory") {
64
+ const requestParams = buildPluginCatalogRequestParams(params, pluginPolicy.marketplaceName);
65
+ const catalogKey = JSON.stringify([requestParams, pluginMetadataCatalogScope(pluginPolicy.marketplaceName)]);
66
+ let catalog = pluginCatalogs.get(catalogKey);
67
+ if (!catalog) {
68
+ catalog = listCodexPluginMetadata(params, pluginPolicy.marketplaceName);
69
+ pluginCatalogs.set(catalogKey, catalog);
70
+ }
71
+ listed = await catalog;
72
+ resolvedPlugin = findConfiguredMarketplacePlugin(listed, pluginPolicy);
263
73
  }
264
- if (!(pluginPolicy.marketplaceName === "workspace-directory" ? listed.marketplaces.some((entry) => entry.name === "workspace-directory") : listed.marketplaces.some(isOpenAiCuratedMarketplace))) {
74
+ if (!listed.marketplaces.some((marketplace) => marketplaceMatchesConfiguredName(marketplace, pluginPolicy.marketplaceName))) {
265
75
  diagnostics.push({
266
76
  code: "marketplace_missing",
267
77
  plugin: pluginPolicy,
@@ -278,6 +88,15 @@ async function readCodexPluginInventory(params) {
278
88
  continue;
279
89
  }
280
90
  const { summary } = resolvedPlugin;
91
+ const unavailableByMarketplacePolicy = summary.availability === "DISABLED_BY_ADMIN" || summary.installPolicy === "NOT_AVAILABLE";
92
+ if (unavailableByMarketplacePolicy) {
93
+ diagnostics.push({
94
+ code: "plugin_disabled",
95
+ plugin: pluginPolicy,
96
+ message: `${pluginPolicy.pluginName} is unavailable in ${pluginPolicy.marketplaceName}.`
97
+ });
98
+ if (!summary.installed) continue;
99
+ }
281
100
  const detail = await readPluginDetail(params, marketplaceRef(resolvedPlugin.marketplace, pluginPolicy.marketplaceName), pluginPolicy, summary, diagnostics);
282
101
  const ownedAppIds = detail?.apps.map((app) => app.id).filter(Boolean).toSorted() ?? [];
283
102
  const appOwnership = resolveAppOwnership({
@@ -304,7 +123,7 @@ async function readCodexPluginInventory(params) {
304
123
  policy: pluginPolicy,
305
124
  summary,
306
125
  ...detail ? { detail } : {},
307
- activationRequired: pluginPolicy.enabled && (!summary.installed || !summary.enabled),
126
+ activationRequired: pluginPolicy.enabled && (unavailableByMarketplacePolicy || !summary.installed || !summary.enabled),
308
127
  authRequired: apps.some((app) => app.needsAuth || !app.accessible),
309
128
  appOwnership,
310
129
  ownedAppIds,
@@ -318,11 +137,14 @@ async function readCodexPluginInventory(params) {
318
137
  ...appInventory ? { appInventory } : {}
319
138
  };
320
139
  }
321
- /** Finds one plugin summary in the OpenAI curated marketplace response. */
322
- function findOpenAiCuratedPluginSummary(listed, pluginName) {
323
- const resolved = findOpenAiCuratedMarketplacePlugin(listed, pluginName);
140
+ /** Finds a configured plugin only in its authorized marketplace identity. */
141
+ function findCodexMarketplacePluginSummary(listed, marketplaceName, pluginName) {
142
+ const resolved = findConfiguredMarketplacePlugin(listed, {
143
+ marketplaceName,
144
+ pluginName
145
+ });
324
146
  return resolved ? {
325
- marketplace: marketplaceRef(resolved.marketplace, CODEX_PLUGINS_MARKETPLACE_NAME),
147
+ marketplace: marketplaceRef(resolved.marketplace, marketplaceName),
326
148
  summary: resolved.summary
327
149
  } : void 0;
328
150
  }
@@ -343,15 +165,16 @@ function resolveRecoverableCodexPluginConfigKeys(params) {
343
165
  configCwd: params.configCwd
344
166
  })).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
345
167
  }
346
- async function listCodexPluginMetadata(params) {
347
- const requestParams = {};
168
+ async function listCodexPluginMetadata(params, marketplaceName) {
169
+ const requestParams = buildPluginCatalogRequestParams(params, marketplaceName);
348
170
  if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
349
171
  return (await params.metadataCache.load({
350
172
  appCacheKey: params.appCacheKey,
351
173
  queryKind: "curated-global",
352
174
  requestParams,
175
+ catalogScope: pluginMetadataCatalogScope(marketplaceName),
353
176
  request: async (method, listedParams) => await params.request(method, listedParams),
354
- cacheable: (response) => response.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
177
+ cacheable: (response) => response.marketplaces.some((marketplace) => marketplaceMatchesConfiguredName(marketplace, marketplaceName))
355
178
  })).response;
356
179
  }
357
180
  async function readInstalledCodexPluginMetadata(params) {
@@ -364,17 +187,26 @@ async function readInstalledCodexPluginMetadata(params) {
364
187
  request: async (method, installedParams) => await params.request(method, installedParams),
365
188
  cacheable: (response) => params.policy.pluginPolicies.every((pluginPolicy) => {
366
189
  if (!pluginPolicy.enabled && !params.policy.allowAllPlugins) return true;
367
- return pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(response, pluginPolicy.pluginName) !== void 0 : findOpenAiCuratedMarketplacePlugin(response, pluginPolicy.pluginName) !== void 0;
190
+ return Boolean(findConfiguredMarketplacePlugin(response, pluginPolicy));
368
191
  })
369
192
  })).response;
370
193
  }
371
194
  function isSettledMissingPluginPolicy(params) {
372
195
  const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "installed" : "curated-global";
373
- const requestParams = queryKind === "installed" && params.configCwd ? { cwds: [params.configCwd] } : {};
374
- const listed = params.metadataCache.read(params.appCacheKey, queryKind, requestParams)?.response;
196
+ const requestParams = queryKind === "installed" ? params.configCwd ? { cwds: [params.configCwd] } : {} : buildPluginCatalogRequestParams(params, params.pluginPolicy.marketplaceName);
197
+ const listed = params.metadataCache.read(params.appCacheKey, queryKind, requestParams, queryKind === "curated-global" ? pluginMetadataCatalogScope(params.pluginPolicy.marketplaceName) : void 0)?.response;
375
198
  if (!listed) return false;
376
- if (queryKind === "installed") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
377
- return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
199
+ return !findConfiguredMarketplacePlugin(listed, params.pluginPolicy);
200
+ }
201
+ function pluginMetadataCatalogScope(marketplaceName) {
202
+ return isOpenAiCuratedMarketplaceName(marketplaceName) ? void 0 : marketplaceName;
203
+ }
204
+ function buildPluginCatalogRequestParams(params, marketplaceName) {
205
+ const marketplaceKinds = marketplaceName === "created-by-me-remote" ? ["created-by-me-remote"] : marketplaceName.startsWith("workspace-shared-with-me") ? ["shared-with-me"] : void 0;
206
+ return {
207
+ ...params.configCwd ? { cwds: [params.configCwd] } : {},
208
+ ...marketplaceKinds ? { marketplaceKinds: [...marketplaceKinds] } : {}
209
+ };
378
210
  }
379
211
  function readCachedAppInventory(params) {
380
212
  if (!params.appCache || !params.appCacheKey) return;
@@ -442,18 +274,25 @@ function resolveOwnedApps(params) {
442
274
  }).toSorted((left, right) => left.id.localeCompare(right.id));
443
275
  }
444
276
  function findPluginSummary(marketplace, pluginName) {
445
- return marketplace.plugins.find((plugin) => plugin.name === pluginName || plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}` || pluginNameFromPluginId(plugin.id, marketplace.name) === pluginName);
277
+ const exact = marketplace.plugins.find((plugin) => plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}`);
278
+ if (exact) return exact;
279
+ const matches = marketplace.plugins.filter((plugin) => plugin.name === pluginName || pluginNameFromPluginId(plugin.id, marketplace.name) === pluginName);
280
+ return matches.length === 1 ? matches[0] : void 0;
446
281
  }
447
- function findOpenAiCuratedMarketplacePlugin(listed, pluginName) {
282
+ function findConfiguredMarketplacePlugin(listed, plugin) {
283
+ if (plugin.marketplaceName === "workspace-directory") return findWorkspaceMarketplacePlugin(listed, plugin.pluginName);
448
284
  for (const marketplace of listed.marketplaces) {
449
- if (!isOpenAiCuratedMarketplace(marketplace)) continue;
450
- const summary = findPluginSummary(marketplace, pluginName);
285
+ if (!marketplaceMatchesConfiguredName(marketplace, plugin.marketplaceName)) continue;
286
+ const summary = findPluginSummary(marketplace, plugin.pluginName);
451
287
  if (summary) return {
452
288
  marketplace,
453
289
  summary
454
290
  };
455
291
  }
456
292
  }
293
+ function marketplaceMatchesConfiguredName(marketplace, configuredMarketplaceName) {
294
+ return isOpenAiCuratedMarketplaceName(configuredMarketplaceName) ? isOpenAiCuratedMarketplace(marketplace) : marketplace.name === configuredMarketplaceName;
295
+ }
457
296
  function findWorkspaceMarketplacePlugin(listed, pluginName) {
458
297
  const marketplace = listed.marketplaces.find((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME);
459
298
  const summary = marketplace?.plugins.find((plugin) => plugin.id === pluginName);
@@ -477,20 +316,33 @@ function marketplaceRef(marketplace, name) {
477
316
  }
478
317
  /** True for any supported OpenAI curated marketplace wire name, matching Codex's own curated predicate. */
479
318
  function isOpenAiCuratedMarketplace(marketplace) {
480
- return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME || marketplace.name === CODEX_PLUGINS_API_MARKETPLACE_NAME;
319
+ return isOpenAiCuratedMarketplaceName(marketplace.name);
320
+ }
321
+ /** True for all Codex wire aliases of the same OpenAI-curated catalog. */
322
+ function isOpenAiCuratedMarketplaceName(marketplaceName) {
323
+ return marketplaceName === "openai-curated" || marketplaceName === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME || marketplaceName === CODEX_PLUGINS_API_MARKETPLACE_NAME;
481
324
  }
482
325
  //#endregion
483
326
  //#region extensions/codex/src/app-server/plugin-activation.ts
484
- /** Activates a curated plugin or rejects a workspace plugin that is not already active. */
327
+ /**
328
+ * Activates legacy curated Codex plugins while requiring owner-managed
329
+ * installation for every other marketplace.
330
+ */
331
+ /** Activates legacy curated plugins without granting install authority to other marketplaces. */
485
332
  async function ensureCodexPluginActivation(params) {
486
333
  if (params.identity.marketplaceName === "workspace-directory") return activationFailure(params.identity, "disabled", { message: "workspace-directory plugins must be installed and enabled outside OpenClaw before use." });
334
+ if (!isOpenAiCuratedMarketplaceName(params.identity.marketplaceName)) {
335
+ const target = params.identity.pluginName.endsWith(`@${params.identity.marketplaceName}`) ? params.identity.pluginName : `${params.identity.pluginName}@${params.identity.marketplaceName}`;
336
+ return activationFailure(params.identity, "disabled", { message: `${params.identity.marketplaceName} plugins must be installed and enabled by an owner before use. Run /codex plugins install ${target}.` });
337
+ }
487
338
  const listed = await listCuratedCodexPluginMetadata(params);
488
- const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName);
339
+ const resolved = findCodexMarketplacePluginSummary(listed, params.identity.marketplaceName, params.identity.pluginName);
489
340
  if (!resolved) {
490
- if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
341
+ if (!listed.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
491
342
  return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
492
343
  }
493
344
  if (resolved.marketplace.remoteMarketplaceName && !resolved.summary.remotePluginId) return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} detail unavailable: Codex did not return a remote plugin id.` });
345
+ if (resolved.summary.availability === "DISABLED_BY_ADMIN" || resolved.summary.installPolicy === "NOT_AVAILABLE") return activationFailure(params.identity, "disabled", { message: `${params.identity.pluginName} was disabled or made unavailable by its marketplace administrator.` });
494
346
  if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
495
347
  identity: params.identity,
496
348
  ok: true,
@@ -499,7 +351,21 @@ async function ensureCodexPluginActivation(params) {
499
351
  marketplace: resolved.marketplace,
500
352
  diagnostics: []
501
353
  };
502
- const installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, resolved.marketplace.remoteMarketplaceName && resolved.summary.remotePluginId ? resolved.summary.remotePluginId : params.identity.pluginName));
354
+ const remotePluginId = resolved.marketplace.remoteMarketplaceName ? resolved.summary.remotePluginId : void 0;
355
+ let installResponse;
356
+ try {
357
+ installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, remotePluginId ?? params.identity.pluginName));
358
+ } catch (error) {
359
+ if (!(error instanceof CodexAppServerRpcError) || error.code !== -32600 || !remotePluginId || error.message !== `remote plugin ${remotePluginId} is disabled by admin` && error.message !== `remote plugin ${remotePluginId} is not available for install`) throw error;
360
+ return {
361
+ identity: params.identity,
362
+ ok: false,
363
+ reason: "install_failed",
364
+ installAttempted: true,
365
+ marketplace: resolved.marketplace,
366
+ diagnostics: [{ message: `Codex plugin install failed: ${coerceErrorMessage(error)}` }]
367
+ };
368
+ }
503
369
  if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
504
370
  const refreshDiagnostics = [];
505
371
  let refreshFailed = false;
@@ -508,6 +374,7 @@ async function ensureCodexPluginActivation(params) {
508
374
  request: params.request,
509
375
  appCache: params.appCache,
510
376
  appCacheKey: params.appCacheKey,
377
+ configCwd: params.configCwd,
511
378
  metadataCache: params.metadataCache,
512
379
  deferAppInventoryRefresh: params.deferAppInventoryRefresh,
513
380
  targetAppIds: params.targetAppIds
@@ -515,7 +382,7 @@ async function ensureCodexPluginActivation(params) {
515
382
  refreshDiagnostics.push(...refreshResult.diagnostics);
516
383
  } catch (error) {
517
384
  refreshFailed = true;
518
- refreshDiagnostics.push({ message: `Codex plugin runtime refresh failed after install: ${error instanceof Error ? error.message : String(error)}` });
385
+ refreshDiagnostics.push({ message: `Codex plugin runtime refresh failed after install: ${coerceErrorMessage(error)}` });
519
386
  }
520
387
  const authRequired = installResponse.appsNeedingAuth.length > 0;
521
388
  return {
@@ -533,13 +400,13 @@ async function refreshCodexPluginRuntimeState(params) {
533
400
  const diagnostics = [];
534
401
  await listCuratedCodexPluginMetadata(params, { forceRefetch: true });
535
402
  await params.request("skills/list", {
536
- cwds: [],
403
+ cwds: params.configCwd ? [params.configCwd] : [],
537
404
  forceReload: true
538
405
  });
539
406
  try {
540
- await params.request("hooks/list", { cwds: [] });
407
+ await params.request("hooks/list", { cwds: params.configCwd ? [params.configCwd] : [] });
541
408
  } catch (error) {
542
- diagnostics.push({ message: `Codex hooks refresh skipped: ${error instanceof Error ? error.message : String(error)}` });
409
+ diagnostics.push({ message: `Codex hooks refresh skipped: ${coerceErrorMessage(error)}` });
543
410
  }
544
411
  await params.request("config/mcpServer/reload", void 0);
545
412
  if (params.appCache && params.appCacheKey) {
@@ -554,13 +421,16 @@ async function refreshCodexPluginRuntimeState(params) {
554
421
  targetAppIds: params.targetAppIds
555
422
  });
556
423
  } catch (error) {
557
- diagnostics.push({ message: `Codex app inventory refresh skipped: ${error instanceof Error ? error.message : String(error)}` });
424
+ diagnostics.push({ message: `Codex app inventory refresh skipped: ${coerceErrorMessage(error)}` });
558
425
  }
559
426
  }
560
427
  return { diagnostics };
561
428
  }
562
429
  async function listCuratedCodexPluginMetadata(params, options = {}) {
563
- const requestParams = options.forceRefetch ? { forceRefetch: true } : {};
430
+ const requestParams = {
431
+ ...params.configCwd ? { cwds: [params.configCwd] } : {},
432
+ ...options.forceRefetch ? { forceRefetch: true } : {}
433
+ };
564
434
  if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
565
435
  return (await params.metadataCache.load({
566
436
  appCacheKey: params.appCacheKey,
@@ -792,6 +662,7 @@ async function buildCodexPluginThreadConfig(params) {
792
662
  request: params.request,
793
663
  appCache,
794
664
  appCacheKey: params.appCacheKey,
665
+ configCwd: params.configCwd,
795
666
  metadataCache: params.metadataCache,
796
667
  deferAppInventoryRefresh: true,
797
668
  targetAppIds: record.ownedAppIds
@@ -867,7 +738,12 @@ async function buildCodexPluginThreadConfig(params) {
867
738
  inventory,
868
739
  accountApps: accountAppsResult.apps
869
740
  });
870
- const unresolvedDisabledPluginOwnership = policy.allowAllPlugins ? policy.pluginPolicies.find((pluginPolicy) => !pluginPolicy.enabled && !inventory.records.some((record) => record.policy.configKey === pluginPolicy.configKey && record.detail)) : void 0;
741
+ const unresolvedDisabledPluginOwnership = policy.allowAllPlugins ? policy.pluginPolicies.find((pluginPolicy) => {
742
+ const record = inventory.records.find((candidate) => candidate.policy.configKey === pluginPolicy.configKey);
743
+ const disabledByMarketplacePolicy = record?.summary.availability === "DISABLED_BY_ADMIN" || record?.summary.installPolicy === "NOT_AVAILABLE";
744
+ const unresolvedPluginIdentity = !record && inventory.diagnostics.some((diagnostic) => diagnostic.plugin?.configKey === pluginPolicy.configKey && (diagnostic.code === "plugin_disabled" || diagnostic.code === "plugin_missing" || diagnostic.code === "marketplace_missing"));
745
+ return (!pluginPolicy.enabled || disabledByMarketplacePolicy || unresolvedPluginIdentity) && !record?.detail;
746
+ }) : void 0;
871
747
  if (unresolvedDisabledPluginOwnership) diagnostics.push({
872
748
  code: "account_app_ownership_unavailable",
873
749
  plugin: unresolvedDisabledPluginOwnership,
@@ -907,6 +783,7 @@ async function buildCodexPluginThreadConfig(params) {
907
783
  marketplaceName: record.policy.marketplaceName,
908
784
  pluginName: record.policy.pluginName,
909
785
  allowDestructiveActions: record.policy.allowDestructiveActions,
786
+ allowOpenWorld: true,
910
787
  destructiveApprovalMode: record.policy.destructiveApprovalMode,
911
788
  mcpServerNames: [...record.detail?.mcpServers ?? []].toSorted()
912
789
  };
@@ -938,6 +815,7 @@ async function buildCodexPluginThreadConfig(params) {
938
815
  source: "account",
939
816
  appName: app.name,
940
817
  allowDestructiveActions: policy.allowDestructiveActions,
818
+ allowOpenWorld: true,
941
819
  destructiveApprovalMode: policy.destructiveApprovalMode,
942
820
  mcpServerNames: []
943
821
  };
@@ -1017,7 +895,7 @@ function buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext) {
1017
895
  for (const [appId, policy] of Object.entries(policyContext.apps).toSorted(([left], [right]) => left.localeCompare(right))) apps[appId] = {
1018
896
  enabled: true,
1019
897
  destructive_enabled: policy.allowDestructiveActions,
1020
- open_world_enabled: true,
898
+ open_world_enabled: policy.allowOpenWorld !== false,
1021
899
  default_tools_approval_mode: "auto",
1022
900
  ...policy.destructiveApprovalMode === "ask" ? { approvals_reviewer: "user" } : {}
1023
901
  };
@@ -1110,13 +988,10 @@ function mergeJsonObjects(left, right) {
1110
988
  const merged = { ...left };
1111
989
  for (const [key, value] of Object.entries(right)) {
1112
990
  const existing = merged[key];
1113
- merged[key] = isPlainJsonObject(existing) && isPlainJsonObject(value) ? mergeJsonObjects(existing, value) : value;
991
+ merged[key] = isJsonObject(existing) && isJsonObject(value) ? mergeJsonObjects(existing, value) : value;
1114
992
  }
1115
993
  return merged;
1116
994
  }
1117
- function isPlainJsonObject(value) {
1118
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
1119
- }
1120
995
  function fingerprintJson(value) {
1121
996
  return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
1122
997
  }
@@ -1142,15 +1017,18 @@ const CODEX_TURN_START_TEXT_INPUT_MAX_CHARS = 1 << 20;
1142
1017
  const DEFAULT_CODEX_PROJECTION_RESERVE_TOKENS = 2e4;
1143
1018
  const MIN_PROMPT_BUDGET_RATIO = .5;
1144
1019
  const MIN_PROMPT_BUDGET_TOKENS = 8e3;
1020
+ function neutralizeCodexExplicitMentionSigils(text) {
1021
+ return text.replace(/\$(?=[A-Za-z0-9_:-])/gu, "$").replace(/\[@(?=[A-Za-z0-9_:-]+\]\()/gu, "[@");
1022
+ }
1145
1023
  /** Projects assembled OpenClaw context-engine messages into Codex prompt inputs. */
1146
1024
  function projectContextEngineAssemblyForCodex(params) {
1147
1025
  const prompt = params.prompt.trim();
1148
1026
  const contextMessages = dropDuplicateTrailingPrompt(params.assembledMessages, prompt);
1149
1027
  const maxRenderedContextChars = normalizeRenderedContextMaxChars(params.maxRenderedContextChars);
1150
- const renderedContext = renderMessagesForCodexContext(contextMessages, {
1028
+ const renderedContext = neutralizeCodexExplicitMentionSigils(renderMessagesForCodexContext(contextMessages, {
1151
1029
  maxTextPartChars: resolveTextPartMaxChars(maxRenderedContextChars),
1152
1030
  toolPayloadMode: params.toolPayloadMode ?? "elide"
1153
- });
1031
+ }));
1154
1032
  const boundedContext = renderedContext ? truncateOlderContext(renderedContext, maxRenderedContextChars) : void 0;
1155
1033
  const promptPrefix = boundedContext ? [
1156
1034
  CONTEXT_HEADER,
@@ -1414,18 +1292,172 @@ function areContextEngineProjectionBindingsCompatible(previous, next) {
1414
1292
  return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
1415
1293
  }
1416
1294
  function resolveContextEngineCitationsMode(config) {
1417
- const rootConfig = isUnknownRecord(config) ? config : void 0;
1418
- const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
1295
+ const rootConfig = isRecord(config) ? config : void 0;
1296
+ const citations = (isRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
1419
1297
  return isJsonConfigValue(citations) ? citations : void 0;
1420
1298
  }
1421
- function isUnknownRecord(value) {
1422
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
1423
- }
1424
1299
  function isJsonConfigValue(value) {
1425
1300
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1426
1301
  if (typeof value === "number") return Number.isFinite(value);
1427
1302
  if (Array.isArray(value)) return value.every(isJsonConfigValue);
1428
- return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
1303
+ return isRecord(value) && Object.values(value).every(isJsonConfigValue);
1304
+ }
1305
+ //#endregion
1306
+ //#region extensions/codex/src/app-server/dynamic-tool-profile.ts
1307
+ /** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
1308
+ const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
1309
+ "read",
1310
+ "write",
1311
+ "edit",
1312
+ "apply_patch",
1313
+ "exec",
1314
+ "process",
1315
+ "update_plan",
1316
+ "tool_call",
1317
+ "tool_describe",
1318
+ "tool_search",
1319
+ "tool_search_code"
1320
+ ];
1321
+ const CODEX_NATIVE_GOAL_TOOL_EXCLUDES = [
1322
+ "get_goal",
1323
+ "create_goal",
1324
+ "update_goal"
1325
+ ];
1326
+ const CODEX_APP_SERVER_OWNED_REPLACEABLE_TOOL_EXCLUDES = /* @__PURE__ */ new Set([
1327
+ "read",
1328
+ "write",
1329
+ "edit",
1330
+ "apply_patch",
1331
+ "update_plan",
1332
+ ...CODEX_NATIVE_GOAL_TOOL_EXCLUDES
1333
+ ]);
1334
+ const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
1335
+ const DYNAMIC_TOOL_NAME_ALIASES = {
1336
+ bash: "exec",
1337
+ "apply-patch": "apply_patch"
1338
+ };
1339
+ /** Normalizes OpenClaw/Codex tool names before filtering and allowlist checks. */
1340
+ function normalizeCodexDynamicToolName(name) {
1341
+ const normalized = name.trim().toLowerCase();
1342
+ return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
1343
+ }
1344
+ /** True only for the host-scoped OpenClaw run's exact tool contract. */
1345
+ function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
1346
+ return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
1347
+ }
1348
+ /** True when a private source reply may use the message delivery tool only. */
1349
+ function isMessageOnlyCodexSourceReply(params) {
1350
+ return params.sourceReplyDeliveryMode === "message_tool_only" && params.toolsAllow?.length === 1 && normalizeCodexDynamicToolName(params.toolsAllow[0] ?? "") === "message";
1351
+ }
1352
+ /** Returns true for private QA runs that force the Codex runtime profile. */
1353
+ function isForcedPrivateQaCodexRuntime(env = process.env) {
1354
+ return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
1355
+ }
1356
+ /** Resolves whether dynamic tools load directly or through Codex tool search. */
1357
+ function resolveCodexDynamicToolsLoading(config, env = process.env) {
1358
+ return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
1359
+ }
1360
+ function normalizeCodexModelId(modelId) {
1361
+ const normalized = modelId?.trim().toLowerCase();
1362
+ if (!normalized) return "";
1363
+ return normalized.includes("/") ? normalized.split("/").at(-1) : normalized;
1364
+ }
1365
+ /** Returns true when model behavior requires direct dynamic-tool registration. */
1366
+ function shouldUseDirectCodexDynamicToolsForModel(modelId) {
1367
+ return shouldDisableCodexToolSearchForModel(modelId);
1368
+ }
1369
+ /** Returns true for models whose tool-search path is unsupported or inefficient. */
1370
+ function shouldDisableCodexToolSearchForModel(modelId) {
1371
+ return normalizeCodexModelId(modelId) === "gpt-5.4-nano";
1372
+ }
1373
+ /** Resolves dynamic-tool loading after applying model-specific restrictions. */
1374
+ function resolveCodexDynamicToolsLoadingForModel(config, modelId, env = process.env) {
1375
+ const loading = resolveCodexDynamicToolsLoading(config, env);
1376
+ return loading === "searchable" && shouldUseDirectCodexDynamicToolsForModel(modelId) ? "direct" : loading;
1377
+ }
1378
+ /** Resolves dynamic-tool loading for the app-server connection that will execute the turn. */
1379
+ function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}, env = process.env) {
1380
+ const loading = resolveCodexDynamicToolsLoadingForModel(config, modelId, env);
1381
+ return loading === "searchable" && options.connectionClass === "remote" ? "direct" : loading;
1382
+ }
1383
+ /** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
1384
+ function filterCodexDynamicTools(tools, config, env = process.env) {
1385
+ return filterCodexDynamicToolsWithOptions(tools, config, env, {
1386
+ preserveOpenClawReplacements: false,
1387
+ preserveOpenClawShell: false
1388
+ });
1389
+ }
1390
+ /** Keeps OpenClaw coding tools that replace a disabled Codex native surface. */
1391
+ function filterCodexDynamicToolsForDisabledNativeSurface(tools, config, options, env = process.env) {
1392
+ return filterCodexDynamicToolsWithOptions(tools, config, env, {
1393
+ preserveOpenClawReplacements: true,
1394
+ preserveOpenClawShell: options.preserveShell
1395
+ });
1396
+ }
1397
+ function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
1398
+ const excludes = /* @__PURE__ */ new Set();
1399
+ if (!options.preserveOpenClawReplacements) for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
1400
+ if (isForcedPrivateQaCodexRuntime(env)) excludes.add("apply_patch");
1401
+ else for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
1402
+ if (options.preserveOpenClawReplacements && CODEX_APP_SERVER_OWNED_REPLACEABLE_TOOL_EXCLUDES.has(name)) continue;
1403
+ if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
1404
+ excludes.add(name);
1405
+ }
1406
+ for (const name of config.codexDynamicToolsExclude ?? []) {
1407
+ const trimmed = normalizeCodexDynamicToolName(name);
1408
+ if (trimmed) excludes.add(trimmed);
1409
+ }
1410
+ return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
1411
+ }
1412
+ //#endregion
1413
+ //#region extensions/codex/src/app-server/thread-binding-policy.ts
1414
+ function shouldRotateCodexAppServerBindingForRuntime(params) {
1415
+ if (!params.current) return false;
1416
+ if (params.binding === params.current) return false;
1417
+ return params.connectionClass === "remote" || Boolean(params.binding);
1418
+ }
1419
+ function resolveCodexGpt56MultiAgentVersion(modelRef) {
1420
+ let modelId = modelRef?.trim().toLowerCase();
1421
+ if (!modelId) return;
1422
+ const slashIndex = modelId.indexOf("/");
1423
+ if (slashIndex > 0) {
1424
+ const provider = modelId.slice(0, slashIndex);
1425
+ if (provider !== "openai" && provider !== "codex") return;
1426
+ modelId = modelId.slice(slashIndex + 1);
1427
+ }
1428
+ if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
1429
+ return modelId === "gpt-5.6-luna" ? "v1" : void 0;
1430
+ }
1431
+ function shouldRotateCodexGpt56MultiAgentBinding(params) {
1432
+ const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
1433
+ const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
1434
+ return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
1435
+ }
1436
+ function isTransientWebSearchRestriction(params) {
1437
+ if (params.nativeProviderWebSearchSupport === "unknown") return true;
1438
+ if (params.params.config?.tools?.web?.search?.enabled === false) return false;
1439
+ if (params.params.disableTools === true) return true;
1440
+ const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
1441
+ if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
1442
+ if (params.webSearchAllowed !== false) return false;
1443
+ if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
1444
+ if (params.params.toolsAllow === void 0) return false;
1445
+ return !params.params.toolsAllow.some((name) => {
1446
+ const normalized = normalizeCodexDynamicToolName(name);
1447
+ return normalized === "*" || normalized === "web_search";
1448
+ });
1449
+ }
1450
+ function shouldRecheckRecoverablePluginBinding(params) {
1451
+ if (!params.pluginThreadConfig?.enabled) return false;
1452
+ if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
1453
+ const policyContext = params.binding.pluginAppPolicyContext;
1454
+ if (!policyContext) return false;
1455
+ const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
1456
+ const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
1457
+ const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
1458
+ 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));
1459
+ const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
1460
+ return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
1429
1461
  }
1430
1462
  //#endregion
1431
1463
  //#region extensions/codex/src/app-server/thread-fingerprints.ts
@@ -1469,21 +1501,15 @@ function fingerprintUserMcpServersAuthorizationHeader(value) {
1469
1501
  function fingerprintJsonObject(value) {
1470
1502
  return JSON.stringify(stabilizeJsonValue(value));
1471
1503
  }
1472
- /** Hash every resume-visible setting without retaining config, credentials, or instructions. */
1504
+ /** Hash thread-creation identity; settings already applied by turn/start must not restart Codex. */
1473
1505
  function fingerprintCodexThreadConfig(request, authProfileId, dynamicToolsFingerprint) {
1474
1506
  return hashCodexAppServerBindingFingerprint(fingerprintJsonObject({
1475
1507
  authProfileId: authProfileId ?? null,
1476
1508
  dynamicToolsFingerprint: dynamicToolsFingerprint ?? null,
1477
- model: request.model ?? null,
1478
- requestedModel: request.requestedModel === void 0 ? request.model ?? null : request.requestedModel,
1509
+ nativeMultiAgentVersion: resolveCodexGpt56MultiAgentVersion(typeof request.requestedModel === "string" ? request.requestedModel : typeof request.model === "string" ? request.model : void 0) ?? null,
1479
1510
  modelProvider: request.modelProvider ?? null,
1480
1511
  requestedModelProvider: request.requestedModelProvider === void 0 ? request.modelProvider ?? null : request.requestedModelProvider,
1481
- approvalPolicy: request.approvalPolicy ?? null,
1482
- approvalsReviewer: request.approvalsReviewer ?? null,
1483
- sandbox: request.sandbox ?? null,
1484
1512
  permissions: request.permissions ?? null,
1485
- personality: request.personality ?? null,
1486
- serviceTier: request.serviceTier === void 0 ? "<omitted>" : request.serviceTier,
1487
1513
  baseInstructions: request.baseInstructions ?? null,
1488
1514
  developerInstructions: request.developerInstructions ?? null,
1489
1515
  config: request.config ?? {}
@@ -1493,17 +1519,7 @@ function fingerprintEnvironmentSelection(environments) {
1493
1519
  return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
1494
1520
  }
1495
1521
  function fingerprintDynamicToolSpec(tool) {
1496
- return stabilizeDynamicToolFingerprintValue(tool);
1497
- }
1498
- function stabilizeDynamicToolFingerprintValue(value) {
1499
- if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
1500
- if (!isJsonObject(value)) return value;
1501
- const stable = {};
1502
- for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
1503
- if (key === "description") continue;
1504
- stable[key] = stabilizeDynamicToolFingerprintValue(child);
1505
- }
1506
- return stable;
1522
+ return stabilizeJsonValue(tool);
1507
1523
  }
1508
1524
  function stabilizeJsonValue(value) {
1509
1525
  if (Array.isArray(value)) return value.map(stabilizeJsonValue);
@@ -1663,216 +1679,78 @@ async function collectPersonalSkillRealPaths(homes, codexHome) {
1663
1679
  depth: current.depth + 1,
1664
1680
  onlyEscapedStateTargets: current.onlyEscapedStateTargets
1665
1681
  });
1666
- continue;
1667
- }
1668
- }
1669
- } catch (error) {
1670
- if (!isMissingPathError(error)) complete = false;
1671
- }
1672
- }
1673
- return {
1674
- complete,
1675
- skillPaths
1676
- };
1677
- }
1678
- /** Resolves the native user-scope skills that an isolated OpenClaw thread must disable. */
1679
- async function resolveCodexNativeSkillIsolation(params) {
1680
- params.signal?.throwIfAborted();
1681
- if (!process.env.OPENCLAW_STATE_DIR?.trim()) return;
1682
- const key = JSON.stringify([
1683
- path.resolve(resolveStateDir()),
1684
- path.resolve(params.cwd),
1685
- params.codexHome?.trim() || process.env.CODEX_HOME?.trim() || "",
1686
- params.home?.trim() || process.env.HOME?.trim() || "",
1687
- params.userProfile?.trim() || process.env.USERPROFILE?.trim() || ""
1688
- ]);
1689
- const cached = nativeSkillIsolationByClient.get(params.client);
1690
- if (cached?.key === key && (cached.settled || cached.signal === params.signal)) {
1691
- const isolation = await cached.result;
1692
- params.signal?.throwIfAborted();
1693
- return isolation;
1694
- }
1695
- const result = resolveUncachedCodexNativeSkillIsolation(params);
1696
- const entry = {
1697
- key,
1698
- result,
1699
- settled: false,
1700
- signal: params.signal
1701
- };
1702
- nativeSkillIsolationByClient.set(params.client, entry);
1703
- try {
1704
- const isolation = await result;
1705
- entry.settled = true;
1706
- params.signal?.throwIfAborted();
1707
- return isolation;
1708
- } catch (error) {
1709
- if (nativeSkillIsolationByClient.get(params.client)?.result === result) nativeSkillIsolationByClient.delete(params.client);
1710
- throw error;
1711
- }
1712
- }
1713
- async function resolveUncachedCodexNativeSkillIsolation(params) {
1714
- if (await usesDefaultStateDir()) return;
1715
- const response = await params.client.request("skills/list", {
1716
- cwds: [params.cwd],
1717
- forceReload: true
1718
- }, { signal: params.signal });
1719
- const homes = [params.home?.trim() || process.env.HOME?.trim() || process.env.USERPROFILE?.trim() || os.homedir()];
1720
- if (process.platform === "win32") homes.push(params.userProfile?.trim() || os.homedir());
1721
- const personalSkills = await collectPersonalSkillRealPaths([...new Set(homes.map((home) => path.resolve(home)))], params.codexHome);
1722
- return { disabledUserSkillPaths: [...personalSkills.complete ? personalSkills.skillPaths : /* @__PURE__ */ new Set([...personalSkills.skillPaths, ...response.data.flatMap((entry) => entry.skills.filter((skill) => skill.scope === "user").map((skill) => skill.path))])].toSorted((left, right) => left.localeCompare(right)) };
1723
- }
1724
- /** Applies path-exact session rules after caller config so isolated user skills stay disabled. */
1725
- function applyCodexNativeSkillIsolation(config, isolation) {
1726
- if (!isolation) return config;
1727
- const existingRules = config?.["skills.config"];
1728
- if (existingRules !== void 0 && !Array.isArray(existingRules)) throw new Error("Codex thread skills.config must be an array");
1729
- const disabledRules = isolation.disabledUserSkillPaths.map((skillPath) => ({
1730
- path: skillPath,
1731
- enabled: false
1732
- }));
1733
- return {
1734
- ...config,
1735
- "skills.include_instructions": false,
1736
- "skills.config": [...existingRules ?? [], ...disabledRules]
1737
- };
1738
- }
1739
- //#endregion
1740
- //#region extensions/codex/src/app-server/dynamic-tool-profile.ts
1741
- /** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
1742
- const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
1743
- "read",
1744
- "write",
1745
- "edit",
1746
- "apply_patch",
1747
- "exec",
1748
- "process",
1749
- "update_plan",
1750
- "tool_call",
1751
- "tool_describe",
1752
- "tool_search",
1753
- "tool_search_code"
1754
- ];
1755
- const CODEX_NATIVE_GOAL_TOOL_EXCLUDES = [
1756
- "get_goal",
1757
- "create_goal",
1758
- "update_goal"
1759
- ];
1760
- const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
1761
- const DYNAMIC_TOOL_NAME_ALIASES = {
1762
- bash: "exec",
1763
- "apply-patch": "apply_patch"
1764
- };
1765
- /** Normalizes OpenClaw/Codex tool names before filtering and allowlist checks. */
1766
- function normalizeCodexDynamicToolName(name) {
1767
- const normalized = name.trim().toLowerCase();
1768
- return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
1769
- }
1770
- /** True only for the host-scoped OpenClaw run's exact tool contract. */
1771
- function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
1772
- return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
1773
- }
1774
- /** Returns true for private QA runs that force the Codex runtime profile. */
1775
- function isForcedPrivateQaCodexRuntime(env = process.env) {
1776
- return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
1777
- }
1778
- /** Resolves whether dynamic tools load directly or through Codex tool search. */
1779
- function resolveCodexDynamicToolsLoading(config, env = process.env) {
1780
- return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
1781
- }
1782
- function normalizeCodexModelId(modelId) {
1783
- const normalized = modelId?.trim().toLowerCase();
1784
- if (!normalized) return "";
1785
- return normalized.includes("/") ? normalized.split("/").at(-1) : normalized;
1786
- }
1787
- /** Returns true when model behavior requires direct dynamic-tool registration. */
1788
- function shouldUseDirectCodexDynamicToolsForModel(modelId) {
1789
- return shouldDisableCodexToolSearchForModel(modelId);
1790
- }
1791
- /** Returns true for models whose tool-search path is unsupported or inefficient. */
1792
- function shouldDisableCodexToolSearchForModel(modelId) {
1793
- return normalizeCodexModelId(modelId) === "gpt-5.4-nano";
1794
- }
1795
- /** Resolves dynamic-tool loading after applying model-specific restrictions. */
1796
- function resolveCodexDynamicToolsLoadingForModel(config, modelId, env = process.env) {
1797
- const loading = resolveCodexDynamicToolsLoading(config, env);
1798
- return loading === "searchable" && shouldUseDirectCodexDynamicToolsForModel(modelId) ? "direct" : loading;
1799
- }
1800
- /** Resolves dynamic-tool loading for the app-server connection that will execute the turn. */
1801
- function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}, env = process.env) {
1802
- const loading = resolveCodexDynamicToolsLoadingForModel(config, modelId, env);
1803
- return loading === "searchable" && options.connectionClass === "remote" ? "direct" : loading;
1804
- }
1805
- /** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
1806
- function filterCodexDynamicTools(tools, config, env = process.env) {
1807
- return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: false });
1808
- }
1809
- /** Keeps exec/process only when Codex cannot advertise an environment-backed native shell. */
1810
- function filterCodexDynamicToolsWithOpenClawShell(tools, config, env = process.env) {
1811
- return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: true });
1812
- }
1813
- function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
1814
- const excludes = /* @__PURE__ */ new Set();
1815
- for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
1816
- if (isForcedPrivateQaCodexRuntime(env)) excludes.add("apply_patch");
1817
- else for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
1818
- if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
1819
- excludes.add(name);
1820
- }
1821
- for (const name of config.codexDynamicToolsExclude ?? []) {
1822
- const trimmed = normalizeCodexDynamicToolName(name);
1823
- if (trimmed) excludes.add(trimmed);
1824
- }
1825
- return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
1826
- }
1827
- //#endregion
1828
- //#region extensions/codex/src/app-server/thread-binding-policy.ts
1829
- function shouldRotateCodexAppServerBindingForRuntime(params) {
1830
- if (!params.current) return false;
1831
- if (params.binding === params.current) return false;
1832
- return params.connectionClass === "remote" || Boolean(params.binding);
1833
- }
1834
- function resolveCodexGpt56MultiAgentVersion(modelRef) {
1835
- let modelId = modelRef?.trim().toLowerCase();
1836
- if (!modelId) return;
1837
- const slashIndex = modelId.indexOf("/");
1838
- if (slashIndex > 0) {
1839
- const provider = modelId.slice(0, slashIndex);
1840
- if (provider !== "openai" && provider !== "codex") return;
1841
- modelId = modelId.slice(slashIndex + 1);
1682
+ continue;
1683
+ }
1684
+ }
1685
+ } catch (error) {
1686
+ if (!isMissingPathError(error)) complete = false;
1687
+ }
1842
1688
  }
1843
- if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
1844
- return modelId === "gpt-5.6-luna" ? "v1" : void 0;
1689
+ return {
1690
+ complete,
1691
+ skillPaths
1692
+ };
1845
1693
  }
1846
- function shouldRotateCodexGpt56MultiAgentBinding(params) {
1847
- const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
1848
- const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
1849
- return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
1694
+ /** Resolves the native user-scope skills that an isolated OpenClaw thread must disable. */
1695
+ async function resolveCodexNativeSkillIsolation(params) {
1696
+ params.signal?.throwIfAborted();
1697
+ if (!process.env.OPENCLAW_STATE_DIR?.trim()) return;
1698
+ const key = JSON.stringify([
1699
+ path.resolve(resolveStateDir()),
1700
+ path.resolve(params.cwd),
1701
+ params.codexHome?.trim() || process.env.CODEX_HOME?.trim() || "",
1702
+ params.home?.trim() || process.env.HOME?.trim() || "",
1703
+ params.userProfile?.trim() || process.env.USERPROFILE?.trim() || ""
1704
+ ]);
1705
+ const cached = nativeSkillIsolationByClient.get(params.client);
1706
+ if (cached?.key === key && (cached.settled || cached.signal === params.signal)) {
1707
+ const isolation = await cached.result;
1708
+ params.signal?.throwIfAborted();
1709
+ return isolation;
1710
+ }
1711
+ const result = resolveUncachedCodexNativeSkillIsolation(params);
1712
+ const entry = {
1713
+ key,
1714
+ result,
1715
+ settled: false,
1716
+ signal: params.signal
1717
+ };
1718
+ nativeSkillIsolationByClient.set(params.client, entry);
1719
+ try {
1720
+ const isolation = await result;
1721
+ entry.settled = true;
1722
+ params.signal?.throwIfAborted();
1723
+ return isolation;
1724
+ } catch (error) {
1725
+ if (nativeSkillIsolationByClient.get(params.client)?.result === result) nativeSkillIsolationByClient.delete(params.client);
1726
+ throw error;
1727
+ }
1850
1728
  }
1851
- function isTransientWebSearchRestriction(params) {
1852
- if (params.nativeProviderWebSearchSupport === "unknown") return true;
1853
- if (params.params.config?.tools?.web?.search?.enabled === false) return false;
1854
- if (params.params.disableTools === true) return true;
1855
- const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
1856
- if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
1857
- if (params.webSearchAllowed !== false) return false;
1858
- if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
1859
- if (params.params.toolsAllow === void 0) return false;
1860
- return !params.params.toolsAllow.some((name) => {
1861
- const normalized = normalizeCodexDynamicToolName(name);
1862
- return normalized === "*" || normalized === "web_search";
1863
- });
1729
+ async function resolveUncachedCodexNativeSkillIsolation(params) {
1730
+ if (await usesDefaultStateDir()) return;
1731
+ const response = await params.client.request("skills/list", {
1732
+ cwds: [params.cwd],
1733
+ forceReload: true
1734
+ }, { signal: params.signal });
1735
+ const homes = [params.home?.trim() || process.env.HOME?.trim() || process.env.USERPROFILE?.trim() || os.homedir()];
1736
+ if (process.platform === "win32") homes.push(params.userProfile?.trim() || os.homedir());
1737
+ const personalSkills = await collectPersonalSkillRealPaths([...new Set(homes.map((home) => path.resolve(home)))], params.codexHome);
1738
+ return { disabledUserSkillPaths: [...personalSkills.complete ? personalSkills.skillPaths : /* @__PURE__ */ new Set([...personalSkills.skillPaths, ...response.data.flatMap((entry) => entry.skills.filter((skill) => skill.scope === "user").map((skill) => skill.path))])].toSorted((left, right) => left.localeCompare(right)) };
1864
1739
  }
1865
- function shouldRecheckRecoverablePluginBinding(params) {
1866
- if (!params.pluginThreadConfig?.enabled) return false;
1867
- if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
1868
- const policyContext = params.binding.pluginAppPolicyContext;
1869
- if (!policyContext) return false;
1870
- const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
1871
- const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
1872
- const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
1873
- 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));
1874
- const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
1875
- return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
1740
+ /** Applies path-exact session rules after caller config so isolated user skills stay disabled. */
1741
+ function applyCodexNativeSkillIsolation(config, isolation) {
1742
+ if (!isolation) return config;
1743
+ const existingRules = config?.["skills.config"];
1744
+ if (existingRules !== void 0 && !Array.isArray(existingRules)) throw new Error("Codex thread skills.config must be an array");
1745
+ const disabledRules = isolation.disabledUserSkillPaths.map((skillPath) => ({
1746
+ path: skillPath,
1747
+ enabled: false
1748
+ }));
1749
+ return {
1750
+ ...config,
1751
+ "skills.include_instructions": false,
1752
+ "skills.config": [...existingRules ?? [], ...disabledRules]
1753
+ };
1876
1754
  }
1877
1755
  //#endregion
1878
1756
  //#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
@@ -1888,10 +1766,10 @@ var CodexThreadBindingConflictError = class extends Error {
1888
1766
  this.name = "CodexThreadBindingConflictError";
1889
1767
  }
1890
1768
  };
1891
- var CodexRingZeroAttestationError = class extends Error {
1769
+ var CodexRestrictedToolSurfaceAttestationError = class extends Error {
1892
1770
  constructor(cause) {
1893
- super("Codex ring-zero MCP attestation failed", { cause });
1894
- this.name = "CodexRingZeroAttestationError";
1771
+ super("Codex restricted-tool-surface MCP attestation failed", { cause });
1772
+ this.name = "CodexRestrictedToolSurfaceAttestationError";
1895
1773
  }
1896
1774
  };
1897
1775
  var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
@@ -1967,30 +1845,13 @@ const CODEX_REASONING_EFFORTS = [
1967
1845
  "max",
1968
1846
  "ultra"
1969
1847
  ];
1970
- const GPT_56_MAX_REASONING_EFFORTS = [
1971
- "low",
1972
- "medium",
1973
- "high",
1974
- "xhigh",
1975
- "max"
1976
- ];
1977
- const GPT_56_ULTRA_REASONING_EFFORTS = [...GPT_56_MAX_REASONING_EFFORTS, "ultra"];
1978
- const GPT_5_PRO_REASONING_EFFORTS = [
1848
+ const LEGACY_PRO_REASONING_EFFORTS = [
1979
1849
  "medium",
1980
1850
  "high",
1981
1851
  "xhigh"
1982
1852
  ];
1983
- const GPT_56_ULTRA_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.6-sol", "gpt-5.6-terra"]);
1984
- const GPT_56_MAX_MODEL_IDS = /* @__PURE__ */ new Set([...GPT_56_ULTRA_MODEL_IDS, "gpt-5.6-luna"]);
1985
- const MODERN_CODEX_MODEL_IDS = /* @__PURE__ */ new Set([
1986
- ...GPT_56_MAX_MODEL_IDS,
1987
- "gpt-5.5",
1988
- "gpt-5.5-pro",
1989
- "gpt-5.4",
1990
- "gpt-5.4-pro",
1991
- "gpt-5.4-mini",
1992
- "gpt-5.3-codex-spark"
1993
- ]);
1853
+ const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
1854
+ const MODERN_GPT_5_MODEL_ID_RE = /^gpt-5\.(?:[3-9]|[1-9]\d)(?:$|-)/u;
1994
1855
  function normalizeCodexReasoningEfforts(efforts) {
1995
1856
  if (!efforts) return [];
1996
1857
  const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
@@ -2010,24 +1871,21 @@ function resolveSupportedReasoningEffort(params) {
2010
1871
  const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
2011
1872
  return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
2012
1873
  }
2013
- function resolveFallbackReasoningEfforts(modelId) {
2014
- const normalized = modelId.trim().toLowerCase();
2015
- if (GPT_56_ULTRA_MODEL_IDS.has(normalized)) return GPT_56_ULTRA_REASONING_EFFORTS;
2016
- if (normalized === "gpt-5.6-luna") return GPT_56_MAX_REASONING_EFFORTS;
2017
- if (normalized === "gpt-5.5-pro" || normalized === "gpt-5.4-pro") return GPT_5_PRO_REASONING_EFFORTS;
2018
- }
2019
- /** Resolve a turn effort from app-server metadata, with exact-name offline fallbacks. */
1874
+ /** Resolve a turn effort from the selected model's provider-owned metadata. */
2020
1875
  function resolveCodexAppServerReasoningEffort(params) {
2021
1876
  if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
2022
- const supportedReasoningEfforts = params.supportedReasoningEfforts ?? resolveFallbackReasoningEfforts(params.modelId);
2023
- if (supportedReasoningEfforts) return resolveSupportedReasoningEffort({
1877
+ if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
2024
1878
  requested: params.thinkLevel,
2025
- supportedReasoningEfforts
1879
+ supportedReasoningEfforts: params.supportedReasoningEfforts
1880
+ }) ?? null;
1881
+ const modelId = params.modelId.trim().toLowerCase();
1882
+ if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
1883
+ requested: params.thinkLevel,
1884
+ supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
2026
1885
  }) ?? null;
2027
- const normalizedModelId = params.modelId.trim().toLowerCase();
2028
- if (params.thinkLevel === "minimal") return MODERN_CODEX_MODEL_IDS.has(normalizedModelId) ? "low" : "minimal";
2029
- if (params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
2030
- return params.thinkLevel === "max" && GPT_56_MAX_MODEL_IDS.has(normalizedModelId) ? "max" : null;
1886
+ if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
1887
+ if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
1888
+ return null;
2031
1889
  }
2032
1890
  //#endregion
2033
1891
  //#region extensions/codex/src/app-server/thread-model-selection.ts
@@ -2109,6 +1967,7 @@ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts)
2109
1967
  function buildDeveloperInstructions(params, options = {}) {
2110
1968
  const deferredToolNames = /* @__PURE__ */ new Set();
2111
1969
  let hasSkillWorkshop = false;
1970
+ let hasSessionsSpawn = false;
2112
1971
  let hasSessionsYield = false;
2113
1972
  let hasSeenDirectNamespace = false;
2114
1973
  let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
@@ -2119,6 +1978,7 @@ function buildDeveloperInstructions(params, options = {}) {
2119
1978
  const name = tool.name.trim();
2120
1979
  if (tool.deferLoading === true && name) deferredToolNames.add(name);
2121
1980
  hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
1981
+ hasSessionsSpawn ||= name === "sessions_spawn";
2122
1982
  hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
2123
1983
  messageToolAvailable ||= name === "message";
2124
1984
  }
@@ -2127,20 +1987,24 @@ function buildDeveloperInstructions(params, options = {}) {
2127
1987
  surface: "codex_app_server",
2128
1988
  includeLegacyGlobalGuidance: false
2129
1989
  }).join("\n");
1990
+ const nativeDelegationAvailable = params.disableTools !== true && params.delegationCapability !== "report_only" && !isMessageOnlyCodexSourceReply(params) && !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) && !shouldDisableCodexToolSearchForModel(params.modelId);
1991
+ const deferredToolDiscoveryGuidance = deferredToolNames.size > 0 || nativeDelegationAvailable ? "Deferred tools may be absent from the direct tool list. Use `tool_search` when directly callable. On code-mode-only models, use `exec` instead: filter `ALL_TOOLS` by name and description, then call the matching entry through `tools`." : void 0;
2130
1992
  return [
2131
1993
  "You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
2132
- deferredToolNames.size > 0 ? `Deferred searchable OpenClaw dynamic tools available: ${[...deferredToolNames].toSorted((left, right) => left.localeCompare(right)).join(", ")}. Use \`tool_search\` to load exact callable specs before use.` : void 0,
1994
+ deferredToolNames.size > 0 ? `Deferred searchable OpenClaw dynamic tools available: ${[...deferredToolNames].toSorted((left, right) => left.localeCompare(right)).join(", ")}.` : void 0,
1995
+ deferredToolDiscoveryGuidance,
2133
1996
  hasSkillWorkshop ? buildSkillWorkshopPromptSection().join("\n") : void 0,
2134
- "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`.",
2135
- hasSessionsYield ? "When a native child's result belongs in a later turn, end the current turn with `openclaw_direct.sessions_yield`; the completion arrives as the next model-visible input. Use native `wait_agent` only for an intentional same-turn wait when the immediate next step is blocked on the child. Never loop-poll for native child completion." : void 0,
1997
+ nativeDelegationAvailable ? `Use Codex native \`spawn_agent\` for Codex subagents. \`spawn_agent\` and the other native collaboration tools may be deferred.${hasSessionsSpawn ? " Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`." : ""}` : void 0,
1998
+ hasSessionsYield && nativeDelegationAvailable ? "When a native child's result belongs in a later turn, end the current turn with `openclaw_direct.sessions_yield`; the completion arrives as the next model-visible input. Use native `wait_agent` only for an intentional same-turn wait when the immediate next step is blocked on the child. Never loop-poll for native child completion." : void 0,
2136
1999
  buildVisibleReplyInstruction(params, messageToolAvailable),
2000
+ TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT,
2137
2001
  nativeCommandGuidance,
2138
2002
  params.extraSystemPrompt
2139
2003
  ].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
2140
2004
  }
2141
2005
  function buildVisibleReplyInstruction(params, messageToolAvailable) {
2142
- 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.";
2143
- 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.";
2006
+ 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`. Set `final=true`, or omit it, for the completed reply to the current source conversation; OpenClaw stops after confirming delivery. Do not repeat visible message content in your final answer.";
2007
+ 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` for supported non-text actions in the current conversation, such as reacting to its current message. Reserve other `message` actions for explicit out-of-band sends or media/file delivery. Reactions are not delivered automatically.";
2144
2008
  return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
2145
2009
  }
2146
2010
  //#endregion
@@ -2149,16 +2013,13 @@ const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG = {
2149
2013
  "features.standalone_web_search": false,
2150
2014
  web_search: "disabled"
2151
2015
  };
2152
- function normalizeOptionalString$1(value) {
2153
- return typeof value === "string" ? value.trim() || void 0 : void 0;
2154
- }
2155
2016
  function normalizeUniqueStrings(value) {
2156
2017
  if (!Array.isArray(value)) return;
2157
- const normalized = [...new Set(value.map(normalizeOptionalString$1).filter((entry) => Boolean(entry)))];
2018
+ const normalized = [...new Set(value.map(normalizeOptionalString).filter((entry) => Boolean(entry)))];
2158
2019
  return normalized.length > 0 ? normalized : void 0;
2159
2020
  }
2160
2021
  function hasManagedSearchProvider(config) {
2161
- return normalizeOptionalString$1(config?.tools?.web?.search?.provider) !== void 0;
2022
+ return normalizeOptionalString(config?.tools?.web?.search?.provider) !== void 0;
2162
2023
  }
2163
2024
  function hasNativeDomainRestrictions(config) {
2164
2025
  return normalizeUniqueStrings(config?.tools?.web?.search?.openaiCodex?.allowedDomains) !== void 0;
@@ -2173,10 +2034,10 @@ function buildCodexNativeWebSearchThreadConfig(config) {
2173
2034
  if (allowedDomains) threadConfig["tools.web_search.allowed_domains"] = allowedDomains;
2174
2035
  if (nativeConfig?.contextSize) threadConfig["tools.web_search.context_size"] = nativeConfig.contextSize;
2175
2036
  const location = nativeConfig?.userLocation;
2176
- const country = normalizeOptionalString$1(location?.country);
2177
- const region = normalizeOptionalString$1(location?.region);
2178
- const city = normalizeOptionalString$1(location?.city);
2179
- const timezone = normalizeOptionalString$1(location?.timezone);
2037
+ const country = normalizeOptionalString(location?.country);
2038
+ const region = normalizeOptionalString(location?.region);
2039
+ const city = normalizeOptionalString(location?.city);
2040
+ const timezone = normalizeOptionalString(location?.timezone);
2180
2041
  if (country) threadConfig["tools.web_search.location.country"] = country;
2181
2042
  if (region) threadConfig["tools.web_search.location.region"] = region;
2182
2043
  if (city) threadConfig["tools.web_search.location.city"] = city;
@@ -2223,26 +2084,50 @@ const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
2223
2084
  const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
2224
2085
  const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
2225
2086
  const CODEX_DELEGATION_DISABLED_THREAD_CONFIG = {
2087
+ "agents.enabled": false,
2226
2088
  "features.multi_agent": false,
2227
2089
  "features.multi_agent_v2": false
2228
2090
  };
2091
+ const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
2092
+ "apps",
2093
+ "artifact",
2094
+ "browser_use",
2095
+ "browser_use_external",
2096
+ "browser_use_full_cdp_access",
2097
+ "chronicle",
2098
+ "code_mode",
2099
+ "code_mode_only",
2100
+ "computer_use",
2101
+ "current_time_reminder",
2102
+ "default_mode_request_user_input",
2103
+ "deferred_executor",
2104
+ "goals",
2105
+ "hooks",
2106
+ "image_generation",
2107
+ "memories",
2108
+ "multi_agent",
2109
+ "multi_agent_v2",
2110
+ "plugins",
2111
+ "request_permissions_tool",
2112
+ "skill_search",
2113
+ "shell_tool",
2114
+ "standalone_web_search",
2115
+ "token_budget",
2116
+ "unified_exec",
2117
+ "view_image",
2118
+ "web_search_cached",
2119
+ "web_search_request",
2120
+ "workspace_dependencies"
2121
+ ]);
2229
2122
  const CODEX_RING_ZERO_THREAD_CONFIG = {
2230
- "features.apps": false,
2231
- "features.current_time_reminder": false,
2232
- "features.deferred_executor": false,
2233
- "features.enable_fanout": false,
2234
- "features.goals": false,
2235
- "features.hooks": false,
2236
- "features.image_generation": false,
2237
- "features.memories": false,
2238
- "features.multi_agent": false,
2239
- "features.multi_agent_v2": false,
2240
- "features.plugins": false,
2241
- "features.standalone_web_search": false,
2242
- "features.token_budget": false,
2123
+ ...CODEX_DELEGATION_DISABLED_THREAD_CONFIG,
2124
+ ...Object.fromEntries([...CODEX_RING_ZERO_RESTRICTED_FEATURES].map((feature) => [`features.${feature}`, false])),
2243
2125
  "orchestrator.mcp.enabled": false,
2244
2126
  "orchestrator.skills.enabled": false,
2127
+ "skills.bundled.enabled": false,
2128
+ "skills.include_instructions": false,
2245
2129
  "tools.experimental_request_user_input.enabled": false,
2130
+ "tools.update_plan.enabled": false,
2246
2131
  hooks: {
2247
2132
  PreToolUse: [],
2248
2133
  PermissionRequest: [],
@@ -2259,22 +2144,13 @@ const CODEX_RING_ZERO_THREAD_CONFIG = {
2259
2144
  notify: [],
2260
2145
  web_search: "disabled"
2261
2146
  };
2262
- const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
2263
- "apps",
2264
- "code_mode",
2265
- "code_mode_only",
2266
- "current_time_reminder",
2267
- "deferred_executor",
2268
- "enable_fanout",
2269
- "goals",
2270
- "hooks",
2271
- "image_generation",
2272
- "memories",
2273
- "multi_agent",
2274
- "multi_agent_v2",
2275
- "plugins",
2276
- "standalone_web_search",
2277
- "token_budget"
2147
+ const CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES = /* @__PURE__ */ new Map([
2148
+ ["connectors", "apps"],
2149
+ ["imagegenext", "image_generation"],
2150
+ ["collab", "multi_agent"],
2151
+ ["memory_tool", "memories"],
2152
+ ["telepathy", "chronicle"],
2153
+ ["codex_hooks", "hooks"]
2278
2154
  ]);
2279
2155
  const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
2280
2156
  "mdm",
@@ -2320,7 +2196,7 @@ function buildThreadStartParams(params, options) {
2320
2196
  webSearchAllowed: options.webSearchAllowed,
2321
2197
  appServer: options.appServer,
2322
2198
  hostSystemAgentActive: options.hostSystemAgentActive,
2323
- ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
2199
+ restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames
2324
2200
  }),
2325
2201
  ...resolveCodexThreadEnvironmentSelection(options),
2326
2202
  developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
@@ -2369,7 +2245,7 @@ function buildThreadResumeParams(params, options) {
2369
2245
  webSearchAllowed: options.webSearchAllowed,
2370
2246
  appServer: options.appServer,
2371
2247
  hostSystemAgentActive: options.hostSystemAgentActive,
2372
- ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
2248
+ restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames
2373
2249
  }),
2374
2250
  developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
2375
2251
  };
@@ -2408,9 +2284,15 @@ function resolveDirectOnlyToolNamespaces(dynamicTools) {
2408
2284
  }
2409
2285
  function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2410
2286
  const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
2287
+ const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params);
2288
+ const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true;
2411
2289
  const configMcpServers = config?.mcp_servers;
2412
- if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
2413
- const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
2290
+ if (restrictedToolSurface && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex restricted tool surface received invalid thread mcp_servers config");
2291
+ const restrictedToolSurfaceMcpServerNames = [...options.restrictedToolSurfaceInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
2292
+ const restrictedRunConfig = restrictedToolSurface && isJsonObject(configMcpServers) ? {
2293
+ ...config,
2294
+ mcp_servers: {}
2295
+ } : config;
2414
2296
  const webSearchConfig = resolveCodexWebSearchPlan({
2415
2297
  config: params.config,
2416
2298
  disableTools: params.disableTools,
@@ -2418,8 +2300,8 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2418
2300
  nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
2419
2301
  webSearchAllowed: options.webSearchAllowed
2420
2302
  }).threadConfig;
2421
- const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
2422
- const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
2303
+ const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(restrictedRunConfig, webSearchConfig), options);
2304
+ const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildCodexRestrictedToolThreadConfigPatch(restrictedToolSurfaceMcpServerNames) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames)) ?? baseConfig;
2423
2305
  if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
2424
2306
  return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
2425
2307
  ...runtimeConfig,
@@ -2428,6 +2310,9 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
2428
2310
  }
2429
2311
  function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
2430
2312
  if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
2313
+ return buildCodexRestrictedToolThreadConfigPatch(inheritedMcpServerNames);
2314
+ }
2315
+ function buildCodexRestrictedToolThreadConfigPatch(inheritedMcpServerNames) {
2431
2316
  const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
2432
2317
  return {
2433
2318
  ...CODEX_RING_ZERO_THREAD_CONFIG,
@@ -2443,15 +2328,15 @@ async function readCodexInheritedMcpServerNames(client, cwd, signal) {
2443
2328
  if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
2444
2329
  for (const layer of response.layers) {
2445
2330
  if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
2446
- if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex ring-zero cannot override config layer ${layer.name.type}`);
2447
- 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}`);
2331
+ if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex restricted tool surface cannot override config layer ${layer.name.type}`);
2332
+ if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex restricted tool surface does not recognize config layer ${layer.name.type}`);
2448
2333
  }
2449
2334
  const configuredServers = response.config.mcp_servers;
2450
2335
  if (configuredServers === void 0) return [];
2451
2336
  if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
2452
2337
  return Object.keys(configuredServers).toSorted();
2453
2338
  }
2454
- async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
2339
+ async function assertCodexRestrictedToolSurfaceHasNoManagedHooks(client, signal) {
2455
2340
  const response = await client.request("configRequirements/read", void 0, { signal });
2456
2341
  if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
2457
2342
  if (response.requirements === null) return;
@@ -2464,7 +2349,7 @@ async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
2464
2349
  const hooks = response.requirements[key];
2465
2350
  if (hooks === void 0 || hooks === null) continue;
2466
2351
  if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
2467
- if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex ring-zero cannot override managed hooks");
2352
+ if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex restricted tool surface cannot override managed hooks");
2468
2353
  }
2469
2354
  for (const key of ["featureRequirements", "feature_requirements"]) {
2470
2355
  const requirements = response.requirements[key];
@@ -2472,22 +2357,35 @@ async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
2472
2357
  if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
2473
2358
  for (const [feature, enabled] of Object.entries(requirements)) {
2474
2359
  if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
2475
- if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(feature)) throw new Error(`Codex ring-zero cannot override required feature ${feature}`);
2360
+ const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
2361
+ if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature)) throw new Error(`Codex restricted tool surface cannot override required feature ${feature}`);
2476
2362
  }
2477
2363
  }
2478
2364
  }
2479
- async function attestCodexRingZeroThreadHasNoMcpServers(client, threadId, signal) {
2365
+ async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal) {
2366
+ const configuredServers = threadConfig?.mcp_servers;
2367
+ if (configuredServers !== void 0 && !isJsonObject(configuredServers)) throw new Error("Codex restricted-tool-surface thread config has invalid mcp_servers");
2368
+ const expectedDisabledServerNames = /* @__PURE__ */ new Set();
2369
+ for (const [name, serverConfig] of Object.entries(configuredServers ?? {})) {
2370
+ if (!isJsonObject(serverConfig) || serverConfig.enabled !== false) throw new Error(`Codex restricted-tool-surface MCP server ${name} is not disabled`);
2371
+ expectedDisabledServerNames.add(name);
2372
+ }
2480
2373
  const response = await client.request("mcpServerStatus/list", {
2481
2374
  threadId,
2482
- limit: 1,
2483
2375
  detail: "toolsAndAuthOnly"
2484
2376
  }, { signal });
2485
- if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid ring-zero attestation");
2486
- if (response.data.length > 0) {
2487
- const first = response.data[0];
2488
- const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
2489
- throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
2490
- }
2377
+ if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
2378
+ const observedDisabledServerNames = /* @__PURE__ */ new Set();
2379
+ for (const status of response.data) {
2380
+ if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
2381
+ if (!expectedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
2382
+ if (observedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
2383
+ observedDisabledServerNames.add(status.name);
2384
+ if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
2385
+ if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
2386
+ if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
2387
+ }
2388
+ for (const expectedName of expectedDisabledServerNames) if (!observedDisabledServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing server ${expectedName}`);
2491
2389
  if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
2492
2390
  }
2493
2391
  function hasNonEmptyJsonValue(value) {
@@ -2509,32 +2407,6 @@ function resolveCodexThreadEnvironmentSelection(options) {
2509
2407
  return {};
2510
2408
  }
2511
2409
  //#endregion
2512
- //#region extensions/codex/src/app-server/thread-resume.ts
2513
- /** Owns Codex thread/resume subscription safety. */
2514
- /** Resumes one thread and retires the physical client when acceptance is indeterminate. */
2515
- async function resumeCodexAppServerThread(params) {
2516
- const threadId = params.request.threadId;
2517
- let response;
2518
- try {
2519
- response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
2520
- ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
2521
- ...params.signal ? { signal: params.signal } : {}
2522
- }));
2523
- assertCodexThreadResumeSubscription(threadId, response.thread.id);
2524
- } catch (error) {
2525
- if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
2526
- if (error instanceof CodexAppServerRpcError) throw error;
2527
- try {
2528
- await params.abandonClient();
2529
- } catch (abandonError) {
2530
- throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
2531
- }
2532
- if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
2533
- throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
2534
- }
2535
- return response;
2536
- }
2537
- //#endregion
2538
2410
  //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
2539
2411
  function resolveCodexThreadRolloutPath(thread) {
2540
2412
  const rolloutPath = thread.path?.trim();
@@ -2542,7 +2414,7 @@ function resolveCodexThreadRolloutPath(thread) {
2542
2414
  return rolloutPath;
2543
2415
  }
2544
2416
  async function resumeExistingCodexThread(params, context) {
2545
- const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
2417
+ const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
2546
2418
  let resumeReservation;
2547
2419
  try {
2548
2420
  const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
@@ -2553,7 +2425,7 @@ async function resumeExistingCodexThread(params, context) {
2553
2425
  configPatch: params.finalConfigPatch,
2554
2426
  nativeHookRelayGeneration: params.nativeHookRelayGeneration
2555
2427
  };
2556
- const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
2428
+ const pluginAppsConfigPatch = context.prebuiltPluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
2557
2429
  const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
2558
2430
  const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
2559
2431
  threadId: resumeBinding.threadId,
@@ -2570,7 +2442,7 @@ async function resumeExistingCodexThread(params, context) {
2570
2442
  nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2571
2443
  webSearchAllowed: params.webSearchAllowed,
2572
2444
  hostSystemAgentActive,
2573
- ringZeroInheritedMcpServerNames
2445
+ restrictedToolSurfaceInheritedMcpServerNames
2574
2446
  }));
2575
2447
  const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
2576
2448
  throwIfAborted();
@@ -2589,16 +2461,17 @@ async function resumeExistingCodexThread(params, context) {
2589
2461
  request: resumeParams,
2590
2462
  signal: params.signal
2591
2463
  }));
2592
- if (ringZeroActive) try {
2593
- await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
2464
+ if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
2465
+ await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
2594
2466
  } catch (error) {
2595
2467
  await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2596
- throw new CodexRingZeroAttestationError(error);
2468
+ throw new CodexRestrictedToolSurfaceAttestationError(error);
2597
2469
  }
2598
2470
  throwIfAborted();
2599
2471
  const boundAuthProfileId = authProfileId;
2600
2472
  const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
2601
2473
  const resumePatch = {
2474
+ clientId: resolveCodexAppServerClientInstanceId(params.client),
2602
2475
  cwd: params.cwd,
2603
2476
  rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
2604
2477
  authProfileId: boundAuthProfileId,
@@ -2611,15 +2484,17 @@ async function resumeExistingCodexThread(params, context) {
2611
2484
  nativeSkillIsolationFingerprint,
2612
2485
  userMcpServersFingerprint,
2613
2486
  mcpServersFingerprint: nextMcpServersFingerprint,
2487
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
2614
2488
  ringZeroConfigFingerprint,
2615
2489
  ringZeroClientInstanceId,
2490
+ nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
2616
2491
  networkProxyProfileName: params.appServer.networkProxy?.profileName,
2617
2492
  networkProxyConfigFingerprint,
2618
2493
  nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
2619
2494
  appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
2620
- pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
2621
- pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
2622
- pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
2495
+ pluginAppsFingerprint: context.prebuiltPluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
2496
+ pluginAppsInputFingerprint: context.prebuiltPluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
2497
+ pluginAppPolicyContext: context.prebuiltPluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
2623
2498
  contextEngine: contextEngineBinding,
2624
2499
  environmentSelectionFingerprint
2625
2500
  };
@@ -2665,8 +2540,8 @@ async function resumeExistingCodexThread(params, context) {
2665
2540
  } catch (error) {
2666
2541
  resumeReservation?.release();
2667
2542
  if (isCodexAppServerStartSelectionChangedError(error)) throw error;
2668
- if (error instanceof CodexRingZeroAttestationError) {
2669
- await clearCurrentBinding("retiring a failed ring-zero thread attestation");
2543
+ if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
2544
+ await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
2670
2545
  throw error;
2671
2546
  }
2672
2547
  if (error instanceof CodexAdoptedThreadActiveError) throw error;
@@ -2683,7 +2558,7 @@ async function resumeExistingCodexThread(params, context) {
2683
2558
  }
2684
2559
  async function startFreshCodexThread(params, context) {
2685
2560
  const clientId = resolveCodexAppServerClientInstanceId(params.client);
2686
- const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding } = context;
2561
+ const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding, replacementPredecessor } = context;
2687
2562
  const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2688
2563
  const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2689
2564
  configPatch: params.finalConfigPatch,
@@ -2704,7 +2579,7 @@ async function startFreshCodexThread(params, context) {
2704
2579
  model: startModelSelection.model,
2705
2580
  modelProvider: startModelProvider,
2706
2581
  hostSystemAgentActive,
2707
- ringZeroInheritedMcpServerNames
2582
+ restrictedToolSurfaceInheritedMcpServerNames
2708
2583
  }));
2709
2584
  const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
2710
2585
  const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
@@ -2735,13 +2610,27 @@ async function startFreshCodexThread(params, context) {
2735
2610
  throw error;
2736
2611
  }
2737
2612
  const rolloutPath = resolveCodexThreadRolloutPath(response.thread);
2738
- if (ringZeroActive) try {
2739
- await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
2613
+ if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
2614
+ await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, startParams.config, params.signal));
2740
2615
  } catch (error) {
2741
2616
  await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2742
2617
  throw error;
2743
2618
  }
2744
- throwIfAborted();
2619
+ try {
2620
+ throwIfAborted();
2621
+ } catch (error) {
2622
+ if (replacementPredecessor) {
2623
+ if (!await discardUnattestedCodexPluginThread({
2624
+ client: params.client,
2625
+ threadId: response.thread.id,
2626
+ ephemeral: startParams.ephemeral === true
2627
+ })) {
2628
+ await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2629
+ throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after an aborted binding replacement", { cause: error });
2630
+ }
2631
+ }
2632
+ throw error;
2633
+ }
2745
2634
  const modelProvider = resolveCodexAppServerModelProvider({
2746
2635
  provider: params.params.provider,
2747
2636
  authProfileId: params.params.authProfileId,
@@ -2752,36 +2641,63 @@ async function startFreshCodexThread(params, context) {
2752
2641
  const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
2753
2642
  const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2754
2643
  if (!preserveExistingBinding) {
2755
- if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
2756
- kind: "set",
2757
- if: { kind: "absent" },
2758
- binding: {
2644
+ const nextBinding = {
2645
+ threadId: response.thread.id,
2646
+ ...clientId ? { clientId } : {},
2647
+ cwd: params.cwd,
2648
+ ...rolloutPath ? { rolloutPath } : {},
2649
+ authProfileId: params.params.authProfileId,
2650
+ model: response.model ?? startParams.model ?? params.params.modelId,
2651
+ modelProvider: bindingModelProvider,
2652
+ dynamicToolsFingerprint,
2653
+ dynamicToolsContainDeferred,
2654
+ webSearchThreadConfigFingerprint,
2655
+ nativeSkillIsolationFingerprint,
2656
+ userMcpServersFingerprint,
2657
+ mcpServersFingerprint: nextMcpServersFingerprint,
2658
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
2659
+ ringZeroConfigFingerprint,
2660
+ ringZeroClientInstanceId,
2661
+ nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
2662
+ networkProxyProfileName: params.appServer.networkProxy?.profileName,
2663
+ networkProxyConfigFingerprint,
2664
+ nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2665
+ appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2666
+ pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2667
+ pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2668
+ pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2669
+ contextEngine: contextEngineBinding,
2670
+ environmentSelectionFingerprint
2671
+ };
2672
+ const cleanupUncommittedSuccessor = async (cause) => {
2673
+ if (!await discardUnattestedCodexPluginThread({
2674
+ client: params.client,
2759
2675
  threadId: response.thread.id,
2760
- ...clientId ? { clientId } : {},
2761
- cwd: params.cwd,
2762
- ...rolloutPath ? { rolloutPath } : {},
2763
- authProfileId: params.params.authProfileId,
2764
- model: response.model ?? startParams.model ?? params.params.modelId,
2765
- modelProvider: bindingModelProvider,
2766
- dynamicToolsFingerprint,
2767
- dynamicToolsContainDeferred,
2768
- webSearchThreadConfigFingerprint,
2769
- nativeSkillIsolationFingerprint,
2770
- userMcpServersFingerprint,
2771
- mcpServersFingerprint: nextMcpServersFingerprint,
2772
- ringZeroConfigFingerprint,
2773
- ringZeroClientInstanceId,
2774
- networkProxyProfileName: params.appServer.networkProxy?.profileName,
2775
- networkProxyConfigFingerprint,
2776
- nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
2777
- appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
2778
- pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
2779
- pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
2780
- pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2781
- contextEngine: contextEngineBinding,
2782
- environmentSelectionFingerprint
2676
+ ephemeral: startParams.ephemeral === true
2677
+ })) {
2678
+ await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2679
+ throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
2783
2680
  }
2784
- }))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
2681
+ };
2682
+ let committed;
2683
+ try {
2684
+ committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
2685
+ kind: "replace-thread",
2686
+ expectedThreadId: replacementPredecessor.threadId,
2687
+ binding: nextBinding
2688
+ } : {
2689
+ kind: "set",
2690
+ if: { kind: "absent" },
2691
+ binding: nextBinding
2692
+ }));
2693
+ } catch (error) {
2694
+ if (replacementPredecessor) await cleanupUncommittedSuccessor(error);
2695
+ throw error;
2696
+ }
2697
+ if (!committed) {
2698
+ if (replacementPredecessor) await cleanupUncommittedSuccessor();
2699
+ throw new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread");
2700
+ }
2785
2701
  if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
2786
2702
  sessionId: params.params.sessionId,
2787
2703
  sessionKey: params.params.sessionKey,
@@ -2813,6 +2729,7 @@ async function startFreshCodexThread(params, context) {
2813
2729
  nativeSkillIsolationFingerprint,
2814
2730
  userMcpServersFingerprint,
2815
2731
  mcpServersFingerprint: nextMcpServersFingerprint,
2732
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
2816
2733
  ringZeroConfigFingerprint,
2817
2734
  ringZeroClientInstanceId,
2818
2735
  networkProxyProfileName: params.appServer.networkProxy?.profileName,
@@ -2979,13 +2896,15 @@ async function prepareCodexThreadLifecyclePreflight(params) {
2979
2896
  const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
2980
2897
  const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
2981
2898
  const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
2982
- if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
2983
- const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
2984
- if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
2899
+ const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
2900
+ const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
2901
+ if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
2902
+ const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
2903
+ if (restrictedToolSurface) await lifecycleTiming.measure("restricted-tool-surface-config-requirements-read", () => assertCodexRestrictedToolSurfaceHasNoManagedHooks(params.client, params.signal));
2985
2904
  const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
2986
2905
  version: 1,
2987
2906
  baseInstructions: "",
2988
- config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
2907
+ config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
2989
2908
  }) : void 0;
2990
2909
  return {
2991
2910
  contextEngineBinding,
@@ -3002,7 +2921,8 @@ async function prepareCodexThreadLifecyclePreflight(params) {
3002
2921
  ringZeroActive,
3003
2922
  ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
3004
2923
  ringZeroConfigFingerprint,
3005
- ringZeroInheritedMcpServerNames,
2924
+ restrictedToolSurface,
2925
+ restrictedToolSurfaceInheritedMcpServerNames,
3006
2926
  userMcpServersConfigPatch,
3007
2927
  userMcpServersFingerprint,
3008
2928
  webSearchThreadConfigFingerprint
@@ -3034,8 +2954,8 @@ async function releaseCodexRetainedLiveThread(options) {
3034
2954
  }
3035
2955
  /** Reuses one safely owned, fully matching subscription on its original client. */
3036
2956
  async function tryReuseCodexLiveThread(options) {
3037
- const { params, binding, bindingIdentity, clientId, contextEngineBinding, dynamicToolsFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, releaseConsumedThread, ringZeroActive, ringZeroInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
3038
- if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive || contextEngineBinding) return {};
2957
+ const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, releaseConsumedThread, ringZeroActive, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
2958
+ if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive) return {};
3039
2959
  const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
3040
2960
  action: "resume",
3041
2961
  binding
@@ -3061,7 +2981,7 @@ async function tryReuseCodexLiveThread(options) {
3061
2981
  nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
3062
2982
  webSearchAllowed: params.webSearchAllowed,
3063
2983
  hostSystemAgentActive,
3064
- ringZeroInheritedMcpServerNames
2984
+ restrictedToolSurfaceInheritedMcpServerNames
3065
2985
  }));
3066
2986
  const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
3067
2987
  ...resumeParams,
@@ -3070,13 +2990,24 @@ async function tryReuseCodexLiveThread(options) {
3070
2990
  modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
3071
2991
  requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
3072
2992
  }, resumeAuthProfileId, dynamicToolsFingerprint);
3073
- if (!await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint)) return { prebuiltFinalConfigPatch };
2993
+ const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint);
2994
+ if (!retainedThread) {
2995
+ const incompatibleOwnership = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
2996
+ if (incompatibleOwnership) await incompatibleOwnership.release(binding.threadId);
2997
+ return { prebuiltFinalConfigPatch };
2998
+ }
3074
2999
  try {
3075
3000
  const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
3001
+ const model = startModelSelection.model;
3076
3002
  if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
3077
3003
  kind: "patch",
3078
3004
  threadId: binding.threadId,
3079
- patch: { nativeHookRelayGeneration }
3005
+ patch: {
3006
+ cwd: params.cwd,
3007
+ model,
3008
+ nativeHookRelayGeneration,
3009
+ environmentSelectionFingerprint
3010
+ }
3080
3011
  }))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
3081
3012
  throwIfAborted();
3082
3013
  lifecycleTiming.mark("thread-ready");
@@ -3090,8 +3021,13 @@ async function tryReuseCodexLiveThread(options) {
3090
3021
  return {
3091
3022
  binding: {
3092
3023
  ...binding,
3024
+ cwd: params.cwd,
3025
+ model,
3093
3026
  nativeHookRelayGeneration,
3027
+ environmentSelectionFingerprint,
3094
3028
  liveThreadConfigFingerprint,
3029
+ liveThreadOwnership: retainedThread,
3030
+ ...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
3095
3031
  lifecycle: { action: "resumed" }
3096
3032
  },
3097
3033
  prebuiltFinalConfigPatch
@@ -3143,6 +3079,7 @@ async function materializePendingSupervisionBranch(params) {
3143
3079
  pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId]);
3144
3080
  params.throwIfAborted();
3145
3081
  const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
3082
+ if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
3146
3083
  const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
3147
3084
  const nativeModelProvider = requireNativeSupervisionModelProvider({
3148
3085
  responseModelProvider: probeResponse.modelProvider,
@@ -3163,7 +3100,9 @@ async function materializePendingSupervisionBranch(params) {
3163
3100
  webSearchAllowed: params.webSearchAllowed,
3164
3101
  environmentSelection: params.environmentSelection,
3165
3102
  model: nativeModel,
3166
- modelProvider: nativeModelProvider
3103
+ modelProvider: nativeModelProvider,
3104
+ hostSystemAgentActive: params.hostSystemAgentActive,
3105
+ restrictedToolSurfaceInheritedMcpServerNames: params.restrictedToolSurfaceInheritedMcpServerNames
3167
3106
  });
3168
3107
  assertExactSupervisionModelSelection(startParams, {
3169
3108
  model: nativeModel,
@@ -3191,6 +3130,7 @@ async function materializePendingSupervisionBranch(params) {
3191
3130
  modelProvider: nativeModelProvider,
3192
3131
  operation: "thread/start response"
3193
3132
  });
3133
+ if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, finalThreadId, startParams.config, params.signal));
3194
3134
  if (params.provisionalAppIds?.length) try {
3195
3135
  await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
3196
3136
  client: params.client,
@@ -3316,7 +3256,9 @@ function buildPendingSupervisionProbeForkParams(params, pending) {
3316
3256
  nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3317
3257
  nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
3318
3258
  webSearchAllowed: params.webSearchAllowed,
3319
- appServer: params.appServer
3259
+ appServer: params.appServer,
3260
+ hostSystemAgentActive: params.hostSystemAgentActive,
3261
+ restrictedToolSurfaceInheritedMcpServerNames: params.restrictedToolSurfaceInheritedMcpServerNames
3320
3262
  });
3321
3263
  return {
3322
3264
  threadId: pending.sourceThreadId,
@@ -3453,9 +3395,11 @@ async function startOrResumeThread(params) {
3453
3395
  config: params.params.config
3454
3396
  });
3455
3397
  return await params.bindingStore.withLease(bindingIdentity, async () => {
3456
- const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, ringZeroInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = await prepareCodexThreadLifecyclePreflight(params);
3398
+ const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = await prepareCodexThreadLifecyclePreflight(params);
3457
3399
  let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
3400
+ let replacementPredecessor;
3458
3401
  const initialBoundThreadId = binding?.threadId;
3402
+ const initialBoundClientId = binding?.clientId;
3459
3403
  const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
3460
3404
  authProfileId,
3461
3405
  modelProvider,
@@ -3464,12 +3408,28 @@ async function startOrResumeThread(params) {
3464
3408
  config: params.params.config
3465
3409
  });
3466
3410
  const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
3467
- const releaseRetainedThread = (threadId) => releaseCodexRetainedLiveThread({
3468
- client: params.client,
3469
- abandonClient: params.abandonClient,
3470
- lifecycleTiming,
3471
- threadId
3472
- });
3411
+ const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId) => {
3412
+ if (ownerClientId && ownerClientId !== clientId) {
3413
+ const previousClient = retainSharedCodexAppServerClientByInstanceId(ownerClientId);
3414
+ if (!previousClient) return;
3415
+ try {
3416
+ await releaseCodexRetainedLiveThread({
3417
+ client: previousClient.client,
3418
+ lifecycleTiming,
3419
+ threadId
3420
+ });
3421
+ } finally {
3422
+ previousClient.release();
3423
+ }
3424
+ return;
3425
+ }
3426
+ await releaseCodexRetainedLiveThread({
3427
+ client: params.client,
3428
+ abandonClient: params.abandonClient,
3429
+ lifecycleTiming,
3430
+ threadId
3431
+ });
3432
+ };
3473
3433
  if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
3474
3434
  if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
3475
3435
  bindingStore: params.bindingStore,
@@ -3502,6 +3462,9 @@ async function startOrResumeThread(params) {
3502
3462
  nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3503
3463
  nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
3504
3464
  webSearchAllowed: params.webSearchAllowed,
3465
+ hostSystemAgentActive,
3466
+ restrictedToolSurface,
3467
+ restrictedToolSurfaceInheritedMcpServerNames,
3505
3468
  environmentSelection: params.environmentSelection,
3506
3469
  provisionalAppIds: pluginThreadConfig?.provisionalAppIds,
3507
3470
  signal: params.signal,
@@ -3519,6 +3482,7 @@ async function startOrResumeThread(params) {
3519
3482
  nativeSkillIsolationFingerprint,
3520
3483
  userMcpServersFingerprint,
3521
3484
  mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
3485
+ configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
3522
3486
  networkProxyProfileName: params.appServer.networkProxy?.profileName,
3523
3487
  networkProxyConfigFingerprint,
3524
3488
  nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
@@ -3542,6 +3506,7 @@ async function startOrResumeThread(params) {
3542
3506
  })) throw new CodexThreadBindingConflictError(current.threadId, operation);
3543
3507
  binding = void 0;
3544
3508
  };
3509
+ if (binding?.threadId && !restrictedToolSurface && binding.nativeToolPolicyRestricted === true) await clearCurrentBinding("rotating a host-policy-restricted thread binding");
3545
3510
  if (binding?.threadId && binding.nativeSkillIsolationFingerprint !== nativeSkillIsolationFingerprint) {
3546
3511
  embeddedAgentLog.debug("codex app-server native skill isolation changed; starting a new thread", { threadId: binding.threadId });
3547
3512
  await clearCurrentBinding("rotating stale native skill isolation");
@@ -3594,6 +3559,14 @@ async function startOrResumeThread(params) {
3594
3559
  const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
3595
3560
  const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
3596
3561
  const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
3562
+ if (binding?.threadId && (params.configuredMcpOwnershipVersion === 1 && (binding.configuredMcpOwnershipVersion !== 1 || binding.dynamicToolsFingerprint === void 0 || binding.mcpServersFingerprint !== void 0 || binding.userMcpServersFingerprint !== void 0) || params.configuredMcpOwnershipVersion !== 1 && binding.configuredMcpOwnershipVersion === 1) && binding?.threadId) {
3563
+ const predecessorBinding = binding;
3564
+ assertCodexBindingMayBeReplaced(predecessorBinding, "changing configured MCP ownership");
3565
+ embeddedAgentLog.debug("codex app-server configured MCP ownership changed; starting a new thread", { threadId: predecessorBinding.threadId });
3566
+ replacementPredecessor = predecessorBinding;
3567
+ binding = void 0;
3568
+ preserveExistingBinding = false;
3569
+ }
3597
3570
  if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
3598
3571
  assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
3599
3572
  if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
@@ -3609,7 +3582,7 @@ async function startOrResumeThread(params) {
3609
3582
  if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
3610
3583
  assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
3611
3584
  if (!ringZeroActive && transientWebSearchRestriction) {
3612
- embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
3585
+ embeddedAgentLog.debug("codex app-server tool surface restricted for turn; starting transient thread", { threadId: binding.threadId });
3613
3586
  preserveExistingBinding = true;
3614
3587
  } else {
3615
3588
  embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
@@ -3655,11 +3628,6 @@ async function startOrResumeThread(params) {
3655
3628
  await clearCurrentBinding("rotating a stale thread binding");
3656
3629
  binding = void 0;
3657
3630
  }
3658
- if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
3659
- embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
3660
- await clearCurrentBinding("rotating a stale thread binding");
3661
- binding = void 0;
3662
- }
3663
3631
  if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
3664
3632
  embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
3665
3633
  await clearCurrentBinding("rotating a stale thread binding");
@@ -3673,13 +3641,15 @@ async function startOrResumeThread(params) {
3673
3641
  currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
3674
3642
  hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
3675
3643
  });
3676
- if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
3644
+ if (!pluginBindingStale && (params.pluginThreadConfig?.requiresCurrentPolicyCheck || shouldRecheckRecoverablePluginBinding({
3677
3645
  binding,
3678
3646
  pluginThreadConfig: params.pluginThreadConfig
3679
- })) try {
3680
- prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build());
3647
+ }))) try {
3648
+ const bindingThreadId = binding.threadId;
3649
+ prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: bindingThreadId }));
3681
3650
  pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
3682
3651
  } catch (error) {
3652
+ if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
3683
3653
  embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
3684
3654
  error,
3685
3655
  threadId: binding.threadId
@@ -3737,8 +3707,8 @@ async function startOrResumeThread(params) {
3737
3707
  binding,
3738
3708
  bindingIdentity,
3739
3709
  clientId,
3740
- contextEngineBinding,
3741
3710
  dynamicToolsFingerprint,
3711
+ environmentSelectionFingerprint,
3742
3712
  hostSystemAgentActive,
3743
3713
  lifecycleTiming,
3744
3714
  nativeSkillIsolation,
@@ -3750,7 +3720,7 @@ async function startOrResumeThread(params) {
3750
3720
  cause
3751
3721
  }),
3752
3722
  ringZeroActive,
3753
- ringZeroInheritedMcpServerNames,
3723
+ restrictedToolSurfaceInheritedMcpServerNames,
3754
3724
  startModelProvider,
3755
3725
  startModelSelection,
3756
3726
  throwIfAborted,
@@ -3776,18 +3746,20 @@ async function startOrResumeThread(params) {
3776
3746
  environmentSelectionFingerprint,
3777
3747
  hostSystemAgentActive,
3778
3748
  ringZeroActive,
3779
- ringZeroInheritedMcpServerNames,
3749
+ restrictedToolSurface,
3750
+ restrictedToolSurfaceInheritedMcpServerNames,
3780
3751
  nativeSkillIsolation,
3781
3752
  lifecycleTiming,
3782
3753
  normalizeBindingModelProvider,
3783
3754
  throwIfAborted,
3784
3755
  clearCurrentBinding,
3785
- prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch
3756
+ prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
3757
+ prebuiltPluginThreadConfig
3786
3758
  });
3787
3759
  if (resumed) return resumed;
3788
3760
  }
3789
- if (initialBoundThreadId) await releaseRetainedThread(initialBoundThreadId);
3790
- return await startFreshCodexThread(params, {
3761
+ if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
3762
+ const started = await startFreshCodexThread(params, {
3791
3763
  bindingIdentity,
3792
3764
  startModelSelection,
3793
3765
  startModelProvider,
@@ -3804,85 +3776,27 @@ async function startOrResumeThread(params) {
3804
3776
  environmentSelectionFingerprint,
3805
3777
  hostSystemAgentActive,
3806
3778
  ringZeroActive,
3807
- ringZeroInheritedMcpServerNames,
3779
+ restrictedToolSurface,
3780
+ restrictedToolSurfaceInheritedMcpServerNames,
3808
3781
  nativeSkillIsolation,
3809
3782
  lifecycleTiming,
3810
3783
  normalizeBindingModelProvider,
3811
3784
  throwIfAborted,
3812
3785
  prebuiltPluginThreadConfig,
3813
3786
  preserveExistingBinding,
3814
- rotatedContextEngineBinding
3787
+ rotatedContextEngineBinding,
3788
+ replacementPredecessor
3815
3789
  });
3790
+ if (replacementPredecessor) await releaseRetainedThread(replacementPredecessor.threadId, replacementPredecessor.clientId);
3791
+ return started;
3816
3792
  });
3817
3793
  }
3818
3794
  //#endregion
3819
- //#region extensions/codex/src/app-server/image-payload-sanitizer.ts
3820
- /**
3821
- * Sanitizes inline image payloads mirrored through Codex history so invalid
3822
- * base64 data becomes readable text instead of poisoning replayed transcripts.
3823
- */
3824
- const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
3825
- /** Validates and normalizes an inline image data URL for Codex history payloads. */
3826
- function sanitizeInlineImageDataUrl$1(imageUrl) {
3827
- return sanitizeInlineImageDataUrl(imageUrl);
3828
- }
3829
- /** Builds the replacement text inserted when an inline image payload is invalid. */
3830
- function invalidInlineImageText(label) {
3831
- return `[${label}] ${IMAGE_OMITTED_TEXT}`;
3832
- }
3833
- function sanitizeImageContentRecord(record, label) {
3834
- if (record.type === "image" && typeof record.data === "string") {
3835
- const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
3836
- const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
3837
- if (!imageUrl) return {
3838
- type: "text",
3839
- text: invalidInlineImageText(label)
3840
- };
3841
- const commaIndex = imageUrl.indexOf(",");
3842
- const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
3843
- return {
3844
- ...record,
3845
- mimeType: mime,
3846
- data: imageUrl.slice(commaIndex + 1)
3847
- };
3848
- }
3849
- if (record.type === "inputImage" && typeof record.imageUrl === "string") {
3850
- const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
3851
- return imageUrl ? {
3852
- ...record,
3853
- imageUrl
3854
- } : {
3855
- type: "inputText",
3856
- text: invalidInlineImageText(label)
3857
- };
3858
- }
3859
- if (record.type === "input_image" && typeof record.image_url === "string") {
3860
- const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
3861
- return imageUrl ? {
3862
- ...record,
3863
- image_url: imageUrl
3864
- } : {
3865
- type: "input_text",
3866
- text: invalidInlineImageText(label)
3867
- };
3868
- }
3869
- }
3870
- /** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
3871
- function sanitizeCodexHistoryImagePayloads(value, label) {
3872
- if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
3873
- if (!isRecord(value)) return value;
3874
- const imageRecord = sanitizeImageContentRecord(value, label);
3875
- if (imageRecord) return imageRecord;
3876
- const next = {};
3877
- for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
3878
- return next;
3879
- }
3880
- //#endregion
3881
3795
  //#region extensions/codex/src/app-server/user-input.ts
3882
3796
  /** Builds ordered Codex user input for both new turns and same-turn steering. */
3883
3797
  function buildCodexUserInput(text, images) {
3884
3798
  const imageInputs = (images ?? []).map((image) => {
3885
- const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
3799
+ const imageUrl = sanitizeInlineImageDataUrl(`data:${image.mimeType};base64,${image.data}`);
3886
3800
  return imageUrl ? {
3887
3801
  type: "image",
3888
3802
  url: imageUrl
@@ -3900,6 +3814,27 @@ function buildCodexUserInput(text, images) {
3900
3814
  }
3901
3815
  //#endregion
3902
3816
  //#region extensions/codex/src/app-server/turn-params.ts
3817
+ const CODEX_CURRENT_SENDER_FIELD_MAX_CHARS = 256;
3818
+ function buildCodexCurrentSenderContextValue(params) {
3819
+ const metadata = asOptionalRecord(asOptionalRecord(params.userTurnTranscriptRecorder?.message)?.["__openclaw"]);
3820
+ const recorded = [
3821
+ normalizeOptionalString(metadata?.["senderId"]),
3822
+ normalizeOptionalString(metadata?.["senderName"]),
3823
+ normalizeOptionalString(metadata?.["senderUsername"])
3824
+ ];
3825
+ const [id, name, username] = recorded.some(Boolean) ? recorded : [
3826
+ normalizeOptionalString(params.senderId),
3827
+ normalizeOptionalString(params.senderName),
3828
+ normalizeOptionalString(params.senderUsername)
3829
+ ];
3830
+ if (!id && !name && !username) return;
3831
+ const bound = (value) => truncateUtf16Safe(value, CODEX_CURRENT_SENDER_FIELD_MAX_CHARS);
3832
+ return JSON.stringify({ sender: {
3833
+ ...id ? { id: bound(id) } : {},
3834
+ ...name ? { name: bound(name) } : {},
3835
+ ...username ? { username: bound(username) } : {}
3836
+ } });
3837
+ }
3903
3838
  function buildTurnStartParams(params, options) {
3904
3839
  const modelSelection = options.preserveNativeTurnSettings ? void 0 : resolveCodexAppServerRequestModelSelection({
3905
3840
  model: options.model ?? params.modelId,
@@ -3910,9 +3845,15 @@ function buildTurnStartParams(params, options) {
3910
3845
  config: params.config
3911
3846
  });
3912
3847
  const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
3848
+ const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
3849
+ const additionalContext = currentSenderContext ? { openclaw_current_sender: {
3850
+ kind: "untrusted",
3851
+ value: currentSenderContext
3852
+ } } : void 0;
3913
3853
  return {
3914
3854
  threadId: options.threadId,
3915
- input: buildCodexUserInput(options.promptText ?? params.prompt, params.images),
3855
+ input: [...buildCodexUserInput(options.promptText ?? params.prompt, params.images), ...options.explicitSkillInputs ?? []],
3856
+ ...additionalContext ? { additionalContext } : {},
3916
3857
  cwd: options.cwd,
3917
3858
  approvalPolicy: options.appServer.approvalPolicy,
3918
3859
  approvalsReviewer: options.appServer.approvalsReviewer,
@@ -3921,7 +3862,7 @@ function buildTurnStartParams(params, options) {
3921
3862
  model: modelSelection.model,
3922
3863
  personality: CODEX_NATIVE_PERSONALITY_NONE
3923
3864
  } : {},
3924
- ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
3865
+ ...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : options.clearInheritedServiceTier ? { serviceTier: null } : {},
3925
3866
  ...modelSelection ? { effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)) } : {},
3926
3867
  ...options.environmentSelection ? { environments: options.environmentSelection } : {},
3927
3868
  ...modelSelection ? { collaborationMode: buildTurnCollaborationMode(params, {
@@ -3946,7 +3887,7 @@ function buildTurnCollaborationMode(params, options = {}) {
3946
3887
  function buildTurnScopedCollaborationInstructions(params, options = {}) {
3947
3888
  const contextInstructions = joinPresentSections(options.turnScopedDeveloperInstructions, options.memoryCollaborationInstructions, options.skillsCollaborationInstructions);
3948
3889
  if (params.trigger === "cron") return joinPresentSections(buildCronCollaborationInstructions(), contextInstructions);
3949
- if (params.trigger === "heartbeat" && params.bootstrapContextRunKind !== "commitment-only") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions);
3890
+ if (params.trigger === "heartbeat") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions);
3950
3891
  if (contextInstructions?.trim()) return joinPresentSections(buildDefaultCollaborationInstructions(), contextInstructions);
3951
3892
  return null;
3952
3893
  }
@@ -3984,4 +3925,4 @@ function joinPresentSections(...sections) {
3984
3925
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
3985
3926
  }
3986
3927
  //#endregion
3987
- export { readCodexErrorNotificationMessage as $, resolveCodexDynamicToolsLoading as A, resolveCodexContextEngineProjectionReserveTokens as B, resolveReasoningEffort as C, isForcedPrivateQaCodexRuntime as D, filterCodexDynamicToolsWithOpenClawShell as E, buildContextEngineBinding as F, mergeCodexThreadConfigs as G, buildCodexPluginThreadConfig as H, isContextEngineBindingCompatible as I, isOpenAiCuratedMarketplace as J, shouldBuildCodexPluginThreadConfig as K, fitCodexProjectedContextForTurnStart as L, areCodexDynamicToolFingerprintsCompatible as M, codexDynamicToolsFingerprint as N, isSystemAgentOnlyCodexDynamicToolAllowlist as O, codexLegacyDynamicToolsFingerprint as P, normalizeNonEmptyString as Q, projectContextEngineAssemblyForCodex as R, resolveCodexBindingModelProviderFallback as S, filterCodexDynamicTools as T, buildCodexPluginThreadConfigInputFingerprint as U, buildCodexPluginAppsConfigPatchFromPolicyContext as V, buildCodexPluginThreadConfigTimeoutFallback as W, resolveRecoverableCodexPluginConfigKeys as X, pluginReadParams as Y, extractRawAssistantText as Z, buildDeveloperInstructions as _, unsubscribeCodexThreadBestEffort as _t, sanitizeCodexHistoryImagePayloads as a, readNonNegativeInteger as at, resolveCodexAppServerRequestModelSelection as b, isCodexAppServerProfilerEnabled as c, readString as ct, attestCodexRingZeroThreadHasNoMcpServers as d, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as dt, readHookOutputEntries as et, buildCodexRingZeroThreadConfigPatch as f, CodexAppServerUnsafeSubscriptionError as ft, resolveCodexWebSearchPlan as g, retireCodexAppServerClientAfterTimedOutTurn as gt, buildCodexNativeWebSearchThreadConfig as h, interruptCodexTurnBestEffort as ht, invalidInlineImageText as i, readNonEmptyStringArray as it, resolveCodexDynamicToolsLoadingForRuntime as j, normalizeCodexDynamicToolName as k, resumeCodexAppServerThread as l, splitPlanText as lt, readCodexInheritedMcpServerNames as m, interruptCodexTurnAndWaitBestEffort as mt, buildTurnStartParams as n, readItemString as nt, sanitizeInlineImageDataUrl$1 as o, readNullableString as ot, buildCodexRuntimeThreadConfig as p, closeCodexStartupClientBestEffort as pt, ensureCodexPluginActivation as q, buildCodexUserInput as r, readNonEmptyString as rt, startOrResumeThread as s, readNumber as st, buildTurnCollaborationMode as t, readItem as tt, assertCodexRingZeroHasNoManagedHooks as u, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as ut, CODEX_NATIVE_PERSONALITY_NONE as v, readCodexSupportedReasoningEfforts as w, resolveCodexAppServerThreadModelSelection as x, resolveCodexAppServerModelProvider as y, resolveCodexContextEngineProjectionMaxChars as z };
3928
+ export { resolveCodexDynamicToolsLoadingForRuntime as A, buildCodexPluginThreadConfigInputFingerprint as B, codexLegacyDynamicToolsFingerprint as C, isSystemAgentOnlyCodexDynamicToolAllowlist as D, isForcedPrivateQaCodexRuntime as E, projectContextEngineAssemblyForCodex as F, shouldBuildCodexPluginThreadConfig as G, buildDisabledAppsConfigPatch as H, resolveCodexContextEngineProjectionMaxChars as I, isOpenAiCuratedMarketplace as J, ensureCodexPluginActivation as K, resolveCodexContextEngineProjectionReserveTokens as L, isContextEngineBindingCompatible as M, fitCodexProjectedContextForTurnStart as N, normalizeCodexDynamicToolName as O, neutralizeCodexExplicitMentionSigils as P, buildCodexPluginAppsConfigPatchFromPolicyContext as R, codexDynamicToolsFingerprint as S, filterCodexDynamicToolsForDisabledNativeSurface as T, buildPluginAppPolicyContext as U, buildCodexPluginThreadConfigTimeoutFallback as V, mergeCodexThreadConfigs as W, pluginReadParams as X, isOpenAiCuratedMarketplaceName as Y, resolveRecoverableCodexPluginConfigKeys as Z, resolveCodexAppServerThreadModelSelection as _, isCodexAppServerProfilerEnabled as a, readCodexSupportedReasoningEfforts as b, buildCodexRingZeroThreadConfigPatch as c, buildCodexNativeWebSearchThreadConfig as d, resolveCodexWebSearchPlan as f, resolveCodexAppServerRequestModelSelection as g, resolveCodexAppServerModelProvider as h, startOrResumeThread as i, buildContextEngineBinding as j, resolveCodexDynamicToolsLoading as k, buildCodexRuntimeThreadConfig as l, CODEX_NATIVE_PERSONALITY_NONE as m, buildTurnStartParams as n, assertCodexRestrictedToolSurfaceHasNoManagedHooks as o, buildDeveloperInstructions as p, refreshCodexPluginRuntimeState as q, buildCodexUserInput as r, attestCodexRestrictedToolSurfaceMcpServersDisabled as s, buildTurnCollaborationMode as t, readCodexInheritedMcpServerNames as u, resolveCodexBindingModelProviderFallback as v, filterCodexDynamicTools as w, areCodexDynamicToolFingerprintsCompatible as x, resolveReasoningEffort as y, buildCodexPluginThreadConfig as z };