@openclaw/codex 2026.7.2-beta.5 → 2026.7.2-beta.7

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 (42) hide show
  1. package/dist/{app-server-policy-BmDgseo_.js → app-server-policy-BSaO2JIT.js} +1 -1
  2. package/dist/{attempt-notifications-CMR2_fmR.js → attempt-notifications-AP-a-Q5k.js} +1 -1
  3. package/dist/{bounded-turn-FEbUXoYT.js → bounded-turn-D2ZAgZVz.js} +29 -22
  4. package/dist/{capabilities-BHRSI6Cn.js → capabilities-BFDTVYr4.js} +1 -1
  5. package/dist/cli-metadata.js +1 -1
  6. package/dist/{command-formatters-ico4ZFnk.js → command-formatters-C336eRVS.js} +2 -2
  7. package/dist/{command-handlers-CRmnwI2N.js → command-handlers-DAHUSry9.js} +12 -12
  8. package/dist/{compact-dEN49UH6.js → compact-D_uvHurS.js} +5 -5
  9. package/dist/{computer-use-BjGylO-E.js → computer-use-CYEc8U3w.js} +111 -113
  10. package/dist/{conversation-control-DE7d9DOj.js → conversation-control-xsIeXbo4.js} +6 -6
  11. package/dist/doctor-contract-api.js +1 -1
  12. package/dist/{dynamic-tools-LIt9ojep.js → dynamic-tools-D2gYkSBO.js} +438 -51
  13. package/dist/effective-mcp-catalog-Bbd98Inw.js +114 -0
  14. package/dist/{event-projector-usage-tvgb1NCX.js → event-projector-usage-Xp4AlR2k.js} +20 -3
  15. package/dist/harness.js +13 -9
  16. package/dist/index.js +131 -108
  17. package/dist/media-understanding-provider.js +1 -1
  18. package/dist/{models-BA01NEez.js → models-BBMKU4jA.js} +2 -2
  19. package/dist/{notification-correlation-Bj_Ak_jI.js → notification-correlation-CU6_AnOb.js} +3 -3
  20. package/dist/{plugin-app-cache-key-DSXX-rXO.js → plugin-app-cache-key-DztPafOA.js} +132 -39
  21. package/dist/{rate-limits-S_PXs0ul.js → rate-limits-CU-gVCCS.js} +1 -1
  22. package/dist/{request-CXpPennK.js → request-qKh331yu.js} +3 -3
  23. package/dist/{run-attempt-C5GTPJOE.js → run-attempt-CY9WlDLc.js} +677 -199
  24. package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-CmtG_t1L.js} +90 -24
  25. package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-NuhpXmPB.js} +4 -0
  26. package/dist/{session-binding-DlVQ-Pxs.js → session-binding-BXpo9ymA.js} +60 -7
  27. package/dist/{session-catalog-CP2oCcEb.js → session-catalog-DyOgR4WH.js} +268 -44
  28. package/dist/{session-cli-CqZ0Fp2a.js → session-cli-2PZaFLRh.js} +1 -1
  29. package/dist/{session-upstream-marker-QE-m5QFG.js → session-upstream-marker-B6U7tLw5.js} +1 -1
  30. package/dist/{settled-turn-finalizer-CA_2QXIE.js → settled-turn-finalizer-CXY2Z9dT.js} +19 -6
  31. package/dist/{shared-client-BVKE40c-.js → shared-client-DTbvH-XV.js} +166 -49
  32. package/dist/{side-question-CO12sHxo.js → side-question-_ulBk1AL.js} +10 -10
  33. package/dist/{thread-lifecycle-DRg_FdzC.js → thread-lifecycle-DF53ogiA.js} +968 -214
  34. package/dist/{protocol-validators-Dm4eXZmt.js → transcript-mirror-DzsdNCf5.js} +589 -2
  35. package/dist/{upstream-session-fork-_Vrz4WJr.js → upstream-session-fork-Dy4Xvuoj.js} +3 -3
  36. package/dist/{usage-UpVbdX5G.js → usage-CQ6GhD5-.js} +3 -3
  37. package/dist/{web-search-provider.runtime-CaQMYDbp.js → web-search-provider.runtime-CCFhg2z5.js} +3 -3
  38. package/package.json +6 -7
  39. package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
  40. package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
  41. package/dist/realtime-voice-api.js +0 -2
  42. package/dist/transcript-mirror-uKA-A4tB.js +0 -550
@@ -1,21 +1,147 @@
1
- import { B as flattenCodexDynamicToolFunctions, E as resolveCodexAppServerClientInstanceId, V as isJsonObject, _ as CodexAppServerRpcError, s as isCodexAppServerStartSelectionChangedError, v as getCodexAppServerClientInstanceId, w as isCodexAppServerPrewriteRequestCancellationError, x as isCodexAppServerConnectionClosedError } from "./shared-client-BVKE40c-.js";
2
- import { A as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, E as resolveCodexPluginsPolicy, a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, f as codexSandboxPolicyForTurn, i as createCodexSessionGenerationSupersededError, k as CODEX_PLUGINS_MARKETPLACE_NAME, l as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-DlVQ-Pxs.js";
3
- import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, i as assertCodexThreadResumeSubscription, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError, u as unsubscribeCodexThreadBestEffort } from "./attempt-client-cleanup-Ia52yRWc.js";
4
- import { i as assertCodexThreadStartResponse, n as assertCodexThreadForkResponse, r as assertCodexThreadResumeResponse } from "./protocol-validators-Dm4eXZmt.js";
5
- import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-DSXX-rXO.js";
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";
6
5
  import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
7
- import { a as projectBoundedCodexThreadHistory } from "./transcript-mirror-uKA-A4tB.js";
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
8
  import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
9
9
  import * as crypto$1 from "node:crypto";
10
10
  import crypto from "node:crypto";
11
+ import os from "node:os";
11
12
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
12
13
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
13
14
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
15
+ import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
16
+ import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
14
17
  import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
15
18
  import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
16
- import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
17
19
  import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
18
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
19
145
  //#region extensions/codex/src/app-server/event-projector-values.ts
20
146
  function readString(record, key) {
21
147
  const value = record[key];
@@ -98,6 +224,7 @@ function readItem(value) {
98
224
  * plugin-owned apps can be exposed to a native Codex thread.
99
225
  */
100
226
  const CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME = `${CODEX_PLUGINS_MARKETPLACE_NAME}-remote`;
227
+ const CODEX_PLUGINS_API_MARKETPLACE_NAME = "openai-api-curated";
101
228
  /** Reads configured Codex plugin state and maps owned apps to readiness diagnostics. */
102
229
  async function readCodexPluginInventory(params) {
103
230
  const policy = params.policy ?? resolveCodexPluginsPolicy(params.pluginConfig);
@@ -110,21 +237,11 @@ async function readCodexPluginInventory(params) {
110
237
  }]
111
238
  };
112
239
  const appInventory = readCachedAppInventory(params);
113
- const curatedListed = await listCodexPluginMetadata(params, "curated-global", {});
114
- const shouldListWorkspacePlugins = policy.pluginPolicies.some((pluginPolicy) => pluginPolicy.enabled && pluginPolicy.marketplaceName === "workspace-directory");
115
- let workspaceListResult;
116
- if (shouldListWorkspacePlugins) try {
117
- workspaceListResult = {
118
- kind: "listed",
119
- response: await listCodexPluginMetadata(params, "workspace-directory", {
120
- cwds: [],
121
- marketplaceKinds: [CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME]
122
- })
123
- };
124
- } catch (error) {
125
- if (!(error instanceof CodexAppServerRpcError)) throw error;
126
- workspaceListResult = { kind: "rejected" };
127
- }
240
+ const installedPlugins = await readInstalledCodexPluginMetadata({
241
+ ...params,
242
+ policy
243
+ });
244
+ let curatedCatalog;
128
245
  const diagnostics = [];
129
246
  const records = [];
130
247
  if (appInventory?.state === "missing") diagnostics.push({
@@ -136,10 +253,15 @@ async function readCodexPluginInventory(params) {
136
253
  message: "Cached Codex app inventory is stale; using stale app readiness and refreshing."
137
254
  });
138
255
  for (const pluginPolicy of policy.pluginPolicies) {
139
- if (!pluginPolicy.enabled) continue;
140
- const listed = pluginPolicy.marketplaceName === "workspace-directory" ? workspaceListResult?.kind === "listed" ? workspaceListResult.response : void 0 : curatedListed;
141
- const hasMarketplace = pluginPolicy.marketplaceName === "workspace-directory" ? listed?.marketplaces.some((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME) === true : listed?.marketplaces.some(isOpenAiCuratedMarketplace) === true;
142
- if (!listed || !hasMarketplace) {
256
+ if (!pluginPolicy.enabled && !policy.allowAllPlugins) continue;
257
+ 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);
263
+ }
264
+ if (!(pluginPolicy.marketplaceName === "workspace-directory" ? listed.marketplaces.some((entry) => entry.name === "workspace-directory") : listed.marketplaces.some(isOpenAiCuratedMarketplace))) {
143
265
  diagnostics.push({
144
266
  code: "marketplace_missing",
145
267
  plugin: pluginPolicy,
@@ -147,7 +269,6 @@ async function readCodexPluginInventory(params) {
147
269
  });
148
270
  continue;
149
271
  }
150
- const resolvedPlugin = pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(listed, pluginPolicy.pluginName) : findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
151
272
  if (!resolvedPlugin) {
152
273
  diagnostics.push({
153
274
  code: "plugin_missing",
@@ -183,7 +304,7 @@ async function readCodexPluginInventory(params) {
183
304
  policy: pluginPolicy,
184
305
  summary,
185
306
  ...detail ? { detail } : {},
186
- activationRequired: !summary.installed || !summary.enabled,
307
+ activationRequired: pluginPolicy.enabled && (!summary.installed || !summary.enabled),
187
308
  authRequired: apps.some((app) => app.needsAuth || !app.accessible),
188
309
  appOwnership,
189
310
  ownedAppIds,
@@ -218,24 +339,41 @@ function resolveRecoverableCodexPluginConfigKeys(params) {
218
339
  return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
219
340
  pluginPolicy,
220
341
  metadataCache: params.metadataCache,
221
- appCacheKey: params.appCacheKey
342
+ appCacheKey: params.appCacheKey,
343
+ configCwd: params.configCwd
222
344
  })).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
223
345
  }
224
- async function listCodexPluginMetadata(params, queryKind, requestParams) {
225
- if (!params.metadataCache || !params.appCacheKey || queryKind === "workspace-directory") return await params.request("plugin/list", requestParams);
346
+ async function listCodexPluginMetadata(params) {
347
+ const requestParams = {};
348
+ if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
226
349
  return (await params.metadataCache.load({
227
350
  appCacheKey: params.appCacheKey,
228
- queryKind,
351
+ queryKind: "curated-global",
229
352
  requestParams,
230
353
  request: async (method, listedParams) => await params.request(method, listedParams),
231
- cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
354
+ cacheable: (response) => response.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
355
+ })).response;
356
+ }
357
+ async function readInstalledCodexPluginMetadata(params) {
358
+ const requestParams = params.configCwd ? { cwds: [params.configCwd] } : {};
359
+ if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/installed", requestParams);
360
+ return (await params.metadataCache.load({
361
+ appCacheKey: params.appCacheKey,
362
+ queryKind: "installed",
363
+ requestParams,
364
+ request: async (method, installedParams) => await params.request(method, installedParams),
365
+ cacheable: (response) => params.policy.pluginPolicies.every((pluginPolicy) => {
366
+ 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;
368
+ })
232
369
  })).response;
233
370
  }
234
371
  function isSettledMissingPluginPolicy(params) {
235
- const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "workspace-directory" : "curated-global";
236
- const listed = params.metadataCache.read(params.appCacheKey, queryKind)?.response;
372
+ 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;
237
375
  if (!listed) return false;
238
- if (queryKind === "workspace-directory") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
376
+ if (queryKind === "installed") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
239
377
  return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
240
378
  }
241
379
  function readCachedAppInventory(params) {
@@ -250,7 +388,7 @@ function readCachedAppInventory(params) {
250
388
  }
251
389
  async function readPluginDetail(params, marketplace, pluginPolicy, summary, diagnostics) {
252
390
  if (params.readPluginDetails === false) return;
253
- if (marketplace.name === "workspace-directory" && marketplace.remoteMarketplaceName && !summary.remotePluginId) {
391
+ if (marketplace.remoteMarketplaceName && !summary.remotePluginId) {
254
392
  diagnostics.push({
255
393
  code: "plugin_detail_unavailable",
256
394
  plugin: pluginPolicy,
@@ -299,7 +437,7 @@ function resolveOwnedApps(params) {
299
437
  name: app.name,
300
438
  accessible: info.isAccessible,
301
439
  enabled: info.isEnabled,
302
- needsAuth: app.needsAuth || !info.isAccessible
440
+ needsAuth: !info.isAccessible
303
441
  };
304
442
  }).toSorted((left, right) => left.id.localeCompare(right.id));
305
443
  }
@@ -337,9 +475,9 @@ function marketplaceRef(marketplace, name) {
337
475
  ...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
338
476
  };
339
477
  }
340
- /** True for either supported OpenAI curated marketplace wire name. */
478
+ /** True for any supported OpenAI curated marketplace wire name, matching Codex's own curated predicate. */
341
479
  function isOpenAiCuratedMarketplace(marketplace) {
342
- return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME;
480
+ return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME || marketplace.name === CODEX_PLUGINS_API_MARKETPLACE_NAME;
343
481
  }
344
482
  //#endregion
345
483
  //#region extensions/codex/src/app-server/plugin-activation.ts
@@ -352,6 +490,7 @@ async function ensureCodexPluginActivation(params) {
352
490
  if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
353
491
  return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
354
492
  }
493
+ 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.` });
355
494
  if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
356
495
  identity: params.identity,
357
496
  ok: true,
@@ -370,6 +509,7 @@ async function ensureCodexPluginActivation(params) {
370
509
  appCache: params.appCache,
371
510
  appCacheKey: params.appCacheKey,
372
511
  metadataCache: params.metadataCache,
512
+ deferAppInventoryRefresh: params.deferAppInventoryRefresh,
373
513
  targetAppIds: params.targetAppIds
374
514
  });
375
515
  refreshDiagnostics.push(...refreshResult.diagnostics);
@@ -391,7 +531,7 @@ async function ensureCodexPluginActivation(params) {
391
531
  /** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
392
532
  async function refreshCodexPluginRuntimeState(params) {
393
533
  const diagnostics = [];
394
- await listCuratedCodexPluginMetadata(params);
534
+ await listCuratedCodexPluginMetadata(params, { forceRefetch: true });
395
535
  await params.request("skills/list", {
396
536
  cwds: [],
397
537
  forceReload: true
@@ -403,7 +543,8 @@ async function refreshCodexPluginRuntimeState(params) {
403
543
  }
404
544
  await params.request("config/mcpServer/reload", void 0);
405
545
  if (params.appCache && params.appCacheKey) {
406
- params.appCache.invalidate(params.appCacheKey, "Codex plugin activation changed app inventory");
546
+ params.appCache.invalidate(params.appCacheKey, "Codex plugin activation changed app inventory", void 0, params.targetAppIds);
547
+ if (params.deferAppInventoryRefresh) return { diagnostics };
407
548
  const request = async (method, requestParams) => await params.request(method, requestParams);
408
549
  try {
409
550
  await params.appCache.refreshNow({
@@ -418,15 +559,15 @@ async function refreshCodexPluginRuntimeState(params) {
418
559
  }
419
560
  return { diagnostics };
420
561
  }
421
- async function listCuratedCodexPluginMetadata(params) {
422
- const requestParams = {};
562
+ async function listCuratedCodexPluginMetadata(params, options = {}) {
563
+ const requestParams = options.forceRefetch ? { forceRefetch: true } : {};
423
564
  if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
424
565
  return (await params.metadataCache.load({
425
566
  appCacheKey: params.appCacheKey,
426
567
  queryKind: "curated-global",
427
568
  requestParams,
428
569
  request: async (method, listedParams) => await params.request(method, listedParams),
429
- cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
570
+ cacheable: (response) => response.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
430
571
  })).response;
431
572
  }
432
573
  function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
@@ -439,6 +580,129 @@ function activationFailure(identity, reason, diagnostic, extraDiagnostics = [])
439
580
  };
440
581
  }
441
582
  //#endregion
583
+ //#region extensions/codex/src/app-server/plugin-thread-app-admission.ts
584
+ async function refreshCodexPluginAppInventory(params, appCache, options = {}) {
585
+ if (!params.appCacheKey) return;
586
+ const request = async (method, requestParams) => await params.request(method, requestParams);
587
+ try {
588
+ return await appCache.refreshNow({
589
+ key: params.appCacheKey,
590
+ request,
591
+ nowMs: params.nowMs,
592
+ forceRefetch: options.forceRefetch,
593
+ targetAppIds: options.targetAppIds
594
+ });
595
+ } catch (error) {
596
+ embeddedAgentLog.warn("codex plugin thread config app inventory refresh failed", {
597
+ reason: options.reason,
598
+ forceRefetch: options.forceRefetch === true,
599
+ error: serializeCodexAppInventoryError(error)
600
+ });
601
+ return;
602
+ }
603
+ }
604
+ function collectCodexPluginOwnedAppIds(inventory) {
605
+ return Array.from(new Set(inventory.records.flatMap((record) => record.ownedAppIds).filter(Boolean))).toSorted();
606
+ }
607
+ function collectCodexReservedPluginAppIds(params) {
608
+ const reserved = new Set(params.inventory.records.flatMap((record) => record.appOwnership === "proven" ? record.ownedAppIds : []));
609
+ const recordsByConfigKey = new Map(params.inventory.records.map((record) => [record.policy.configKey, record]));
610
+ const configuredOwnerNames = new Set(params.policy.pluginPolicies.flatMap((policy) => {
611
+ const record = recordsByConfigKey.get(policy.configKey);
612
+ return [
613
+ policy.configKey,
614
+ policy.pluginName,
615
+ record?.summary.name,
616
+ record?.summary.id
617
+ ].filter((name) => Boolean(name)).map(normalizeCodexPluginOwnerName);
618
+ }));
619
+ for (const app of params.accountApps) if (app.pluginDisplayNames.some((name) => configuredOwnerNames.has(normalizeCodexPluginOwnerName(name)))) reserved.add(app.id);
620
+ return reserved;
621
+ }
622
+ function normalizeCodexPluginOwnerName(name) {
623
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
624
+ }
625
+ async function readCodexThreadAdmissibleAccountApps(params, appCache) {
626
+ const request = async (method, requestParams) => await params.request(method, requestParams);
627
+ const cachedInventory = appCache.read({
628
+ key: params.appCacheKey,
629
+ request,
630
+ nowMs: params.nowMs,
631
+ suppressRefresh: true
632
+ });
633
+ const snapshot = cachedInventory.state === "fresh" && !cachedInventory.snapshot?.targetAppIds?.length ? cachedInventory.snapshot : await refreshCodexPluginAppInventory(params, appCache, {
634
+ forceRefetch: false,
635
+ reason: "account_apps_all",
636
+ targetAppIds: []
637
+ });
638
+ if (!snapshot) return {
639
+ apps: [],
640
+ diagnostic: {
641
+ code: "account_app_inventory_unavailable",
642
+ message: "Codex account app inventory was unavailable; account apps were not exposed."
643
+ }
644
+ };
645
+ const installedAppsById = new Map(snapshot.installedApps.map((app) => [app.id, app]));
646
+ return { apps: snapshot.apps.filter((app) => resolveCodexInstalledAppThreadAdmission(toCodexPluginOwnedAccountApp(app), installedAppsById.get(app.id)) !== "blocked").toSorted((left, right) => left.id.localeCompare(right.id)) };
647
+ }
648
+ function toCodexPluginOwnedAccountApp(app) {
649
+ return {
650
+ id: app.id,
651
+ name: app.name,
652
+ accessible: app.isAccessible,
653
+ enabled: app.isEnabled,
654
+ needsAuth: !app.isAccessible
655
+ };
656
+ }
657
+ function resolveCodexThreadConfigAppsForRecord(params) {
658
+ return params.inventory.appInventory?.state === "missing" ? [] : params.record.apps;
659
+ }
660
+ function resolveCodexPluginAppThreadAdmission(app, inventory) {
661
+ const snapshot = inventory.appInventory?.snapshot;
662
+ if (!snapshot) return "blocked";
663
+ return resolveCodexInstalledAppThreadAdmission(app, snapshot.installedApps.find((candidate) => candidate.id === app.id));
664
+ }
665
+ function resolveCodexInstalledAppThreadAdmission(app, installed) {
666
+ if (!app.accessible || app.needsAuth || !installed) return "blocked";
667
+ if (installed.enabled && installed.callable) return "ready";
668
+ return !installed.enabled && !installed.callable ? "provisional" : "blocked";
669
+ }
670
+ async function readCodexConfigForAppAdmission(params) {
671
+ try {
672
+ const response = await params.request("config/read", {
673
+ includeLayers: true,
674
+ ...params.configCwd ? { cwd: params.configCwd } : {}
675
+ });
676
+ if (!isJsonObject(response) || !isJsonObject(response.config) || !Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config or config layers");
677
+ return {
678
+ config: response.config,
679
+ layers: response.layers.flatMap((layer) => {
680
+ if (!isJsonObject(layer)) throw new Error("Codex config/read returned an invalid config layer");
681
+ if (layer.disabledReason !== void 0 && layer.disabledReason !== null) {
682
+ if (typeof layer.disabledReason !== "string") throw new Error("Codex config/read returned an invalid disabled layer");
683
+ return [];
684
+ }
685
+ if (!isJsonObject(layer.config)) throw new Error("Codex config/read returned an invalid layer config");
686
+ return [layer.config];
687
+ })
688
+ };
689
+ } catch (error) {
690
+ embeddedAgentLog.warn("codex plugin app admission config read failed", { error: serializeCodexAppInventoryError(error) });
691
+ return;
692
+ }
693
+ }
694
+ function resolveCodexExplicitAppEnablement(layersHighestPrecedenceFirst, appId) {
695
+ for (const layer of layersHighestPrecedenceFirst) {
696
+ const apps = layer.apps;
697
+ const app = isJsonObject(apps) ? apps[appId] : void 0;
698
+ if (isJsonObject(app) && Object.hasOwn(app, "enabled")) return app.enabled === true;
699
+ }
700
+ }
701
+ function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
702
+ if (!params.appCacheKey || !policy.pluginPolicies.some((plugin) => plugin.enabled) || inventory.appInventory?.state === "missing") return false;
703
+ return inventory.records.some((record) => record.appOwnership === "proven" && record.ownedAppIds.length > 0 && (record.apps.length === 0 || record.apps.some((app) => !app.accessible)));
704
+ }
705
+ //#endregion
442
706
  //#region extensions/codex/src/app-server/plugin-thread-config.ts
443
707
  /**
444
708
  * Builds Codex thread config patches that expose only policy-approved apps
@@ -492,16 +756,17 @@ async function buildCodexPluginThreadConfig(params) {
492
756
  request: params.request,
493
757
  appCache,
494
758
  appCacheKey: params.appCacheKey,
759
+ configCwd: params.configCwd,
495
760
  metadataCache: params.metadataCache,
496
761
  nowMs: params.nowMs,
497
762
  suppressAppInventoryRefresh: true
498
763
  }) : emptyCodexPluginInventory(policy);
499
764
  const appInventoryRefreshDeferredForActivation = inventory.records.some((record) => record.activationRequired) && shouldRefreshMissingAppInventory(params, policy, inventory);
500
765
  if (shouldWaitForInitialAppInventory(params, policy, inventory)) {
501
- await refreshAppInventoryNow(params, appCache, {
766
+ await refreshCodexPluginAppInventory(params, appCache, {
502
767
  forceRefetch: false,
503
768
  reason: "initial_missing",
504
- targetAppIds: collectInventoryOwnedAppIds(inventory)
769
+ targetAppIds: collectCodexPluginOwnedAppIds(inventory)
505
770
  });
506
771
  inventory = await readCodexPluginInventory({
507
772
  pluginConfig: params.pluginConfig,
@@ -509,6 +774,7 @@ async function buildCodexPluginThreadConfig(params) {
509
774
  request: params.request,
510
775
  appCache,
511
776
  appCacheKey: params.appCacheKey,
777
+ configCwd: params.configCwd,
512
778
  metadataCache: params.metadataCache,
513
779
  nowMs: params.nowMs
514
780
  });
@@ -527,6 +793,7 @@ async function buildCodexPluginThreadConfig(params) {
527
793
  appCache,
528
794
  appCacheKey: params.appCacheKey,
529
795
  metadataCache: params.metadataCache,
796
+ deferAppInventoryRefresh: true,
530
797
  targetAppIds: record.ownedAppIds
531
798
  });
532
799
  activationResults.push(activation);
@@ -539,10 +806,10 @@ async function buildCodexPluginThreadConfig(params) {
539
806
  const postInstallRefreshRequired = activationResults.some((activation) => activation.ok && activation.installAttempted);
540
807
  const deferredMissingRefreshRequired = appInventoryRefreshDeferredForActivation && !postInstallRefreshRequired && shouldRefreshMissingAppInventory(params, policy, inventory);
541
808
  if (postInstallRefreshRequired || deferredMissingRefreshRequired) {
542
- await refreshAppInventoryNow(params, appCache, {
809
+ await refreshCodexPluginAppInventory(params, appCache, {
543
810
  forceRefetch: true,
544
811
  reason: postInstallRefreshRequired ? "post_install" : "deferred_missing",
545
- targetAppIds: collectInventoryOwnedAppIds(inventory)
812
+ targetAppIds: collectCodexPluginOwnedAppIds(inventory)
546
813
  });
547
814
  inventory = await readCodexPluginInventory({
548
815
  pluginConfig: params.pluginConfig,
@@ -550,6 +817,7 @@ async function buildCodexPluginThreadConfig(params) {
550
817
  request: params.request,
551
818
  appCache,
552
819
  appCacheKey: params.appCacheKey,
820
+ configCwd: params.configCwd,
553
821
  metadataCache: params.metadataCache,
554
822
  nowMs: params.nowMs
555
823
  });
@@ -558,11 +826,11 @@ async function buildCodexPluginThreadConfig(params) {
558
826
  appCacheKey: params.appCacheKey
559
827
  });
560
828
  }
561
- if (shouldForceRefreshForNotReadyPluginApps(params, policy, inventory)) {
562
- await refreshAppInventoryNow(params, appCache, {
829
+ if (shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory)) {
830
+ await refreshCodexPluginAppInventory(params, appCache, {
563
831
  forceRefetch: true,
564
832
  reason: "not_ready_plugin_apps",
565
- targetAppIds: collectInventoryOwnedAppIds(inventory)
833
+ targetAppIds: collectCodexPluginOwnedAppIds(inventory)
566
834
  });
567
835
  inventory = await readCodexPluginInventory({
568
836
  pluginConfig: params.pluginConfig,
@@ -570,6 +838,7 @@ async function buildCodexPluginThreadConfig(params) {
570
838
  request: params.request,
571
839
  appCache,
572
840
  appCacheKey: params.appCacheKey,
841
+ configCwd: params.configCwd,
573
842
  metadataCache: params.metadataCache,
574
843
  nowMs: params.nowMs
575
844
  });
@@ -578,12 +847,13 @@ async function buildCodexPluginThreadConfig(params) {
578
847
  appCacheKey: params.appCacheKey
579
848
  });
580
849
  }
581
- const accountAppsResult = policy.allowAllPlugins ? await readAccessibleAccountApps(params, appCache) : { apps: [] };
850
+ const accountAppsResult = policy.allowAllPlugins ? await readCodexThreadAdmissibleAccountApps(params, appCache) : { apps: [] };
582
851
  const diagnostics = [
583
852
  ...inventory.diagnostics,
584
853
  ...activationDiagnostics,
585
854
  ...accountAppsResult.diagnostic ? [accountAppsResult.diagnostic] : []
586
855
  ];
856
+ const provisionalAppIds = /* @__PURE__ */ new Set();
587
857
  const apps = { _default: {
588
858
  enabled: false,
589
859
  destructive_enabled: false,
@@ -591,17 +861,30 @@ async function buildCodexPluginThreadConfig(params) {
591
861
  } };
592
862
  const policyApps = {};
593
863
  const pluginAppIds = {};
594
- const pluginOwnedAppIds = new Set(inventory.records.flatMap((record) => record.appOwnership === "proven" ? record.ownedAppIds : []));
864
+ let configForAppAdmission;
865
+ const pluginOwnedAppIds = collectCodexReservedPluginAppIds({
866
+ policy,
867
+ inventory,
868
+ accountApps: accountAppsResult.apps
869
+ });
870
+ const unresolvedDisabledPluginOwnership = policy.allowAllPlugins ? policy.pluginPolicies.find((pluginPolicy) => !pluginPolicy.enabled && !inventory.records.some((record) => record.policy.configKey === pluginPolicy.configKey && record.detail)) : void 0;
871
+ if (unresolvedDisabledPluginOwnership) diagnostics.push({
872
+ code: "account_app_ownership_unavailable",
873
+ plugin: unresolvedDisabledPluginOwnership,
874
+ message: `Could not verify disabled Codex plugin app ownership for ${unresolvedDisabledPluginOwnership.pluginName}; account apps were not exposed.`
875
+ });
595
876
  for (const record of inventory.records) {
877
+ if (!record.policy.enabled) continue;
596
878
  const activation = activationResults.find((item) => item.identity.configKey === record.policy.configKey);
597
879
  if (activation?.ok === false || record.activationRequired && !activation?.ok) continue;
598
880
  if (record.appOwnership !== "proven") continue;
599
881
  pluginAppIds[record.policy.configKey] = [...record.ownedAppIds].toSorted();
600
- for (const app of resolveThreadConfigAppsForRecord({
882
+ for (const app of resolveCodexThreadConfigAppsForRecord({
601
883
  record,
602
884
  inventory
603
885
  })) {
604
- if (!isPluginAppReadyForThreadStart(app)) {
886
+ const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await (configForAppAdmission ??= readCodexConfigForAppAdmission(params));
887
+ if (!admissionConfig || resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) {
605
888
  diagnostics.push({
606
889
  code: "app_not_ready",
607
890
  plugin: record.policy,
@@ -612,10 +895,12 @@ async function buildCodexPluginThreadConfig(params) {
612
895
  if (record.policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
613
896
  request: params.request,
614
897
  configCwd: params.configCwd,
898
+ config: admissionConfig.config,
615
899
  plugin: record.policy,
616
900
  app,
617
901
  diagnostics
618
902
  })) continue;
903
+ provisionalAppIds.add(app.id);
619
904
  apps[app.id] = buildEnabledAppConfig(record.policy);
620
905
  policyApps[app.id] = {
621
906
  configKey: record.policy.configKey,
@@ -627,15 +912,27 @@ async function buildCodexPluginThreadConfig(params) {
627
912
  };
628
913
  }
629
914
  }
630
- for (const app of accountAppsResult.apps) {
915
+ for (const app of unresolvedDisabledPluginOwnership ? [] : accountAppsResult.apps) {
631
916
  if (pluginOwnedAppIds.has(app.id)) continue;
632
- const accountApp = toOwnedAccountApp(app);
917
+ configForAppAdmission ??= readCodexConfigForAppAdmission(params);
918
+ const admissionConfig = await configForAppAdmission;
919
+ if (!admissionConfig) {
920
+ diagnostics.push({
921
+ code: "account_app_config_unavailable",
922
+ message: "Codex account app configuration was unavailable; account apps were not exposed."
923
+ });
924
+ break;
925
+ }
926
+ if (resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) continue;
927
+ const accountApp = toCodexPluginOwnedAccountApp(app);
633
928
  if (policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
634
929
  request: params.request,
635
930
  configCwd: params.configCwd,
931
+ config: admissionConfig.config,
636
932
  app: accountApp,
637
933
  diagnostics
638
934
  })) continue;
935
+ provisionalAppIds.add(app.id);
639
936
  apps[app.id] = buildEnabledAppConfig(policy);
640
937
  policyApps[app.id] = {
641
938
  source: "account",
@@ -650,6 +947,7 @@ async function buildCodexPluginThreadConfig(params) {
650
947
  return {
651
948
  enabled: true,
652
949
  configPatch,
950
+ ...provisionalAppIds.size > 0 ? { provisionalAppIds: Array.from(provisionalAppIds).toSorted() } : {},
653
951
  fingerprint: fingerprintJson({
654
952
  version: CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION,
655
953
  inputFingerprint,
@@ -738,13 +1036,22 @@ function buildPluginAppPolicyContext(apps, pluginAppIds) {
738
1036
  }
739
1037
  async function clearPersistedAppToolApprovalOverrides(params) {
740
1038
  try {
741
- const overrideNames = await readPersistedAppToolApprovalOverrideNames(params);
742
- for (const toolName of overrideNames) if (isOverriddenConfigWriteResponse(await params.request("config/value/write", {
1039
+ const overrideNames = readPersistedAppToolApprovalOverrideNames(params.config, params.app);
1040
+ if (overrideNames.length === 0) return true;
1041
+ const edits = overrideNames.map((toolName) => ({
743
1042
  keyPath: `apps.${quoteConfigKeyPathSegment(params.app.id)}.tools.${quoteConfigKeyPathSegment(toolName)}.approval_mode`,
744
1043
  value: null,
745
1044
  mergeStrategy: "replace"
746
- }))) throw new Error(`approval override for ${toolName} is controlled by another config layer`);
747
- const remainingOverrideNames = await readPersistedAppToolApprovalOverrideNames(params);
1045
+ }));
1046
+ const response = await params.request("config/batchWrite", { edits });
1047
+ if (!isJsonObject(response) || response.status !== "ok" && response.status !== "okOverridden") throw new Error("Codex did not confirm the approval override batch");
1048
+ if (response.status === "okOverridden") throw new Error(`approval override for ${overrideNames.join(", ")} is controlled by another config layer`);
1049
+ const confirmed = await params.request("config/read", {
1050
+ includeLayers: false,
1051
+ ...params.configCwd ? { cwd: params.configCwd } : {}
1052
+ });
1053
+ if (!isJsonObject(confirmed) || !isJsonObject(confirmed.config)) throw new Error("Codex did not confirm effective app approval configuration");
1054
+ const remainingOverrideNames = readPersistedAppToolApprovalOverrideNames(confirmed.config, params.app);
748
1055
  if (remainingOverrideNames.length > 0) throw new Error(`effective approval overrides remain for ${remainingOverrideNames.join(", ")}`);
749
1056
  return true;
750
1057
  } catch (error) {
@@ -756,24 +1063,15 @@ async function clearPersistedAppToolApprovalOverrides(params) {
756
1063
  return false;
757
1064
  }
758
1065
  }
759
- async function readPersistedAppToolApprovalOverrideNames(params) {
760
- const response = await params.request("config/read", {
761
- includeLayers: false,
762
- ...params.configCwd ? { cwd: params.configCwd } : {}
763
- });
764
- const config = isJsonObject(response) ? response.config : void 0;
765
- const appsRoot = isJsonObject(config) ? config.apps : void 0;
766
- const nestedApps = isJsonObject(appsRoot) ? appsRoot.apps : void 0;
767
- const appConfig = isJsonObject(appsRoot) ? appsRoot[params.app.id] ?? (isJsonObject(nestedApps) ? nestedApps[params.app.id] : void 0) : void 0;
1066
+ function readPersistedAppToolApprovalOverrideNames(config, app) {
1067
+ const appsRoot = config.apps;
1068
+ const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
768
1069
  const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
769
1070
  if (!isJsonObject(tools)) return [];
770
1071
  return Object.entries(tools).filter(([, value]) => hasPersistedToolApprovalOverride(value)).map(([toolName]) => toolName).toSorted();
771
1072
  }
772
1073
  function hasPersistedToolApprovalOverride(value) {
773
- return isJsonObject(value) && (value.approval_mode !== void 0 || value.approvalMode !== void 0);
774
- }
775
- function isOverriddenConfigWriteResponse(response) {
776
- return isJsonObject(response) && response.status === "okOverridden";
1074
+ return isJsonObject(value) && value.approval_mode !== void 0;
777
1075
  }
778
1076
  function quoteConfigKeyPathSegment(segment) {
779
1077
  return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
@@ -785,30 +1083,6 @@ function shouldWaitForInitialAppInventory(params, policy, inventory) {
785
1083
  function shouldRefreshMissingAppInventory(params, policy, inventory) {
786
1084
  return Boolean(params.appCacheKey && policy.pluginPolicies.some((plugin) => plugin.enabled) && inventory.appInventory?.state === "missing");
787
1085
  }
788
- async function refreshAppInventoryNow(params, appCache, options = {}) {
789
- const appCacheKey = params.appCacheKey;
790
- if (!appCacheKey) return;
791
- const request = async (method, requestParams) => await params.request(method, requestParams);
792
- try {
793
- return await appCache.refreshNow({
794
- key: appCacheKey,
795
- request,
796
- nowMs: params.nowMs,
797
- forceRefetch: options.forceRefetch,
798
- targetAppIds: options.targetAppIds
799
- });
800
- } catch (error) {
801
- embeddedAgentLog.warn("codex plugin thread config app inventory refresh failed", {
802
- reason: options.reason,
803
- forceRefetch: options.forceRefetch === true,
804
- error: serializeCodexAppInventoryError(error)
805
- });
806
- return;
807
- }
808
- }
809
- function collectInventoryOwnedAppIds(inventory) {
810
- return Array.from(new Set(inventory.records.flatMap((record) => record.ownedAppIds).filter(Boolean))).toSorted();
811
- }
812
1086
  function emptyCodexPluginInventory(policy) {
813
1087
  return {
814
1088
  policy,
@@ -816,42 +1090,6 @@ function emptyCodexPluginInventory(policy) {
816
1090
  diagnostics: []
817
1091
  };
818
1092
  }
819
- async function readAccessibleAccountApps(params, appCache) {
820
- const snapshot = await refreshAppInventoryNow(params, appCache, {
821
- forceRefetch: false,
822
- reason: "account_apps_all",
823
- targetAppIds: []
824
- });
825
- if (!snapshot) return {
826
- apps: [],
827
- diagnostic: {
828
- code: "account_app_inventory_unavailable",
829
- message: "Codex account app inventory was unavailable; account apps were not exposed."
830
- }
831
- };
832
- return { apps: snapshot.apps.filter((app) => app.isAccessible).toSorted((left, right) => left.id.localeCompare(right.id)) };
833
- }
834
- function toOwnedAccountApp(app) {
835
- return {
836
- id: app.id,
837
- name: app.name,
838
- accessible: app.isAccessible,
839
- enabled: app.isEnabled,
840
- needsAuth: !app.isAccessible
841
- };
842
- }
843
- function resolveThreadConfigAppsForRecord(params) {
844
- if (params.inventory.appInventory?.state === "missing") return [];
845
- return params.record.apps;
846
- }
847
- function isPluginAppReadyForThreadStart(app) {
848
- return app.accessible;
849
- }
850
- function shouldForceRefreshForNotReadyPluginApps(params, policy, inventory) {
851
- if (!params.appCacheKey || !policy.pluginPolicies.some((plugin) => plugin.enabled)) return false;
852
- if (inventory.appInventory?.state === "missing") return false;
853
- return inventory.records.some((record) => record.appOwnership === "proven" && record.ownedAppIds.length > 0 && (record.apps.length === 0 || record.apps.some((app) => !app.accessible)));
854
- }
855
1093
  function policyFingerprint(policy) {
856
1094
  return {
857
1095
  enabled: policy.enabled,
@@ -1113,8 +1351,8 @@ function hasMessageContent(message) {
1113
1351
  return "content" in message;
1114
1352
  }
1115
1353
  function normalizeRenderedContextMaxChars(value) {
1116
- if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RENDERED_CONTEXT_CHARS;
1117
- return Math.min(MAX_RENDERED_CONTEXT_CHARS, Math.max(DEFAULT_RENDERED_CONTEXT_CHARS, Math.floor(value)));
1354
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return DEFAULT_RENDERED_CONTEXT_CHARS;
1355
+ return Math.min(MAX_RENDERED_CONTEXT_CHARS, Math.max(1, Math.floor(value)));
1118
1356
  }
1119
1357
  function resolveTextPartMaxChars(maxRenderedContextChars) {
1120
1358
  return Math.min(MAX_TEXT_PART_CHARS, Math.max(DEFAULT_TEXT_PART_CHARS, Math.floor(maxRenderedContextChars / 4)));
@@ -1231,6 +1469,26 @@ function fingerprintUserMcpServersAuthorizationHeader(value) {
1231
1469
  function fingerprintJsonObject(value) {
1232
1470
  return JSON.stringify(stabilizeJsonValue(value));
1233
1471
  }
1472
+ /** Hash every resume-visible setting without retaining config, credentials, or instructions. */
1473
+ function fingerprintCodexThreadConfig(request, authProfileId, dynamicToolsFingerprint) {
1474
+ return hashCodexAppServerBindingFingerprint(fingerprintJsonObject({
1475
+ authProfileId: authProfileId ?? null,
1476
+ dynamicToolsFingerprint: dynamicToolsFingerprint ?? null,
1477
+ model: request.model ?? null,
1478
+ requestedModel: request.requestedModel === void 0 ? request.model ?? null : request.requestedModel,
1479
+ modelProvider: request.modelProvider ?? null,
1480
+ 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
+ permissions: request.permissions ?? null,
1485
+ personality: request.personality ?? null,
1486
+ serviceTier: request.serviceTier === void 0 ? "<omitted>" : request.serviceTier,
1487
+ baseInstructions: request.baseInstructions ?? null,
1488
+ developerInstructions: request.developerInstructions ?? null,
1489
+ config: request.config ?? {}
1490
+ }));
1491
+ }
1234
1492
  function fingerprintEnvironmentSelection(environments) {
1235
1493
  return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
1236
1494
  }
@@ -1279,6 +1537,206 @@ function compareJsonFingerprint(left, right) {
1279
1537
  return JSON.stringify(left).localeCompare(JSON.stringify(right));
1280
1538
  }
1281
1539
  //#endregion
1540
+ //#region extensions/codex/src/app-server/native-skill-isolation.ts
1541
+ const MAX_PERSONAL_SKILL_DIRECTORIES = 2e3;
1542
+ const MAX_PERSONAL_SKILL_DEPTH = 6;
1543
+ const MAX_PERSONAL_SKILL_ENTRIES = 1e4;
1544
+ const nativeSkillIsolationByClient = /* @__PURE__ */ new WeakMap();
1545
+ function isMissingPathError(error) {
1546
+ return error.code === "ENOENT";
1547
+ }
1548
+ function isPathWithin(root, candidate) {
1549
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
1550
+ return relative === "" || !path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== "..";
1551
+ }
1552
+ async function canonicalizeExistingPath(candidate) {
1553
+ try {
1554
+ return await fs.realpath(candidate);
1555
+ } catch {
1556
+ return path.resolve(candidate);
1557
+ }
1558
+ }
1559
+ async function usesDefaultStateDir() {
1560
+ if (!process.env.OPENCLAW_STATE_DIR?.trim()) return true;
1561
+ const home = resolveRequiredHomeDir();
1562
+ const [stateDir, defaultStateDir] = await Promise.all([canonicalizeExistingPath(resolveStateDir()), canonicalizeExistingPath(path.join(home, ".openclaw"))]);
1563
+ return stateDir === defaultStateDir;
1564
+ }
1565
+ async function collectPersonalSkillRealPaths(homes, codexHome) {
1566
+ const realStateDir = await canonicalizeExistingPath(resolveStateDir());
1567
+ const roots = [];
1568
+ for (const home of homes) {
1569
+ for (const dir of [".agents", ".claude"]) roots.push({
1570
+ dir: path.join(home, dir, "skills"),
1571
+ onlyEscapedStateTargets: false
1572
+ });
1573
+ const defaultCodexHome = path.join(home, ".codex");
1574
+ const realDefaultCodexHome = await canonicalizeExistingPath(defaultCodexHome);
1575
+ roots.push({
1576
+ dir: path.join(defaultCodexHome, "skills"),
1577
+ onlyEscapedStateTargets: isPathWithin(realStateDir, realDefaultCodexHome)
1578
+ });
1579
+ }
1580
+ const configuredCodexHome = codexHome?.trim() || process.env.CODEX_HOME?.trim();
1581
+ if (configuredCodexHome) {
1582
+ const stateOwned = isPathWithin(realStateDir, await canonicalizeExistingPath(configuredCodexHome));
1583
+ roots.push({
1584
+ dir: path.join(configuredCodexHome, "skills"),
1585
+ onlyEscapedStateTargets: stateOwned
1586
+ });
1587
+ }
1588
+ const skillPaths = /* @__PURE__ */ new Set();
1589
+ let complete = true;
1590
+ const seenDirectories = /* @__PURE__ */ new Set();
1591
+ const queue = roots.map((root) => ({
1592
+ dir: root.dir,
1593
+ onlyEscapedStateTargets: root.onlyEscapedStateTargets,
1594
+ depth: 0
1595
+ }));
1596
+ let entryCount = 0;
1597
+ const recordSkillFile = async (filePath, onlyEscapedStateTargets) => {
1598
+ try {
1599
+ const skillRealPath = await fs.realpath(filePath);
1600
+ if (!onlyEscapedStateTargets || !isPathWithin(realStateDir, skillRealPath)) skillPaths.add(skillRealPath);
1601
+ } catch (error) {
1602
+ if (!isMissingPathError(error)) complete = false;
1603
+ }
1604
+ };
1605
+ for (const current of queue) {
1606
+ let realDir;
1607
+ try {
1608
+ realDir = await fs.realpath(current.dir);
1609
+ } catch (error) {
1610
+ if (isMissingPathError(error)) continue;
1611
+ complete = false;
1612
+ continue;
1613
+ }
1614
+ if (seenDirectories.has(realDir)) continue;
1615
+ seenDirectories.add(realDir);
1616
+ if (seenDirectories.size > MAX_PERSONAL_SKILL_DIRECTORIES) {
1617
+ complete = false;
1618
+ break;
1619
+ }
1620
+ let directory;
1621
+ try {
1622
+ directory = await fs.opendir(current.dir);
1623
+ } catch (error) {
1624
+ if (!isMissingPathError(error)) complete = false;
1625
+ continue;
1626
+ }
1627
+ try {
1628
+ for await (const entry of directory) {
1629
+ entryCount += 1;
1630
+ if (entryCount > MAX_PERSONAL_SKILL_ENTRIES) {
1631
+ complete = false;
1632
+ queue.length = 0;
1633
+ break;
1634
+ }
1635
+ if (entry.name.startsWith(".")) continue;
1636
+ const entryPath = path.join(current.dir, entry.name);
1637
+ if (entry.name === "SKILL.md" && entry.isFile()) {
1638
+ await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
1639
+ continue;
1640
+ }
1641
+ if (entry.isSymbolicLink()) {
1642
+ try {
1643
+ const stat = await fs.stat(entryPath);
1644
+ if (entry.name === "SKILL.md" && stat.isFile()) await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
1645
+ else if (stat.isDirectory()) if (current.depth < MAX_PERSONAL_SKILL_DEPTH) queue.push({
1646
+ dir: entryPath,
1647
+ depth: current.depth + 1,
1648
+ onlyEscapedStateTargets: current.onlyEscapedStateTargets
1649
+ });
1650
+ else complete = false;
1651
+ } catch (error) {
1652
+ if (!isMissingPathError(error)) complete = false;
1653
+ }
1654
+ continue;
1655
+ }
1656
+ if (current.depth >= MAX_PERSONAL_SKILL_DEPTH) {
1657
+ if (entry.isDirectory()) complete = false;
1658
+ continue;
1659
+ }
1660
+ if (entry.isDirectory()) {
1661
+ queue.push({
1662
+ dir: entryPath,
1663
+ depth: current.depth + 1,
1664
+ onlyEscapedStateTargets: current.onlyEscapedStateTargets
1665
+ });
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
1282
1740
  //#region extensions/codex/src/app-server/dynamic-tool-profile.ts
1283
1741
  /** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
1284
1742
  const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
@@ -1367,13 +1825,6 @@ function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
1367
1825
  return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
1368
1826
  }
1369
1827
  //#endregion
1370
- //#region extensions/codex/src/app-server/profiler-flag.ts
1371
- const PROFILER_FLAGS = ["profiler", "codex.profiler"];
1372
- /** Checks the generic and Codex-specific profiler diagnostic flags. */
1373
- function isCodexAppServerProfilerEnabled(config, env = process.env) {
1374
- return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
1375
- }
1376
- //#endregion
1377
1828
  //#region extensions/codex/src/app-server/thread-binding-policy.ts
1378
1829
  function shouldRotateCodexAppServerBindingForRuntime(params) {
1379
1830
  if (!params.current) return false;
@@ -1451,6 +1902,61 @@ var CodexAdoptedThreadActiveError = class extends Error {
1451
1902
  }
1452
1903
  };
1453
1904
  //#endregion
1905
+ //#region extensions/codex/src/app-server/plugin-thread-attestation.ts
1906
+ /**
1907
+ * Confirms admitted plugin and account apps against their actual Codex thread before
1908
+ * OpenClaw commits a binding or starts a turn.
1909
+ */
1910
+ var CodexPluginThreadAppAttestationError = class extends Error {
1911
+ constructor(message, options) {
1912
+ super(message, options);
1913
+ this.name = "CodexPluginThreadAppAttestationError";
1914
+ }
1915
+ };
1916
+ /** Reads the existing runtime snapshot with the started thread's effective app policy. */
1917
+ async function attestCodexPluginThreadApps(params) {
1918
+ const appIds = Array.from(new Set(params.appIds.filter(Boolean))).toSorted();
1919
+ if (appIds.length === 0) return;
1920
+ let response;
1921
+ try {
1922
+ response = await params.client.request("app/installed", {
1923
+ threadId: params.threadId,
1924
+ forceRefresh: false
1925
+ }, { signal: params.signal });
1926
+ } catch (error) {
1927
+ throw new CodexPluginThreadAppAttestationError(`Codex could not confirm admitted apps for thread ${params.threadId}`, { cause: error });
1928
+ }
1929
+ const installedById = new Map(response.apps.map((app) => [app.id, app]));
1930
+ const failures = appIds.flatMap((appId) => {
1931
+ const app = installedById.get(appId);
1932
+ if (!app) return [`${appId}:missing`];
1933
+ if (!app.enabled) return [`${appId}:disabled`];
1934
+ return app.callable ? [] : [`${appId}:not-callable`];
1935
+ });
1936
+ if (failures.length > 0) throw new CodexPluginThreadAppAttestationError(`Codex thread ${params.threadId} did not expose admitted apps: ${failures.join(", ")}`);
1937
+ }
1938
+ /** Deletes a persistent pre-turn thread; ephemeral threads can only be unsubscribed. */
1939
+ async function discardUnattestedCodexPluginThread(params) {
1940
+ if (params.ephemeral) return await unsubscribeCodexThreadBestEffort(params.client, {
1941
+ threadId: params.threadId,
1942
+ timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
1943
+ });
1944
+ try {
1945
+ await params.client.request("thread/delete", { threadId: params.threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
1946
+ return true;
1947
+ } catch (error) {
1948
+ embeddedAgentLog.debug("codex plugin app attestation thread deletion failed", {
1949
+ threadId: params.threadId,
1950
+ error
1951
+ });
1952
+ await unsubscribeCodexThreadBestEffort(params.client, {
1953
+ threadId: params.threadId,
1954
+ timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
1955
+ });
1956
+ return false;
1957
+ }
1958
+ }
1959
+ //#endregion
1454
1960
  //#region extensions/codex/src/app-server/reasoning-effort.ts
1455
1961
  const CODEX_REASONING_EFFORTS = [
1456
1962
  "minimal",
@@ -1601,31 +2107,38 @@ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts)
1601
2107
  //#endregion
1602
2108
  //#region extensions/codex/src/app-server/thread-prompt.ts
1603
2109
  function buildDeveloperInstructions(params, options = {}) {
2110
+ const deferredToolNames = /* @__PURE__ */ new Set();
2111
+ let hasSkillWorkshop = false;
2112
+ let hasSessionsYield = false;
2113
+ let hasSeenDirectNamespace = false;
2114
+ let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
2115
+ for (const spec of options.dynamicTools ?? []) {
2116
+ const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
2117
+ if (isDirectNamespace) hasSeenDirectNamespace = true;
2118
+ for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
2119
+ const name = tool.name.trim();
2120
+ if (tool.deferLoading === true && name) deferredToolNames.add(name);
2121
+ hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
2122
+ hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
2123
+ messageToolAvailable ||= name === "message";
2124
+ }
2125
+ }
1604
2126
  const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
1605
2127
  surface: "codex_app_server",
1606
2128
  includeLegacyGlobalGuidance: false
1607
2129
  }).join("\n");
1608
2130
  return [
1609
2131
  "You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
1610
- buildDeferredDynamicToolManifest(options.dynamicTools),
1611
- buildSkillWorkshopInstruction(options.dynamicTools),
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,
2133
+ hasSkillWorkshop ? buildSkillWorkshopPromptSection().join("\n") : void 0,
1612
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`.",
1613
- buildVisibleReplyInstruction(params, options.dynamicTools),
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,
2136
+ buildVisibleReplyInstruction(params, messageToolAvailable),
1614
2137
  nativeCommandGuidance,
1615
2138
  params.extraSystemPrompt
1616
2139
  ].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
1617
2140
  }
1618
- function buildDeferredDynamicToolManifest(dynamicTools) {
1619
- const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
1620
- if (deferredToolNames.length === 0) return;
1621
- return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
1622
- }
1623
- function buildSkillWorkshopInstruction(dynamicTools) {
1624
- if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
1625
- return buildSkillWorkshopPromptSection().join("\n");
1626
- }
1627
- function buildVisibleReplyInstruction(params, dynamicTools) {
1628
- const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
2141
+ function buildVisibleReplyInstruction(params, messageToolAvailable) {
1629
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.";
1630
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.";
1631
2144
  return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
@@ -2023,12 +2536,17 @@ async function resumeCodexAppServerThread(params) {
2023
2536
  }
2024
2537
  //#endregion
2025
2538
  //#region extensions/codex/src/app-server/thread-lifecycle-io.ts
2539
+ function resolveCodexThreadRolloutPath(thread) {
2540
+ const rolloutPath = thread.path?.trim();
2541
+ if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
2542
+ return rolloutPath;
2543
+ }
2026
2544
  async function resumeExistingCodexThread(params, context) {
2027
- const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
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;
2028
2546
  let resumeReservation;
2029
2547
  try {
2030
2548
  const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
2031
- const finalConfigPatch = params.buildFinalConfigPatch?.({
2549
+ const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
2032
2550
  action: "resume",
2033
2551
  binding: resumeBinding
2034
2552
  }) ?? {
@@ -2036,7 +2554,7 @@ async function resumeExistingCodexThread(params, context) {
2036
2554
  nativeHookRelayGeneration: params.nativeHookRelayGeneration
2037
2555
  };
2038
2556
  const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
2039
- const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
2557
+ const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
2040
2558
  const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
2041
2559
  threadId: resumeBinding.threadId,
2042
2560
  authProfileId,
@@ -2082,6 +2600,7 @@ async function resumeExistingCodexThread(params, context) {
2082
2600
  const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
2083
2601
  const resumePatch = {
2084
2602
  cwd: params.cwd,
2603
+ rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
2085
2604
  authProfileId: boundAuthProfileId,
2086
2605
  model: response.model ?? resumeParams.model ?? params.params.modelId,
2087
2606
  preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
@@ -2089,6 +2608,7 @@ async function resumeExistingCodexThread(params, context) {
2089
2608
  dynamicToolsFingerprint,
2090
2609
  dynamicToolsContainDeferred,
2091
2610
  webSearchThreadConfigFingerprint,
2611
+ nativeSkillIsolationFingerprint,
2092
2612
  userMcpServersFingerprint,
2093
2613
  mcpServersFingerprint: nextMcpServersFingerprint,
2094
2614
  ringZeroConfigFingerprint,
@@ -2130,6 +2650,13 @@ async function resumeExistingCodexThread(params, context) {
2130
2650
  ...resumeBinding,
2131
2651
  threadId: response.thread.id,
2132
2652
  ...resumePatch,
2653
+ liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
2654
+ ...resumeParams,
2655
+ model: resumeBinding.preserveNativeModel === true ? null : response.model ?? resumeParams.model ?? null,
2656
+ requestedModel: resumeBinding.preserveNativeModel === true ? null : resumeParams.model ?? null,
2657
+ modelProvider: resumeBinding.preserveNativeModel === true ? null : resumePatch.modelProvider ?? null,
2658
+ requestedModelProvider: resumeBinding.preserveNativeModel === true ? null : resumeParams.modelProvider ?? resumePatch.modelProvider ?? null
2659
+ }, authProfileId, dynamicToolsFingerprint),
2133
2660
  lifecycle: {
2134
2661
  action: "resumed",
2135
2662
  ...activeTurnIds.length ? { activeTurnIds } : {}
@@ -2156,13 +2683,13 @@ async function resumeExistingCodexThread(params, context) {
2156
2683
  }
2157
2684
  async function startFreshCodexThread(params, context) {
2158
2685
  const clientId = resolveCodexAppServerClientInstanceId(params.client);
2159
- const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding } = context;
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;
2160
2687
  const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2161
2688
  const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2162
2689
  configPatch: params.finalConfigPatch,
2163
2690
  nativeHookRelayGeneration: params.nativeHookRelayGeneration
2164
2691
  };
2165
- const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
2692
+ const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
2166
2693
  const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
2167
2694
  cwd: params.cwd,
2168
2695
  dynamicTools: params.dynamicTools,
@@ -2188,6 +2715,26 @@ async function startFreshCodexThread(params, context) {
2188
2715
  throw error;
2189
2716
  }
2190
2717
  }));
2718
+ const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
2719
+ if (provisionalAppIds?.length) try {
2720
+ await lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
2721
+ client: params.client,
2722
+ threadId: response.thread.id,
2723
+ appIds: provisionalAppIds,
2724
+ signal: params.signal
2725
+ }));
2726
+ } catch (error) {
2727
+ if (!await discardUnattestedCodexPluginThread({
2728
+ client: params.client,
2729
+ threadId: response.thread.id,
2730
+ ephemeral: startParams.ephemeral === true
2731
+ })) {
2732
+ await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
2733
+ throw new CodexAppServerUnsafeSubscriptionError("Codex plugin app attestation cleanup failed", { cause: error });
2734
+ }
2735
+ throw error;
2736
+ }
2737
+ const rolloutPath = resolveCodexThreadRolloutPath(response.thread);
2191
2738
  if (ringZeroActive) try {
2192
2739
  await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
2193
2740
  } catch (error) {
@@ -2202,6 +2749,7 @@ async function startFreshCodexThread(params, context) {
2202
2749
  agentDir: params.params.agentDir,
2203
2750
  config: params.params.config
2204
2751
  });
2752
+ const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
2205
2753
  const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
2206
2754
  if (!preserveExistingBinding) {
2207
2755
  if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
@@ -2211,12 +2759,14 @@ async function startFreshCodexThread(params, context) {
2211
2759
  threadId: response.thread.id,
2212
2760
  ...clientId ? { clientId } : {},
2213
2761
  cwd: params.cwd,
2762
+ ...rolloutPath ? { rolloutPath } : {},
2214
2763
  authProfileId: params.params.authProfileId,
2215
2764
  model: response.model ?? startParams.model ?? params.params.modelId,
2216
- modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
2765
+ modelProvider: bindingModelProvider,
2217
2766
  dynamicToolsFingerprint,
2218
2767
  dynamicToolsContainDeferred,
2219
2768
  webSearchThreadConfigFingerprint,
2769
+ nativeSkillIsolationFingerprint,
2220
2770
  userMcpServersFingerprint,
2221
2771
  mcpServersFingerprint: nextMcpServersFingerprint,
2222
2772
  ringZeroConfigFingerprint,
@@ -2254,11 +2804,13 @@ async function startFreshCodexThread(params, context) {
2254
2804
  threadId: response.thread.id,
2255
2805
  ...clientId ? { clientId } : {},
2256
2806
  cwd: params.cwd,
2807
+ ...rolloutPath ? { rolloutPath } : {},
2257
2808
  authProfileId: params.params.authProfileId,
2258
2809
  model: response.model ?? startParams.model ?? params.params.modelId,
2259
2810
  modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
2260
2811
  dynamicToolsFingerprint,
2261
2812
  dynamicToolsContainDeferred,
2813
+ nativeSkillIsolationFingerprint,
2262
2814
  userMcpServersFingerprint,
2263
2815
  mcpServersFingerprint: nextMcpServersFingerprint,
2264
2816
  ringZeroConfigFingerprint,
@@ -2272,6 +2824,13 @@ async function startFreshCodexThread(params, context) {
2272
2824
  pluginAppPolicyContext: pluginThreadConfig?.policyContext,
2273
2825
  contextEngine: contextEngineBinding,
2274
2826
  environmentSelectionFingerprint,
2827
+ ...!preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
2828
+ ...startParams,
2829
+ model: response.model ?? startParams.model ?? null,
2830
+ requestedModel: startParams.model ?? null,
2831
+ modelProvider: bindingModelProvider ?? null,
2832
+ requestedModelProvider: startParams.modelProvider ?? bindingModelProvider ?? null
2833
+ }, params.params.authProfileId, dynamicToolsFingerprint) } : {},
2275
2834
  lifecycle: {
2276
2835
  action: "started",
2277
2836
  ...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
@@ -2279,6 +2838,13 @@ async function startFreshCodexThread(params, context) {
2279
2838
  };
2280
2839
  }
2281
2840
  //#endregion
2841
+ //#region extensions/codex/src/app-server/profiler-flag.ts
2842
+ const PROFILER_FLAGS = ["profiler", "codex.profiler"];
2843
+ /** Checks the generic and Codex-specific profiler diagnostic flags. */
2844
+ function isCodexAppServerProfilerEnabled(config, env = process.env) {
2845
+ return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
2846
+ }
2847
+ //#endregion
2282
2848
  //#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
2283
2849
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
2284
2850
  const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
@@ -2368,6 +2934,174 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
2368
2934
  };
2369
2935
  }
2370
2936
  //#endregion
2937
+ //#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
2938
+ async function prepareCodexThreadLifecyclePreflight(params) {
2939
+ const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
2940
+ ...params.timing,
2941
+ enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
2942
+ });
2943
+ const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
2944
+ const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
2945
+ const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
2946
+ const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
2947
+ config: params.params.config,
2948
+ disableTools: params.params.disableTools,
2949
+ nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
2950
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2951
+ webSearchAllowed: params.webSearchAllowed
2952
+ })).threadConfig);
2953
+ const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
2954
+ const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
2955
+ const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
2956
+ agentId: params.agentId ?? params.params.agentId,
2957
+ agentDir: params.params.agentDir,
2958
+ allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
2959
+ toolOverrides: params.params.toolOverrides,
2960
+ onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
2961
+ serverName,
2962
+ error: formatErrorMessage(error)
2963
+ })
2964
+ });
2965
+ const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
2966
+ client: params.client,
2967
+ codexHome: params.appServer.start.env?.CODEX_HOME,
2968
+ cwd: params.cwd,
2969
+ home: params.appServer.start.env?.HOME,
2970
+ signal: params.signal,
2971
+ userProfile: params.appServer.start.env?.USERPROFILE
2972
+ }));
2973
+ const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
2974
+ version: 1,
2975
+ disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
2976
+ }) : void 0;
2977
+ const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2978
+ const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2979
+ const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
2980
+ const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
2981
+ 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));
2985
+ const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
2986
+ version: 1,
2987
+ baseInstructions: "",
2988
+ config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
2989
+ }) : void 0;
2990
+ return {
2991
+ contextEngineBinding,
2992
+ dynamicToolsContainDeferred,
2993
+ dynamicToolsFingerprint,
2994
+ environmentSelectionFingerprint,
2995
+ hostSystemAgentActive,
2996
+ legacyDynamicToolsFingerprint,
2997
+ legacyUserMcpServersFingerprint,
2998
+ lifecycleTiming,
2999
+ nativeSkillIsolation,
3000
+ nativeSkillIsolationFingerprint,
3001
+ networkProxyConfigFingerprint,
3002
+ ringZeroActive,
3003
+ ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
3004
+ ringZeroConfigFingerprint,
3005
+ ringZeroInheritedMcpServerNames,
3006
+ userMcpServersConfigPatch,
3007
+ userMcpServersFingerprint,
3008
+ webSearchThreadConfigFingerprint
3009
+ };
3010
+ }
3011
+ //#endregion
3012
+ //#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
3013
+ /** Preserves the caller's abort reason across thread ownership transitions. */
3014
+ function throwIfCodexThreadLifecycleAborted(signal) {
3015
+ if (!signal?.aborted) return;
3016
+ const reason = signal.reason;
3017
+ if (reason instanceof Error) throw reason;
3018
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
3019
+ error.name = "AbortError";
3020
+ throw error;
3021
+ }
3022
+ /** Releases consumed subscription ownership or retires an unsafe client. */
3023
+ async function releaseCodexConsumedLiveThread(options) {
3024
+ if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
3025
+ threadId: options.threadId,
3026
+ timeoutMs: 5e3
3027
+ }))) return;
3028
+ await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
3029
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, options.cause !== void 0 ? { cause: options.cause } : void 0);
3030
+ }
3031
+ /** Transfers retained-slot ownership before unconditionally releasing it. */
3032
+ async function releaseCodexRetainedLiveThread(options) {
3033
+ if (await consumeCodexAppServerLiveThread(options.client, options.threadId)) await releaseCodexConsumedLiveThread(options);
3034
+ }
3035
+ /** Reuses one safely owned, fully matching subscription on its original client. */
3036
+ 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 {};
3039
+ const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
3040
+ action: "resume",
3041
+ binding
3042
+ }) ?? {
3043
+ configPatch: params.finalConfigPatch,
3044
+ nativeHookRelayGeneration: params.nativeHookRelayGeneration
3045
+ };
3046
+ const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0;
3047
+ const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
3048
+ const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
3049
+ const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
3050
+ threadId: binding.threadId,
3051
+ authProfileId: resumeAuthProfileId,
3052
+ model: startModelSelection.model,
3053
+ modelProvider: startModelProvider,
3054
+ preserveNativeModel: false,
3055
+ appServer: params.appServer,
3056
+ dynamicTools: params.dynamicTools,
3057
+ developerInstructions: params.developerInstructions,
3058
+ config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
3059
+ nativeCodeModeEnabled: params.nativeCodeModeEnabled,
3060
+ nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
3061
+ nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
3062
+ webSearchAllowed: params.webSearchAllowed,
3063
+ hostSystemAgentActive,
3064
+ ringZeroInheritedMcpServerNames
3065
+ }));
3066
+ const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
3067
+ ...resumeParams,
3068
+ model: binding.model ?? resumeParams.model ?? null,
3069
+ requestedModel: resumeParams.model ?? null,
3070
+ modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
3071
+ requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
3072
+ }, resumeAuthProfileId, dynamicToolsFingerprint);
3073
+ if (!await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint)) return { prebuiltFinalConfigPatch };
3074
+ try {
3075
+ const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
3076
+ if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
3077
+ kind: "patch",
3078
+ threadId: binding.threadId,
3079
+ patch: { nativeHookRelayGeneration }
3080
+ }))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
3081
+ throwIfAborted();
3082
+ lifecycleTiming.mark("thread-ready");
3083
+ lifecycleTiming.logSummary({
3084
+ runId: params.params.runId,
3085
+ sessionId: params.params.sessionId,
3086
+ sessionKey: params.params.sessionKey,
3087
+ threadId: binding.threadId,
3088
+ action: "resumed"
3089
+ });
3090
+ return {
3091
+ binding: {
3092
+ ...binding,
3093
+ nativeHookRelayGeneration,
3094
+ liveThreadConfigFingerprint,
3095
+ lifecycle: { action: "resumed" }
3096
+ },
3097
+ prebuiltFinalConfigPatch
3098
+ };
3099
+ } catch (error) {
3100
+ await releaseConsumedThread(binding.threadId, error);
3101
+ throw error;
3102
+ }
3103
+ }
3104
+ //#endregion
2371
3105
  //#region extensions/codex/src/app-server/thread-supervision.ts
2372
3106
  async function materializePendingSupervisionBranch(params) {
2373
3107
  let pending = params.binding.pendingSupervisionBranch;
@@ -2457,6 +3191,25 @@ async function materializePendingSupervisionBranch(params) {
2457
3191
  modelProvider: nativeModelProvider,
2458
3192
  operation: "thread/start response"
2459
3193
  });
3194
+ if (params.provisionalAppIds?.length) try {
3195
+ await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
3196
+ client: params.client,
3197
+ threadId: finalThreadId,
3198
+ appIds: params.provisionalAppIds ?? [],
3199
+ signal: params.signal
3200
+ }));
3201
+ } catch (error) {
3202
+ if (!await discardUnattestedCodexPluginThread({
3203
+ client: params.client,
3204
+ threadId: finalThreadId,
3205
+ ephemeral: startParams.ephemeral === true
3206
+ }) || !await archiveSupervisionArtifact(params.client, probeThreadId)) {
3207
+ provisionalCleanupSafe = false;
3208
+ throw new CodexAppServerUnsafeSubscriptionError("Codex supervised plugin app attestation cleanup failed", { cause: error });
3209
+ }
3210
+ pending = await trackPendingSupervisionArtifacts(params, pending, []);
3211
+ throw error;
3212
+ }
2460
3213
  if (history.responseItems.length > 0) {
2461
3214
  await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
2462
3215
  threadId: finalThreadId,
@@ -2700,46 +3453,9 @@ async function startOrResumeThread(params) {
2700
3453
  config: params.params.config
2701
3454
  });
2702
3455
  return await params.bindingStore.withLease(bindingIdentity, async () => {
2703
- const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
2704
- ...params.timing,
2705
- enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
2706
- });
2707
- const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
2708
- const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
2709
- const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
2710
- const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
2711
- config: params.params.config,
2712
- disableTools: params.params.disableTools,
2713
- nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
2714
- nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
2715
- webSearchAllowed: params.webSearchAllowed
2716
- })).threadConfig);
2717
- const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
2718
- const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
2719
- const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
2720
- agentId: params.agentId ?? params.params.agentId,
2721
- agentDir: params.params.agentDir,
2722
- allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
2723
- onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
2724
- serverName,
2725
- error: formatErrorMessage(error)
2726
- })
2727
- });
2728
- const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2729
- const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
2730
- const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
2731
- const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
2732
- const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
2733
- if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
2734
- const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
2735
- if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
2736
- const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
2737
- version: 1,
2738
- baseInstructions: "",
2739
- config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
2740
- }) : void 0;
2741
- const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
3456
+ const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, ringZeroInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = await prepareCodexThreadLifecyclePreflight(params);
2742
3457
  let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
3458
+ const initialBoundThreadId = binding?.threadId;
2743
3459
  const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
2744
3460
  authProfileId,
2745
3461
  modelProvider,
@@ -2747,14 +3463,13 @@ async function startOrResumeThread(params) {
2747
3463
  agentDir: params.params.agentDir,
2748
3464
  config: params.params.config
2749
3465
  });
2750
- const throwIfAborted = () => {
2751
- if (!params.signal?.aborted) return;
2752
- const reason = params.signal.reason;
2753
- if (reason instanceof Error) throw reason;
2754
- const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
2755
- error.name = "AbortError";
2756
- throw error;
2757
- };
3466
+ const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
3467
+ const releaseRetainedThread = (threadId) => releaseCodexRetainedLiveThread({
3468
+ client: params.client,
3469
+ abandonClient: params.abandonClient,
3470
+ lifecycleTiming,
3471
+ threadId
3472
+ });
2758
3473
  if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
2759
3474
  if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
2760
3475
  bindingStore: params.bindingStore,
@@ -2763,13 +3478,14 @@ async function startOrResumeThread(params) {
2763
3478
  }))) throw createCodexSessionGenerationSupersededError(bindingIdentity.sessionId);
2764
3479
  }
2765
3480
  if (binding?.pendingSupervisionBranch) {
3481
+ await releaseRetainedThread(binding.threadId);
2766
3482
  const pendingBinding = binding;
2767
3483
  const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
2768
3484
  const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
2769
3485
  configPatch: params.finalConfigPatch,
2770
3486
  nativeHookRelayGeneration: params.nativeHookRelayGeneration
2771
3487
  };
2772
- const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
3488
+ const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
2773
3489
  return await materializePendingSupervisionBranch({
2774
3490
  client: params.client,
2775
3491
  abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
@@ -2787,6 +3503,7 @@ async function startOrResumeThread(params) {
2787
3503
  nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
2788
3504
  webSearchAllowed: params.webSearchAllowed,
2789
3505
  environmentSelection: params.environmentSelection,
3506
+ provisionalAppIds: pluginThreadConfig?.provisionalAppIds,
2790
3507
  signal: params.signal,
2791
3508
  throwIfAborted,
2792
3509
  lifecycleTiming,
@@ -2799,6 +3516,7 @@ async function startOrResumeThread(params) {
2799
3516
  dynamicToolsFingerprint,
2800
3517
  dynamicToolsContainDeferred,
2801
3518
  webSearchThreadConfigFingerprint,
3519
+ nativeSkillIsolationFingerprint,
2802
3520
  userMcpServersFingerprint,
2803
3521
  mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
2804
3522
  networkProxyProfileName: params.appServer.networkProxy?.profileName,
@@ -2824,6 +3542,10 @@ async function startOrResumeThread(params) {
2824
3542
  })) throw new CodexThreadBindingConflictError(current.threadId, operation);
2825
3543
  binding = void 0;
2826
3544
  };
3545
+ if (binding?.threadId && binding.nativeSkillIsolationFingerprint !== nativeSkillIsolationFingerprint) {
3546
+ embeddedAgentLog.debug("codex app-server native skill isolation changed; starting a new thread", { threadId: binding.threadId });
3547
+ await clearCurrentBinding("rotating stale native skill isolation");
3548
+ }
2827
3549
  if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
2828
3550
  embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
2829
3551
  await clearCurrentBinding("rotating a ring-zero thread binding");
@@ -3010,6 +3732,32 @@ async function startOrResumeThread(params) {
3010
3732
  await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
3011
3733
  binding = void 0;
3012
3734
  } else {
3735
+ const warmReuse = await tryReuseCodexLiveThread({
3736
+ params,
3737
+ binding,
3738
+ bindingIdentity,
3739
+ clientId,
3740
+ contextEngineBinding,
3741
+ dynamicToolsFingerprint,
3742
+ hostSystemAgentActive,
3743
+ lifecycleTiming,
3744
+ nativeSkillIsolation,
3745
+ releaseConsumedThread: (threadId, cause) => releaseCodexConsumedLiveThread({
3746
+ client: params.client,
3747
+ abandonClient: params.abandonClient,
3748
+ lifecycleTiming,
3749
+ threadId,
3750
+ cause
3751
+ }),
3752
+ ringZeroActive,
3753
+ ringZeroInheritedMcpServerNames,
3754
+ startModelProvider,
3755
+ startModelSelection,
3756
+ throwIfAborted,
3757
+ userMcpServersConfigPatch
3758
+ });
3759
+ if (warmReuse.binding) return warmReuse.binding;
3760
+ await releaseRetainedThread(binding.threadId);
3013
3761
  const resumed = await resumeExistingCodexThread(params, {
3014
3762
  binding,
3015
3763
  bindingIdentity,
@@ -3019,6 +3767,7 @@ async function startOrResumeThread(params) {
3019
3767
  dynamicToolsFingerprint,
3020
3768
  dynamicToolsContainDeferred,
3021
3769
  webSearchThreadConfigFingerprint,
3770
+ nativeSkillIsolationFingerprint,
3022
3771
  userMcpServersFingerprint,
3023
3772
  ringZeroConfigFingerprint,
3024
3773
  ringZeroClientInstanceId,
@@ -3028,13 +3777,16 @@ async function startOrResumeThread(params) {
3028
3777
  hostSystemAgentActive,
3029
3778
  ringZeroActive,
3030
3779
  ringZeroInheritedMcpServerNames,
3780
+ nativeSkillIsolation,
3031
3781
  lifecycleTiming,
3032
3782
  normalizeBindingModelProvider,
3033
3783
  throwIfAborted,
3034
- clearCurrentBinding
3784
+ clearCurrentBinding,
3785
+ prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch
3035
3786
  });
3036
3787
  if (resumed) return resumed;
3037
3788
  }
3789
+ if (initialBoundThreadId) await releaseRetainedThread(initialBoundThreadId);
3038
3790
  return await startFreshCodexThread(params, {
3039
3791
  bindingIdentity,
3040
3792
  startModelSelection,
@@ -3043,6 +3795,7 @@ async function startOrResumeThread(params) {
3043
3795
  dynamicToolsFingerprint,
3044
3796
  dynamicToolsContainDeferred,
3045
3797
  webSearchThreadConfigFingerprint,
3798
+ nativeSkillIsolationFingerprint,
3046
3799
  userMcpServersFingerprint,
3047
3800
  ringZeroConfigFingerprint,
3048
3801
  ringZeroClientInstanceId,
@@ -3052,6 +3805,7 @@ async function startOrResumeThread(params) {
3052
3805
  hostSystemAgentActive,
3053
3806
  ringZeroActive,
3054
3807
  ringZeroInheritedMcpServerNames,
3808
+ nativeSkillIsolation,
3055
3809
  lifecycleTiming,
3056
3810
  normalizeBindingModelProvider,
3057
3811
  throwIfAborted,
@@ -3230,4 +3984,4 @@ function joinPresentSections(...sections) {
3230
3984
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
3231
3985
  }
3232
3986
  //#endregion
3233
- export { readHookOutputEntries as $, resolveCodexDynamicToolsLoading as A, resolveCodexContextEngineProjectionReserveTokens as B, readCodexSupportedReasoningEfforts as C, isForcedPrivateQaCodexRuntime as D, filterCodexDynamicToolsWithOpenClawShell as E, buildContextEngineBinding as F, mergeCodexThreadConfigs as G, buildCodexPluginThreadConfig as H, isContextEngineBindingCompatible as I, pluginReadParams as J, shouldBuildCodexPluginThreadConfig as K, fitCodexProjectedContextForTurnStart as L, areCodexDynamicToolFingerprintsCompatible as M, codexDynamicToolsFingerprint as N, isSystemAgentOnlyCodexDynamicToolAllowlist as O, codexLegacyDynamicToolsFingerprint as P, readCodexErrorNotificationMessage as Q, projectContextEngineAssemblyForCodex as R, resolveReasoningEffort as S, filterCodexDynamicTools as T, buildCodexPluginThreadConfigInputFingerprint as U, buildCodexPluginAppsConfigPatchFromPolicyContext as V, buildCodexPluginThreadConfigTimeoutFallback as W, extractRawAssistantText as X, resolveRecoverableCodexPluginConfigKeys as Y, normalizeNonEmptyString as Z, CODEX_NATIVE_PERSONALITY_NONE as _, sanitizeCodexHistoryImagePayloads as a, readNullableString as at, resolveCodexAppServerThreadModelSelection as b, resumeCodexAppServerThread as c, splitPlanText as ct, buildCodexRingZeroThreadConfigPatch as d, readItem as et, buildCodexRuntimeThreadConfig as f, buildDeveloperInstructions as g, resolveCodexWebSearchPlan as h, invalidInlineImageText as i, readNonNegativeInteger as it, resolveCodexDynamicToolsLoadingForRuntime as j, normalizeCodexDynamicToolName as k, assertCodexRingZeroHasNoManagedHooks as l, buildCodexNativeWebSearchThreadConfig as m, buildTurnStartParams as n, readNonEmptyString as nt, sanitizeInlineImageDataUrl$1 as o, readNumber as ot, readCodexInheritedMcpServerNames as p, ensureCodexPluginActivation as q, buildCodexUserInput as r, readNonEmptyStringArray as rt, startOrResumeThread as s, readString as st, buildTurnCollaborationMode as t, readItemString as tt, attestCodexRingZeroThreadHasNoMcpServers as u, resolveCodexAppServerModelProvider as v, isCodexAppServerProfilerEnabled as w, resolveCodexBindingModelProviderFallback as x, resolveCodexAppServerRequestModelSelection as y, resolveCodexContextEngineProjectionMaxChars as z };
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 };