@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.
- package/dist/{app-server-policy-BmDgseo_.js → app-server-policy-BSaO2JIT.js} +1 -1
- package/dist/{attempt-notifications-CMR2_fmR.js → attempt-notifications-AP-a-Q5k.js} +1 -1
- package/dist/{bounded-turn-FEbUXoYT.js → bounded-turn-D2ZAgZVz.js} +29 -22
- package/dist/{capabilities-BHRSI6Cn.js → capabilities-BFDTVYr4.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-ico4ZFnk.js → command-formatters-C336eRVS.js} +2 -2
- package/dist/{command-handlers-CRmnwI2N.js → command-handlers-DAHUSry9.js} +12 -12
- package/dist/{compact-dEN49UH6.js → compact-D_uvHurS.js} +5 -5
- package/dist/{computer-use-BjGylO-E.js → computer-use-CYEc8U3w.js} +111 -113
- package/dist/{conversation-control-DE7d9DOj.js → conversation-control-xsIeXbo4.js} +6 -6
- package/dist/doctor-contract-api.js +1 -1
- package/dist/{dynamic-tools-LIt9ojep.js → dynamic-tools-D2gYkSBO.js} +438 -51
- package/dist/effective-mcp-catalog-Bbd98Inw.js +114 -0
- package/dist/{event-projector-usage-tvgb1NCX.js → event-projector-usage-Xp4AlR2k.js} +20 -3
- package/dist/harness.js +13 -9
- package/dist/index.js +131 -108
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{models-BA01NEez.js → models-BBMKU4jA.js} +2 -2
- package/dist/{notification-correlation-Bj_Ak_jI.js → notification-correlation-CU6_AnOb.js} +3 -3
- package/dist/{plugin-app-cache-key-DSXX-rXO.js → plugin-app-cache-key-DztPafOA.js} +132 -39
- package/dist/{rate-limits-S_PXs0ul.js → rate-limits-CU-gVCCS.js} +1 -1
- package/dist/{request-CXpPennK.js → request-qKh331yu.js} +3 -3
- package/dist/{run-attempt-C5GTPJOE.js → run-attempt-CY9WlDLc.js} +677 -199
- package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-CmtG_t1L.js} +90 -24
- package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-NuhpXmPB.js} +4 -0
- package/dist/{session-binding-DlVQ-Pxs.js → session-binding-BXpo9ymA.js} +60 -7
- package/dist/{session-catalog-CP2oCcEb.js → session-catalog-DyOgR4WH.js} +268 -44
- package/dist/{session-cli-CqZ0Fp2a.js → session-cli-2PZaFLRh.js} +1 -1
- package/dist/{session-upstream-marker-QE-m5QFG.js → session-upstream-marker-B6U7tLw5.js} +1 -1
- package/dist/{settled-turn-finalizer-CA_2QXIE.js → settled-turn-finalizer-CXY2Z9dT.js} +19 -6
- package/dist/{shared-client-BVKE40c-.js → shared-client-DTbvH-XV.js} +166 -49
- package/dist/{side-question-CO12sHxo.js → side-question-_ulBk1AL.js} +10 -10
- package/dist/{thread-lifecycle-DRg_FdzC.js → thread-lifecycle-DF53ogiA.js} +968 -214
- package/dist/{protocol-validators-Dm4eXZmt.js → transcript-mirror-DzsdNCf5.js} +589 -2
- package/dist/{upstream-session-fork-_Vrz4WJr.js → upstream-session-fork-Dy4Xvuoj.js} +3 -3
- package/dist/{usage-UpVbdX5G.js → usage-CQ6GhD5-.js} +3 -3
- package/dist/{web-search-provider.runtime-CaQMYDbp.js → web-search-provider.runtime-CCFhg2z5.js} +3 -3
- package/package.json +6 -7
- package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
- package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
- package/dist/realtime-voice-api.js +0 -2
- package/dist/transcript-mirror-uKA-A4tB.js +0 -550
|
@@ -1,21 +1,147 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
if (!
|
|
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
|
|
225
|
-
|
|
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) =>
|
|
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" ? "
|
|
236
|
-
const
|
|
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 === "
|
|
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.
|
|
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:
|
|
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
|
|
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) =>
|
|
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
|
|
766
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
502
767
|
forceRefetch: false,
|
|
503
768
|
reason: "initial_missing",
|
|
504
|
-
targetAppIds:
|
|
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
|
|
809
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
543
810
|
forceRefetch: true,
|
|
544
811
|
reason: postInstallRefreshRequired ? "post_install" : "deferred_missing",
|
|
545
|
-
targetAppIds:
|
|
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 (
|
|
562
|
-
await
|
|
829
|
+
if (shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory)) {
|
|
830
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
563
831
|
forceRefetch: true,
|
|
564
832
|
reason: "not_ready_plugin_apps",
|
|
565
|
-
targetAppIds:
|
|
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
|
|
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
|
-
|
|
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
|
|
882
|
+
for (const app of resolveCodexThreadConfigAppsForRecord({
|
|
601
883
|
record,
|
|
602
884
|
inventory
|
|
603
885
|
})) {
|
|
604
|
-
|
|
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
|
-
|
|
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 =
|
|
742
|
-
|
|
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
|
-
}))
|
|
747
|
-
const
|
|
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
|
-
|
|
760
|
-
const
|
|
761
|
-
|
|
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) &&
|
|
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(
|
|
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
|
-
|
|
1611
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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 {
|
|
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 };
|