@openclaw/codex 2026.7.2-beta.4 → 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-BO3jYBkv.js → app-server-policy-BSaO2JIT.js} +1 -1
- package/dist/{attempt-notifications-DdmnBMUp.js → attempt-notifications-AP-a-Q5k.js} +1 -1
- package/dist/{bounded-turn-CaQL96kg.js → bounded-turn-D2ZAgZVz.js} +54 -24
- package/dist/{capabilities-CLICvTxf.js → capabilities-BFDTVYr4.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-DgADqnHa.js → command-formatters-C336eRVS.js} +2 -2
- package/dist/{command-handlers-l_lVPNlB.js → command-handlers-DAHUSry9.js} +1233 -1222
- package/dist/{compact-CC5KhBzX.js → compact-D_uvHurS.js} +8 -8
- package/dist/{computer-use-Ip1-QGa-.js → computer-use-CYEc8U3w.js} +123 -115
- package/dist/{conversation-control-sNPsbTCP.js → conversation-control-xsIeXbo4.js} +6 -6
- package/dist/doctor-contract-api.js +24 -86
- package/dist/{dynamic-tools-CQI3UwqY.js → dynamic-tools-D2gYkSBO.js} +482 -91
- package/dist/effective-mcp-catalog-Bbd98Inw.js +114 -0
- package/dist/{event-projector-usage-D6tE7_qD.js → event-projector-usage-Xp4AlR2k.js} +20 -3
- package/dist/harness.js +19 -14
- package/dist/index.js +292 -328
- package/dist/media-understanding-provider.js +6 -1
- package/dist/{models-BNeMlHgg.js → models-BBMKU4jA.js} +2 -2
- package/dist/{notification-correlation-BGLixv5_.js → notification-correlation-CU6_AnOb.js} +3 -3
- package/dist/{plugin-app-cache-key-CvzvmBRz.js → plugin-app-cache-key-DztPafOA.js} +135 -42
- package/dist/{rate-limits-D0IJT-a6.js → rate-limits-CU-gVCCS.js} +1 -1
- package/dist/{request-BTccUTD3.js → request-qKh331yu.js} +3 -3
- package/dist/{run-attempt--858NXDl.js → run-attempt-CY9WlDLc.js} +1060 -402
- 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-ZSMX1omP.js → session-binding-BXpo9ymA.js} +1006 -891
- package/dist/{session-catalog-DsKaezgR.js → session-catalog-DyOgR4WH.js} +289 -56
- package/dist/{session-cli-CYJTfRLG.js → session-cli-2PZaFLRh.js} +2 -2
- package/dist/{session-upstream-marker-D7XlI9us.js → session-upstream-marker-B6U7tLw5.js} +1 -1
- package/dist/{settled-turn-finalizer-DHltNgtF.js → settled-turn-finalizer-CXY2Z9dT.js} +19 -6
- package/dist/{shared-client-wmgNFNYp.js → shared-client-DTbvH-XV.js} +1993 -1720
- package/dist/{side-question-BfFZCsJ-.js → side-question-_ulBk1AL.js} +12 -10
- package/dist/{thread-lifecycle-cqrOlJkq.js → thread-lifecycle-DF53ogiA.js} +980 -346
- package/dist/{transcript-mirror-2p7kl6e4.js → transcript-mirror-DzsdNCf5.js} +80 -41
- package/dist/{upstream-session-fork-vPjwkT1i.js → upstream-session-fork-Dy4Xvuoj.js} +3 -3
- package/dist/{usage-DM_I3mPT.js → usage-CQ6GhD5-.js} +3 -3
- package/dist/{web-search-provider.runtime-Bx6HAc_k.js → web-search-provider.runtime-CCFhg2z5.js} +3 -3
- package/package.json +6 -8
- package/npm-shrinkwrap.json +0 -202
- package/dist/{auth-binding-C1czuHPA.js → auth-binding-B4FfbcCg.js} +1 -1
|
@@ -1,19 +1,147 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { _ as assertCodexThreadStartResponse, a as projectBoundedCodexThreadHistory, g as assertCodexThreadResumeResponse, h as assertCodexThreadForkResponse } from "./transcript-mirror-
|
|
4
|
-
import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-
|
|
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";
|
|
5
5
|
import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
|
|
6
|
-
import
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
7
9
|
import * as crypto$1 from "node:crypto";
|
|
8
10
|
import crypto from "node:crypto";
|
|
11
|
+
import os from "node:os";
|
|
9
12
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
10
|
-
import {
|
|
13
|
+
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
11
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";
|
|
12
17
|
import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
13
18
|
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
14
|
-
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
15
19
|
import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
|
|
16
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
|
|
17
145
|
//#region extensions/codex/src/app-server/event-projector-values.ts
|
|
18
146
|
function readString(record, key) {
|
|
19
147
|
const value = record[key];
|
|
@@ -96,6 +224,7 @@ function readItem(value) {
|
|
|
96
224
|
* plugin-owned apps can be exposed to a native Codex thread.
|
|
97
225
|
*/
|
|
98
226
|
const CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME = `${CODEX_PLUGINS_MARKETPLACE_NAME}-remote`;
|
|
227
|
+
const CODEX_PLUGINS_API_MARKETPLACE_NAME = "openai-api-curated";
|
|
99
228
|
/** Reads configured Codex plugin state and maps owned apps to readiness diagnostics. */
|
|
100
229
|
async function readCodexPluginInventory(params) {
|
|
101
230
|
const policy = params.policy ?? resolveCodexPluginsPolicy(params.pluginConfig);
|
|
@@ -108,21 +237,11 @@ async function readCodexPluginInventory(params) {
|
|
|
108
237
|
}]
|
|
109
238
|
};
|
|
110
239
|
const appInventory = readCachedAppInventory(params);
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
kind: "listed",
|
|
117
|
-
response: await listCodexPluginMetadata(params, "workspace-directory", {
|
|
118
|
-
cwds: [],
|
|
119
|
-
marketplaceKinds: [CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME]
|
|
120
|
-
})
|
|
121
|
-
};
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if (!(error instanceof CodexAppServerRpcError)) throw error;
|
|
124
|
-
workspaceListResult = { kind: "rejected" };
|
|
125
|
-
}
|
|
240
|
+
const installedPlugins = await readInstalledCodexPluginMetadata({
|
|
241
|
+
...params,
|
|
242
|
+
policy
|
|
243
|
+
});
|
|
244
|
+
let curatedCatalog;
|
|
126
245
|
const diagnostics = [];
|
|
127
246
|
const records = [];
|
|
128
247
|
if (appInventory?.state === "missing") diagnostics.push({
|
|
@@ -134,10 +253,15 @@ async function readCodexPluginInventory(params) {
|
|
|
134
253
|
message: "Cached Codex app inventory is stale; using stale app readiness and refreshing."
|
|
135
254
|
});
|
|
136
255
|
for (const pluginPolicy of policy.pluginPolicies) {
|
|
137
|
-
if (!pluginPolicy.enabled) continue;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
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))) {
|
|
141
265
|
diagnostics.push({
|
|
142
266
|
code: "marketplace_missing",
|
|
143
267
|
plugin: pluginPolicy,
|
|
@@ -145,7 +269,6 @@ async function readCodexPluginInventory(params) {
|
|
|
145
269
|
});
|
|
146
270
|
continue;
|
|
147
271
|
}
|
|
148
|
-
const resolvedPlugin = pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(listed, pluginPolicy.pluginName) : findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
|
|
149
272
|
if (!resolvedPlugin) {
|
|
150
273
|
diagnostics.push({
|
|
151
274
|
code: "plugin_missing",
|
|
@@ -181,7 +304,7 @@ async function readCodexPluginInventory(params) {
|
|
|
181
304
|
policy: pluginPolicy,
|
|
182
305
|
summary,
|
|
183
306
|
...detail ? { detail } : {},
|
|
184
|
-
activationRequired: !summary.installed || !summary.enabled,
|
|
307
|
+
activationRequired: pluginPolicy.enabled && (!summary.installed || !summary.enabled),
|
|
185
308
|
authRequired: apps.some((app) => app.needsAuth || !app.accessible),
|
|
186
309
|
appOwnership,
|
|
187
310
|
ownedAppIds,
|
|
@@ -216,24 +339,41 @@ function resolveRecoverableCodexPluginConfigKeys(params) {
|
|
|
216
339
|
return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
|
|
217
340
|
pluginPolicy,
|
|
218
341
|
metadataCache: params.metadataCache,
|
|
219
|
-
appCacheKey: params.appCacheKey
|
|
342
|
+
appCacheKey: params.appCacheKey,
|
|
343
|
+
configCwd: params.configCwd
|
|
220
344
|
})).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
|
|
221
345
|
}
|
|
222
|
-
async function listCodexPluginMetadata(params
|
|
223
|
-
|
|
346
|
+
async function listCodexPluginMetadata(params) {
|
|
347
|
+
const requestParams = {};
|
|
348
|
+
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
|
|
224
349
|
return (await params.metadataCache.load({
|
|
225
350
|
appCacheKey: params.appCacheKey,
|
|
226
|
-
queryKind,
|
|
351
|
+
queryKind: "curated-global",
|
|
227
352
|
requestParams,
|
|
228
353
|
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
229
|
-
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
|
+
})
|
|
230
369
|
})).response;
|
|
231
370
|
}
|
|
232
371
|
function isSettledMissingPluginPolicy(params) {
|
|
233
|
-
const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "
|
|
234
|
-
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;
|
|
235
375
|
if (!listed) return false;
|
|
236
|
-
if (queryKind === "
|
|
376
|
+
if (queryKind === "installed") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
237
377
|
return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
238
378
|
}
|
|
239
379
|
function readCachedAppInventory(params) {
|
|
@@ -248,7 +388,7 @@ function readCachedAppInventory(params) {
|
|
|
248
388
|
}
|
|
249
389
|
async function readPluginDetail(params, marketplace, pluginPolicy, summary, diagnostics) {
|
|
250
390
|
if (params.readPluginDetails === false) return;
|
|
251
|
-
if (marketplace.
|
|
391
|
+
if (marketplace.remoteMarketplaceName && !summary.remotePluginId) {
|
|
252
392
|
diagnostics.push({
|
|
253
393
|
code: "plugin_detail_unavailable",
|
|
254
394
|
plugin: pluginPolicy,
|
|
@@ -297,7 +437,7 @@ function resolveOwnedApps(params) {
|
|
|
297
437
|
name: app.name,
|
|
298
438
|
accessible: info.isAccessible,
|
|
299
439
|
enabled: info.isEnabled,
|
|
300
|
-
needsAuth:
|
|
440
|
+
needsAuth: !info.isAccessible
|
|
301
441
|
};
|
|
302
442
|
}).toSorted((left, right) => left.id.localeCompare(right.id));
|
|
303
443
|
}
|
|
@@ -335,9 +475,9 @@ function marketplaceRef(marketplace, name) {
|
|
|
335
475
|
...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
|
|
336
476
|
};
|
|
337
477
|
}
|
|
338
|
-
/** True for
|
|
478
|
+
/** True for any supported OpenAI curated marketplace wire name, matching Codex's own curated predicate. */
|
|
339
479
|
function isOpenAiCuratedMarketplace(marketplace) {
|
|
340
|
-
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;
|
|
341
481
|
}
|
|
342
482
|
//#endregion
|
|
343
483
|
//#region extensions/codex/src/app-server/plugin-activation.ts
|
|
@@ -350,6 +490,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
350
490
|
if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
|
|
351
491
|
return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
|
|
352
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.` });
|
|
353
494
|
if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
|
|
354
495
|
identity: params.identity,
|
|
355
496
|
ok: true,
|
|
@@ -368,6 +509,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
368
509
|
appCache: params.appCache,
|
|
369
510
|
appCacheKey: params.appCacheKey,
|
|
370
511
|
metadataCache: params.metadataCache,
|
|
512
|
+
deferAppInventoryRefresh: params.deferAppInventoryRefresh,
|
|
371
513
|
targetAppIds: params.targetAppIds
|
|
372
514
|
});
|
|
373
515
|
refreshDiagnostics.push(...refreshResult.diagnostics);
|
|
@@ -389,7 +531,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
389
531
|
/** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
|
|
390
532
|
async function refreshCodexPluginRuntimeState(params) {
|
|
391
533
|
const diagnostics = [];
|
|
392
|
-
await listCuratedCodexPluginMetadata(params);
|
|
534
|
+
await listCuratedCodexPluginMetadata(params, { forceRefetch: true });
|
|
393
535
|
await params.request("skills/list", {
|
|
394
536
|
cwds: [],
|
|
395
537
|
forceReload: true
|
|
@@ -401,7 +543,8 @@ async function refreshCodexPluginRuntimeState(params) {
|
|
|
401
543
|
}
|
|
402
544
|
await params.request("config/mcpServer/reload", void 0);
|
|
403
545
|
if (params.appCache && params.appCacheKey) {
|
|
404
|
-
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 };
|
|
405
548
|
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
406
549
|
try {
|
|
407
550
|
await params.appCache.refreshNow({
|
|
@@ -416,15 +559,15 @@ async function refreshCodexPluginRuntimeState(params) {
|
|
|
416
559
|
}
|
|
417
560
|
return { diagnostics };
|
|
418
561
|
}
|
|
419
|
-
async function listCuratedCodexPluginMetadata(params) {
|
|
420
|
-
const requestParams = {};
|
|
562
|
+
async function listCuratedCodexPluginMetadata(params, options = {}) {
|
|
563
|
+
const requestParams = options.forceRefetch ? { forceRefetch: true } : {};
|
|
421
564
|
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
|
|
422
565
|
return (await params.metadataCache.load({
|
|
423
566
|
appCacheKey: params.appCacheKey,
|
|
424
567
|
queryKind: "curated-global",
|
|
425
568
|
requestParams,
|
|
426
569
|
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
427
|
-
cacheable: (response) =>
|
|
570
|
+
cacheable: (response) => response.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
428
571
|
})).response;
|
|
429
572
|
}
|
|
430
573
|
function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
|
|
@@ -437,6 +580,129 @@ function activationFailure(identity, reason, diagnostic, extraDiagnostics = [])
|
|
|
437
580
|
};
|
|
438
581
|
}
|
|
439
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
|
|
440
706
|
//#region extensions/codex/src/app-server/plugin-thread-config.ts
|
|
441
707
|
/**
|
|
442
708
|
* Builds Codex thread config patches that expose only policy-approved apps
|
|
@@ -490,16 +756,17 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
490
756
|
request: params.request,
|
|
491
757
|
appCache,
|
|
492
758
|
appCacheKey: params.appCacheKey,
|
|
759
|
+
configCwd: params.configCwd,
|
|
493
760
|
metadataCache: params.metadataCache,
|
|
494
761
|
nowMs: params.nowMs,
|
|
495
762
|
suppressAppInventoryRefresh: true
|
|
496
763
|
}) : emptyCodexPluginInventory(policy);
|
|
497
764
|
const appInventoryRefreshDeferredForActivation = inventory.records.some((record) => record.activationRequired) && shouldRefreshMissingAppInventory(params, policy, inventory);
|
|
498
765
|
if (shouldWaitForInitialAppInventory(params, policy, inventory)) {
|
|
499
|
-
await
|
|
766
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
500
767
|
forceRefetch: false,
|
|
501
768
|
reason: "initial_missing",
|
|
502
|
-
targetAppIds:
|
|
769
|
+
targetAppIds: collectCodexPluginOwnedAppIds(inventory)
|
|
503
770
|
});
|
|
504
771
|
inventory = await readCodexPluginInventory({
|
|
505
772
|
pluginConfig: params.pluginConfig,
|
|
@@ -507,6 +774,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
507
774
|
request: params.request,
|
|
508
775
|
appCache,
|
|
509
776
|
appCacheKey: params.appCacheKey,
|
|
777
|
+
configCwd: params.configCwd,
|
|
510
778
|
metadataCache: params.metadataCache,
|
|
511
779
|
nowMs: params.nowMs
|
|
512
780
|
});
|
|
@@ -525,6 +793,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
525
793
|
appCache,
|
|
526
794
|
appCacheKey: params.appCacheKey,
|
|
527
795
|
metadataCache: params.metadataCache,
|
|
796
|
+
deferAppInventoryRefresh: true,
|
|
528
797
|
targetAppIds: record.ownedAppIds
|
|
529
798
|
});
|
|
530
799
|
activationResults.push(activation);
|
|
@@ -537,10 +806,10 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
537
806
|
const postInstallRefreshRequired = activationResults.some((activation) => activation.ok && activation.installAttempted);
|
|
538
807
|
const deferredMissingRefreshRequired = appInventoryRefreshDeferredForActivation && !postInstallRefreshRequired && shouldRefreshMissingAppInventory(params, policy, inventory);
|
|
539
808
|
if (postInstallRefreshRequired || deferredMissingRefreshRequired) {
|
|
540
|
-
await
|
|
809
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
541
810
|
forceRefetch: true,
|
|
542
811
|
reason: postInstallRefreshRequired ? "post_install" : "deferred_missing",
|
|
543
|
-
targetAppIds:
|
|
812
|
+
targetAppIds: collectCodexPluginOwnedAppIds(inventory)
|
|
544
813
|
});
|
|
545
814
|
inventory = await readCodexPluginInventory({
|
|
546
815
|
pluginConfig: params.pluginConfig,
|
|
@@ -548,6 +817,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
548
817
|
request: params.request,
|
|
549
818
|
appCache,
|
|
550
819
|
appCacheKey: params.appCacheKey,
|
|
820
|
+
configCwd: params.configCwd,
|
|
551
821
|
metadataCache: params.metadataCache,
|
|
552
822
|
nowMs: params.nowMs
|
|
553
823
|
});
|
|
@@ -556,11 +826,11 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
556
826
|
appCacheKey: params.appCacheKey
|
|
557
827
|
});
|
|
558
828
|
}
|
|
559
|
-
if (
|
|
560
|
-
await
|
|
829
|
+
if (shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory)) {
|
|
830
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
561
831
|
forceRefetch: true,
|
|
562
832
|
reason: "not_ready_plugin_apps",
|
|
563
|
-
targetAppIds:
|
|
833
|
+
targetAppIds: collectCodexPluginOwnedAppIds(inventory)
|
|
564
834
|
});
|
|
565
835
|
inventory = await readCodexPluginInventory({
|
|
566
836
|
pluginConfig: params.pluginConfig,
|
|
@@ -568,6 +838,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
568
838
|
request: params.request,
|
|
569
839
|
appCache,
|
|
570
840
|
appCacheKey: params.appCacheKey,
|
|
841
|
+
configCwd: params.configCwd,
|
|
571
842
|
metadataCache: params.metadataCache,
|
|
572
843
|
nowMs: params.nowMs
|
|
573
844
|
});
|
|
@@ -576,12 +847,13 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
576
847
|
appCacheKey: params.appCacheKey
|
|
577
848
|
});
|
|
578
849
|
}
|
|
579
|
-
const accountAppsResult = policy.allowAllPlugins ? await
|
|
850
|
+
const accountAppsResult = policy.allowAllPlugins ? await readCodexThreadAdmissibleAccountApps(params, appCache) : { apps: [] };
|
|
580
851
|
const diagnostics = [
|
|
581
852
|
...inventory.diagnostics,
|
|
582
853
|
...activationDiagnostics,
|
|
583
854
|
...accountAppsResult.diagnostic ? [accountAppsResult.diagnostic] : []
|
|
584
855
|
];
|
|
856
|
+
const provisionalAppIds = /* @__PURE__ */ new Set();
|
|
585
857
|
const apps = { _default: {
|
|
586
858
|
enabled: false,
|
|
587
859
|
destructive_enabled: false,
|
|
@@ -589,17 +861,30 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
589
861
|
} };
|
|
590
862
|
const policyApps = {};
|
|
591
863
|
const pluginAppIds = {};
|
|
592
|
-
|
|
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
|
+
});
|
|
593
876
|
for (const record of inventory.records) {
|
|
877
|
+
if (!record.policy.enabled) continue;
|
|
594
878
|
const activation = activationResults.find((item) => item.identity.configKey === record.policy.configKey);
|
|
595
879
|
if (activation?.ok === false || record.activationRequired && !activation?.ok) continue;
|
|
596
880
|
if (record.appOwnership !== "proven") continue;
|
|
597
881
|
pluginAppIds[record.policy.configKey] = [...record.ownedAppIds].toSorted();
|
|
598
|
-
for (const app of
|
|
882
|
+
for (const app of resolveCodexThreadConfigAppsForRecord({
|
|
599
883
|
record,
|
|
600
884
|
inventory
|
|
601
885
|
})) {
|
|
602
|
-
|
|
886
|
+
const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await (configForAppAdmission ??= readCodexConfigForAppAdmission(params));
|
|
887
|
+
if (!admissionConfig || resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) {
|
|
603
888
|
diagnostics.push({
|
|
604
889
|
code: "app_not_ready",
|
|
605
890
|
plugin: record.policy,
|
|
@@ -610,10 +895,12 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
610
895
|
if (record.policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
|
|
611
896
|
request: params.request,
|
|
612
897
|
configCwd: params.configCwd,
|
|
898
|
+
config: admissionConfig.config,
|
|
613
899
|
plugin: record.policy,
|
|
614
900
|
app,
|
|
615
901
|
diagnostics
|
|
616
902
|
})) continue;
|
|
903
|
+
provisionalAppIds.add(app.id);
|
|
617
904
|
apps[app.id] = buildEnabledAppConfig(record.policy);
|
|
618
905
|
policyApps[app.id] = {
|
|
619
906
|
configKey: record.policy.configKey,
|
|
@@ -625,15 +912,27 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
625
912
|
};
|
|
626
913
|
}
|
|
627
914
|
}
|
|
628
|
-
for (const app of accountAppsResult.apps) {
|
|
915
|
+
for (const app of unresolvedDisabledPluginOwnership ? [] : accountAppsResult.apps) {
|
|
629
916
|
if (pluginOwnedAppIds.has(app.id)) continue;
|
|
630
|
-
|
|
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);
|
|
631
928
|
if (policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
|
|
632
929
|
request: params.request,
|
|
633
930
|
configCwd: params.configCwd,
|
|
931
|
+
config: admissionConfig.config,
|
|
634
932
|
app: accountApp,
|
|
635
933
|
diagnostics
|
|
636
934
|
})) continue;
|
|
935
|
+
provisionalAppIds.add(app.id);
|
|
637
936
|
apps[app.id] = buildEnabledAppConfig(policy);
|
|
638
937
|
policyApps[app.id] = {
|
|
639
938
|
source: "account",
|
|
@@ -648,6 +947,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
648
947
|
return {
|
|
649
948
|
enabled: true,
|
|
650
949
|
configPatch,
|
|
950
|
+
...provisionalAppIds.size > 0 ? { provisionalAppIds: Array.from(provisionalAppIds).toSorted() } : {},
|
|
651
951
|
fingerprint: fingerprintJson({
|
|
652
952
|
version: CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION,
|
|
653
953
|
inputFingerprint,
|
|
@@ -736,13 +1036,22 @@ function buildPluginAppPolicyContext(apps, pluginAppIds) {
|
|
|
736
1036
|
}
|
|
737
1037
|
async function clearPersistedAppToolApprovalOverrides(params) {
|
|
738
1038
|
try {
|
|
739
|
-
const overrideNames =
|
|
740
|
-
|
|
1039
|
+
const overrideNames = readPersistedAppToolApprovalOverrideNames(params.config, params.app);
|
|
1040
|
+
if (overrideNames.length === 0) return true;
|
|
1041
|
+
const edits = overrideNames.map((toolName) => ({
|
|
741
1042
|
keyPath: `apps.${quoteConfigKeyPathSegment(params.app.id)}.tools.${quoteConfigKeyPathSegment(toolName)}.approval_mode`,
|
|
742
1043
|
value: null,
|
|
743
1044
|
mergeStrategy: "replace"
|
|
744
|
-
}))
|
|
745
|
-
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);
|
|
746
1055
|
if (remainingOverrideNames.length > 0) throw new Error(`effective approval overrides remain for ${remainingOverrideNames.join(", ")}`);
|
|
747
1056
|
return true;
|
|
748
1057
|
} catch (error) {
|
|
@@ -754,24 +1063,15 @@ async function clearPersistedAppToolApprovalOverrides(params) {
|
|
|
754
1063
|
return false;
|
|
755
1064
|
}
|
|
756
1065
|
}
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
...params.configCwd ? { cwd: params.configCwd } : {}
|
|
761
|
-
});
|
|
762
|
-
const config = isJsonObject(response) ? response.config : void 0;
|
|
763
|
-
const appsRoot = isJsonObject(config) ? config.apps : void 0;
|
|
764
|
-
const nestedApps = isJsonObject(appsRoot) ? appsRoot.apps : void 0;
|
|
765
|
-
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;
|
|
766
1069
|
const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
|
|
767
1070
|
if (!isJsonObject(tools)) return [];
|
|
768
1071
|
return Object.entries(tools).filter(([, value]) => hasPersistedToolApprovalOverride(value)).map(([toolName]) => toolName).toSorted();
|
|
769
1072
|
}
|
|
770
1073
|
function hasPersistedToolApprovalOverride(value) {
|
|
771
|
-
return isJsonObject(value) &&
|
|
772
|
-
}
|
|
773
|
-
function isOverriddenConfigWriteResponse(response) {
|
|
774
|
-
return isJsonObject(response) && response.status === "okOverridden";
|
|
1074
|
+
return isJsonObject(value) && value.approval_mode !== void 0;
|
|
775
1075
|
}
|
|
776
1076
|
function quoteConfigKeyPathSegment(segment) {
|
|
777
1077
|
return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
|
|
@@ -783,30 +1083,6 @@ function shouldWaitForInitialAppInventory(params, policy, inventory) {
|
|
|
783
1083
|
function shouldRefreshMissingAppInventory(params, policy, inventory) {
|
|
784
1084
|
return Boolean(params.appCacheKey && policy.pluginPolicies.some((plugin) => plugin.enabled) && inventory.appInventory?.state === "missing");
|
|
785
1085
|
}
|
|
786
|
-
async function refreshAppInventoryNow(params, appCache, options = {}) {
|
|
787
|
-
const appCacheKey = params.appCacheKey;
|
|
788
|
-
if (!appCacheKey) return;
|
|
789
|
-
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
790
|
-
try {
|
|
791
|
-
return await appCache.refreshNow({
|
|
792
|
-
key: appCacheKey,
|
|
793
|
-
request,
|
|
794
|
-
nowMs: params.nowMs,
|
|
795
|
-
forceRefetch: options.forceRefetch,
|
|
796
|
-
targetAppIds: options.targetAppIds
|
|
797
|
-
});
|
|
798
|
-
} catch (error) {
|
|
799
|
-
embeddedAgentLog.warn("codex plugin thread config app inventory refresh failed", {
|
|
800
|
-
reason: options.reason,
|
|
801
|
-
forceRefetch: options.forceRefetch === true,
|
|
802
|
-
error: serializeCodexAppInventoryError(error)
|
|
803
|
-
});
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
function collectInventoryOwnedAppIds(inventory) {
|
|
808
|
-
return Array.from(new Set(inventory.records.flatMap((record) => record.ownedAppIds).filter(Boolean))).toSorted();
|
|
809
|
-
}
|
|
810
1086
|
function emptyCodexPluginInventory(policy) {
|
|
811
1087
|
return {
|
|
812
1088
|
policy,
|
|
@@ -814,42 +1090,6 @@ function emptyCodexPluginInventory(policy) {
|
|
|
814
1090
|
diagnostics: []
|
|
815
1091
|
};
|
|
816
1092
|
}
|
|
817
|
-
async function readAccessibleAccountApps(params, appCache) {
|
|
818
|
-
const snapshot = await refreshAppInventoryNow(params, appCache, {
|
|
819
|
-
forceRefetch: false,
|
|
820
|
-
reason: "account_apps_all",
|
|
821
|
-
targetAppIds: []
|
|
822
|
-
});
|
|
823
|
-
if (!snapshot) return {
|
|
824
|
-
apps: [],
|
|
825
|
-
diagnostic: {
|
|
826
|
-
code: "account_app_inventory_unavailable",
|
|
827
|
-
message: "Codex account app inventory was unavailable; account apps were not exposed."
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
return { apps: snapshot.apps.filter((app) => app.isAccessible).toSorted((left, right) => left.id.localeCompare(right.id)) };
|
|
831
|
-
}
|
|
832
|
-
function toOwnedAccountApp(app) {
|
|
833
|
-
return {
|
|
834
|
-
id: app.id,
|
|
835
|
-
name: app.name,
|
|
836
|
-
accessible: app.isAccessible,
|
|
837
|
-
enabled: app.isEnabled,
|
|
838
|
-
needsAuth: !app.isAccessible
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
function resolveThreadConfigAppsForRecord(params) {
|
|
842
|
-
if (params.inventory.appInventory?.state === "missing") return [];
|
|
843
|
-
return params.record.apps;
|
|
844
|
-
}
|
|
845
|
-
function isPluginAppReadyForThreadStart(app) {
|
|
846
|
-
return app.accessible;
|
|
847
|
-
}
|
|
848
|
-
function shouldForceRefreshForNotReadyPluginApps(params, policy, inventory) {
|
|
849
|
-
if (!params.appCacheKey || !policy.pluginPolicies.some((plugin) => plugin.enabled)) return false;
|
|
850
|
-
if (inventory.appInventory?.state === "missing") return false;
|
|
851
|
-
return inventory.records.some((record) => record.appOwnership === "proven" && record.ownedAppIds.length > 0 && (record.apps.length === 0 || record.apps.some((app) => !app.accessible)));
|
|
852
|
-
}
|
|
853
1093
|
function policyFingerprint(policy) {
|
|
854
1094
|
return {
|
|
855
1095
|
enabled: policy.enabled,
|
|
@@ -1111,8 +1351,8 @@ function hasMessageContent(message) {
|
|
|
1111
1351
|
return "content" in message;
|
|
1112
1352
|
}
|
|
1113
1353
|
function normalizeRenderedContextMaxChars(value) {
|
|
1114
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RENDERED_CONTEXT_CHARS;
|
|
1115
|
-
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)));
|
|
1116
1356
|
}
|
|
1117
1357
|
function resolveTextPartMaxChars(maxRenderedContextChars) {
|
|
1118
1358
|
return Math.min(MAX_TEXT_PART_CHARS, Math.max(DEFAULT_TEXT_PART_CHARS, Math.floor(maxRenderedContextChars / 4)));
|
|
@@ -1229,6 +1469,26 @@ function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
|
1229
1469
|
function fingerprintJsonObject(value) {
|
|
1230
1470
|
return JSON.stringify(stabilizeJsonValue(value));
|
|
1231
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
|
+
}
|
|
1232
1492
|
function fingerprintEnvironmentSelection(environments) {
|
|
1233
1493
|
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
1234
1494
|
}
|
|
@@ -1259,145 +1519,222 @@ function readActiveCodexTurnIdsFromResume(response) {
|
|
|
1259
1519
|
const pagedTurns = response.initialTurnsPage?.data;
|
|
1260
1520
|
return readActiveCodexTurnIds(Array.isArray(pagedTurns) ? { turns: pagedTurns } : response.thread);
|
|
1261
1521
|
}
|
|
1262
|
-
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
1263
|
-
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
1264
|
-
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
1265
|
-
return !previous || previous === next || previous === nextLegacy;
|
|
1266
|
-
}
|
|
1267
|
-
function areUserMcpServersFingerprintsCompatible(params) {
|
|
1268
|
-
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
1269
|
-
}
|
|
1270
|
-
function shouldStartTransientNoToolThread(params) {
|
|
1271
|
-
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
1272
|
-
}
|
|
1273
|
-
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
1274
|
-
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
1275
|
-
}
|
|
1276
|
-
function compareJsonFingerprint(left, right) {
|
|
1277
|
-
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1278
|
-
}
|
|
1279
|
-
//#endregion
|
|
1280
|
-
//#region extensions/codex/src/app-server/attempt-client-cleanup.ts
|
|
1281
|
-
/**
|
|
1282
|
-
* Best-effort cleanup helpers for Codex app-server startup attempts and turns.
|
|
1283
|
-
*/
|
|
1284
|
-
/** Timeout for best-effort app-server turn interruption during cleanup. */
|
|
1285
|
-
const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
1286
|
-
/** Timeout for best-effort thread unsubscribe during cleanup. */
|
|
1287
|
-
const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
|
|
1288
|
-
/** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
|
|
1289
|
-
var CodexAppServerUnsafeSubscriptionError = class extends Error {
|
|
1290
|
-
constructor(message, options) {
|
|
1291
|
-
super(message, options);
|
|
1292
|
-
this.name = "CodexAppServerUnsafeSubscriptionError";
|
|
1293
|
-
}
|
|
1294
|
-
};
|
|
1295
|
-
function isCodexAppServerUnsafeSubscriptionError(error) {
|
|
1296
|
-
return error instanceof CodexAppServerUnsafeSubscriptionError;
|
|
1297
|
-
}
|
|
1298
|
-
/** Asserts Codex resumed the exact thread this attempt subscribed to. */
|
|
1299
|
-
function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
|
|
1300
|
-
if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
|
|
1301
|
-
}
|
|
1302
|
-
async function closeClientAndWaitIfAvailable(client) {
|
|
1303
|
-
const closeable = client;
|
|
1304
|
-
if (typeof closeable.closeAndWait === "function") {
|
|
1305
|
-
await closeable.closeAndWait();
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
closeable.close?.();
|
|
1309
|
-
}
|
|
1310
|
-
async function closeCodexStartupClientBestEffort(client) {
|
|
1311
|
-
if (!client) return;
|
|
1312
|
-
const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
|
|
1313
|
-
if (unclaimedSharedClient.closed) {
|
|
1314
|
-
await closeClientAndWaitIfAvailable(client);
|
|
1315
|
-
return;
|
|
1316
|
-
}
|
|
1317
|
-
if (unclaimedSharedClient.found) {
|
|
1318
|
-
if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
|
|
1322
|
-
if (retiredSharedClient) {
|
|
1323
|
-
if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
if (clearSharedCodexAppServerClientIfCurrent(client)) {
|
|
1327
|
-
await closeClientAndWaitIfAvailable(client);
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
await closeClientAndWaitIfAvailable(client);
|
|
1331
|
-
}
|
|
1332
|
-
/** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
|
|
1333
|
-
function interruptCodexTurnBestEffort(client, params) {
|
|
1334
|
-
const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
|
|
1335
|
-
const requestParams = {
|
|
1336
|
-
threadId: params.threadId,
|
|
1337
|
-
turnId: params.turnId
|
|
1338
|
-
};
|
|
1339
|
-
try {
|
|
1340
|
-
const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
|
|
1341
|
-
Promise.resolve(interrupt).catch((error) => {
|
|
1342
|
-
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1343
|
-
});
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1346
|
-
}
|
|
1522
|
+
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
1523
|
+
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
1524
|
+
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
1525
|
+
return !previous || previous === next || previous === nextLegacy;
|
|
1347
1526
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1527
|
+
function areUserMcpServersFingerprintsCompatible(params) {
|
|
1528
|
+
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
1529
|
+
}
|
|
1530
|
+
function shouldStartTransientNoToolThread(params) {
|
|
1531
|
+
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
1532
|
+
}
|
|
1533
|
+
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
1534
|
+
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
1535
|
+
}
|
|
1536
|
+
function compareJsonFingerprint(left, right) {
|
|
1537
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1538
|
+
}
|
|
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) {
|
|
1350
1553
|
try {
|
|
1351
|
-
await
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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
|
|
1357
1572
|
});
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
* potentially wedged app-server connection.
|
|
1364
|
-
*/
|
|
1365
|
-
async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
|
|
1366
|
-
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
|
|
1367
|
-
const detachedSharedClient = Boolean(retiredSharedClient);
|
|
1368
|
-
if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
|
|
1369
|
-
interruptCodexTurnBestEffort(client, {
|
|
1370
|
-
threadId: params.threadId,
|
|
1371
|
-
turnId: params.turnId,
|
|
1372
|
-
timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
|
|
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)
|
|
1373
1578
|
});
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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
|
|
1377
1586
|
});
|
|
1378
1587
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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);
|
|
1385
1601
|
} catch (error) {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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;
|
|
1391
1671
|
}
|
|
1392
1672
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
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
|
+
};
|
|
1401
1738
|
}
|
|
1402
1739
|
//#endregion
|
|
1403
1740
|
//#region extensions/codex/src/app-server/dynamic-tool-profile.ts
|
|
@@ -1476,7 +1813,8 @@ function filterCodexDynamicToolsWithOpenClawShell(tools, config, env = process.e
|
|
|
1476
1813
|
function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
1477
1814
|
const excludes = /* @__PURE__ */ new Set();
|
|
1478
1815
|
for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
|
|
1479
|
-
if (
|
|
1816
|
+
if (isForcedPrivateQaCodexRuntime(env)) excludes.add("apply_patch");
|
|
1817
|
+
else for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
|
|
1480
1818
|
if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
|
|
1481
1819
|
excludes.add(name);
|
|
1482
1820
|
}
|
|
@@ -1487,13 +1825,6 @@ function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
|
1487
1825
|
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
1488
1826
|
}
|
|
1489
1827
|
//#endregion
|
|
1490
|
-
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
1491
|
-
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
1492
|
-
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
1493
|
-
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
1494
|
-
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
1495
|
-
}
|
|
1496
|
-
//#endregion
|
|
1497
1828
|
//#region extensions/codex/src/app-server/thread-binding-policy.ts
|
|
1498
1829
|
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
1499
1830
|
if (!params.current) return false;
|
|
@@ -1571,6 +1902,61 @@ var CodexAdoptedThreadActiveError = class extends Error {
|
|
|
1571
1902
|
}
|
|
1572
1903
|
};
|
|
1573
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
|
|
1574
1960
|
//#region extensions/codex/src/app-server/reasoning-effort.ts
|
|
1575
1961
|
const CODEX_REASONING_EFFORTS = [
|
|
1576
1962
|
"minimal",
|
|
@@ -1721,31 +2107,38 @@ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts)
|
|
|
1721
2107
|
//#endregion
|
|
1722
2108
|
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
1723
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
|
+
}
|
|
1724
2126
|
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
1725
2127
|
surface: "codex_app_server",
|
|
1726
2128
|
includeLegacyGlobalGuidance: false
|
|
1727
2129
|
}).join("\n");
|
|
1728
2130
|
return [
|
|
1729
2131
|
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
1730
|
-
|
|
1731
|
-
|
|
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,
|
|
1732
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`.",
|
|
1733
|
-
|
|
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),
|
|
1734
2137
|
nativeCommandGuidance,
|
|
1735
2138
|
params.extraSystemPrompt
|
|
1736
2139
|
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
1737
2140
|
}
|
|
1738
|
-
function
|
|
1739
|
-
const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
|
|
1740
|
-
if (deferredToolNames.length === 0) return;
|
|
1741
|
-
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
1742
|
-
}
|
|
1743
|
-
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
1744
|
-
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
1745
|
-
return buildSkillWorkshopPromptSection().join("\n");
|
|
1746
|
-
}
|
|
1747
|
-
function buildVisibleReplyInstruction(params, dynamicTools) {
|
|
1748
|
-
const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
|
|
2141
|
+
function buildVisibleReplyInstruction(params, messageToolAvailable) {
|
|
1749
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.";
|
|
1750
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.";
|
|
1751
2144
|
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
|
|
@@ -2143,12 +2536,17 @@ async function resumeCodexAppServerThread(params) {
|
|
|
2143
2536
|
}
|
|
2144
2537
|
//#endregion
|
|
2145
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
|
+
}
|
|
2146
2544
|
async function resumeExistingCodexThread(params, context) {
|
|
2147
|
-
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;
|
|
2148
2546
|
let resumeReservation;
|
|
2149
2547
|
try {
|
|
2150
2548
|
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
2151
|
-
const finalConfigPatch = params.buildFinalConfigPatch?.({
|
|
2549
|
+
const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
|
|
2152
2550
|
action: "resume",
|
|
2153
2551
|
binding: resumeBinding
|
|
2154
2552
|
}) ?? {
|
|
@@ -2156,7 +2554,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2156
2554
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2157
2555
|
};
|
|
2158
2556
|
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
|
|
2159
|
-
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
|
|
2557
|
+
const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
|
|
2160
2558
|
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2161
2559
|
threadId: resumeBinding.threadId,
|
|
2162
2560
|
authProfileId,
|
|
@@ -2202,6 +2600,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2202
2600
|
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2203
2601
|
const resumePatch = {
|
|
2204
2602
|
cwd: params.cwd,
|
|
2603
|
+
rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
|
|
2205
2604
|
authProfileId: boundAuthProfileId,
|
|
2206
2605
|
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
2207
2606
|
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
@@ -2209,6 +2608,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2209
2608
|
dynamicToolsFingerprint,
|
|
2210
2609
|
dynamicToolsContainDeferred,
|
|
2211
2610
|
webSearchThreadConfigFingerprint,
|
|
2611
|
+
nativeSkillIsolationFingerprint,
|
|
2212
2612
|
userMcpServersFingerprint,
|
|
2213
2613
|
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2214
2614
|
ringZeroConfigFingerprint,
|
|
@@ -2250,6 +2650,13 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2250
2650
|
...resumeBinding,
|
|
2251
2651
|
threadId: response.thread.id,
|
|
2252
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),
|
|
2253
2660
|
lifecycle: {
|
|
2254
2661
|
action: "resumed",
|
|
2255
2662
|
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
@@ -2276,13 +2683,13 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2276
2683
|
}
|
|
2277
2684
|
async function startFreshCodexThread(params, context) {
|
|
2278
2685
|
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
2279
|
-
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;
|
|
2280
2687
|
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2281
2688
|
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2282
2689
|
configPatch: params.finalConfigPatch,
|
|
2283
2690
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2284
2691
|
};
|
|
2285
|
-
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));
|
|
2286
2693
|
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
2287
2694
|
cwd: params.cwd,
|
|
2288
2695
|
dynamicTools: params.dynamicTools,
|
|
@@ -2308,6 +2715,26 @@ async function startFreshCodexThread(params, context) {
|
|
|
2308
2715
|
throw error;
|
|
2309
2716
|
}
|
|
2310
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);
|
|
2311
2738
|
if (ringZeroActive) try {
|
|
2312
2739
|
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
2313
2740
|
} catch (error) {
|
|
@@ -2322,6 +2749,7 @@ async function startFreshCodexThread(params, context) {
|
|
|
2322
2749
|
agentDir: params.params.agentDir,
|
|
2323
2750
|
config: params.params.config
|
|
2324
2751
|
});
|
|
2752
|
+
const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
|
|
2325
2753
|
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2326
2754
|
if (!preserveExistingBinding) {
|
|
2327
2755
|
if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
@@ -2331,12 +2759,14 @@ async function startFreshCodexThread(params, context) {
|
|
|
2331
2759
|
threadId: response.thread.id,
|
|
2332
2760
|
...clientId ? { clientId } : {},
|
|
2333
2761
|
cwd: params.cwd,
|
|
2762
|
+
...rolloutPath ? { rolloutPath } : {},
|
|
2334
2763
|
authProfileId: params.params.authProfileId,
|
|
2335
2764
|
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2336
|
-
modelProvider:
|
|
2765
|
+
modelProvider: bindingModelProvider,
|
|
2337
2766
|
dynamicToolsFingerprint,
|
|
2338
2767
|
dynamicToolsContainDeferred,
|
|
2339
2768
|
webSearchThreadConfigFingerprint,
|
|
2769
|
+
nativeSkillIsolationFingerprint,
|
|
2340
2770
|
userMcpServersFingerprint,
|
|
2341
2771
|
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2342
2772
|
ringZeroConfigFingerprint,
|
|
@@ -2374,11 +2804,13 @@ async function startFreshCodexThread(params, context) {
|
|
|
2374
2804
|
threadId: response.thread.id,
|
|
2375
2805
|
...clientId ? { clientId } : {},
|
|
2376
2806
|
cwd: params.cwd,
|
|
2807
|
+
...rolloutPath ? { rolloutPath } : {},
|
|
2377
2808
|
authProfileId: params.params.authProfileId,
|
|
2378
2809
|
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2379
2810
|
modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
|
|
2380
2811
|
dynamicToolsFingerprint,
|
|
2381
2812
|
dynamicToolsContainDeferred,
|
|
2813
|
+
nativeSkillIsolationFingerprint,
|
|
2382
2814
|
userMcpServersFingerprint,
|
|
2383
2815
|
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2384
2816
|
ringZeroConfigFingerprint,
|
|
@@ -2392,6 +2824,13 @@ async function startFreshCodexThread(params, context) {
|
|
|
2392
2824
|
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2393
2825
|
contextEngine: contextEngineBinding,
|
|
2394
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) } : {},
|
|
2395
2834
|
lifecycle: {
|
|
2396
2835
|
action: "started",
|
|
2397
2836
|
...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
|
|
@@ -2399,6 +2838,13 @@ async function startFreshCodexThread(params, context) {
|
|
|
2399
2838
|
};
|
|
2400
2839
|
}
|
|
2401
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
|
|
2402
2848
|
//#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
|
|
2403
2849
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
|
|
2404
2850
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
|
|
@@ -2488,6 +2934,174 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
|
2488
2934
|
};
|
|
2489
2935
|
}
|
|
2490
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
|
|
2491
3105
|
//#region extensions/codex/src/app-server/thread-supervision.ts
|
|
2492
3106
|
async function materializePendingSupervisionBranch(params) {
|
|
2493
3107
|
let pending = params.binding.pendingSupervisionBranch;
|
|
@@ -2577,6 +3191,25 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
2577
3191
|
modelProvider: nativeModelProvider,
|
|
2578
3192
|
operation: "thread/start response"
|
|
2579
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
|
+
}
|
|
2580
3213
|
if (history.responseItems.length > 0) {
|
|
2581
3214
|
await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
|
|
2582
3215
|
threadId: finalThreadId,
|
|
@@ -2820,46 +3453,9 @@ async function startOrResumeThread(params) {
|
|
|
2820
3453
|
config: params.params.config
|
|
2821
3454
|
});
|
|
2822
3455
|
return await params.bindingStore.withLease(bindingIdentity, async () => {
|
|
2823
|
-
const lifecycleTiming =
|
|
2824
|
-
...params.timing,
|
|
2825
|
-
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
2826
|
-
});
|
|
2827
|
-
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
2828
|
-
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
2829
|
-
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
2830
|
-
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
2831
|
-
config: params.params.config,
|
|
2832
|
-
disableTools: params.params.disableTools,
|
|
2833
|
-
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
2834
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2835
|
-
webSearchAllowed: params.webSearchAllowed
|
|
2836
|
-
})).threadConfig);
|
|
2837
|
-
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
2838
|
-
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
2839
|
-
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
|
|
2840
|
-
agentId: params.agentId ?? params.params.agentId,
|
|
2841
|
-
agentDir: params.params.agentDir,
|
|
2842
|
-
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
2843
|
-
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
2844
|
-
serverName,
|
|
2845
|
-
error: formatErrorMessage(error)
|
|
2846
|
-
})
|
|
2847
|
-
});
|
|
2848
|
-
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2849
|
-
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2850
|
-
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
2851
|
-
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
2852
|
-
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
2853
|
-
if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
|
|
2854
|
-
const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
2855
|
-
if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
|
|
2856
|
-
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
2857
|
-
version: 1,
|
|
2858
|
-
baseInstructions: "",
|
|
2859
|
-
config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
|
|
2860
|
-
}) : void 0;
|
|
2861
|
-
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);
|
|
2862
3457
|
let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
|
|
3458
|
+
const initialBoundThreadId = binding?.threadId;
|
|
2863
3459
|
const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
|
|
2864
3460
|
authProfileId,
|
|
2865
3461
|
modelProvider,
|
|
@@ -2867,14 +3463,13 @@ async function startOrResumeThread(params) {
|
|
|
2867
3463
|
agentDir: params.params.agentDir,
|
|
2868
3464
|
config: params.params.config
|
|
2869
3465
|
});
|
|
2870
|
-
const throwIfAborted = () =>
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
};
|
|
3466
|
+
const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
|
|
3467
|
+
const releaseRetainedThread = (threadId) => releaseCodexRetainedLiveThread({
|
|
3468
|
+
client: params.client,
|
|
3469
|
+
abandonClient: params.abandonClient,
|
|
3470
|
+
lifecycleTiming,
|
|
3471
|
+
threadId
|
|
3472
|
+
});
|
|
2878
3473
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
2879
3474
|
if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
|
|
2880
3475
|
bindingStore: params.bindingStore,
|
|
@@ -2883,13 +3478,14 @@ async function startOrResumeThread(params) {
|
|
|
2883
3478
|
}))) throw createCodexSessionGenerationSupersededError(bindingIdentity.sessionId);
|
|
2884
3479
|
}
|
|
2885
3480
|
if (binding?.pendingSupervisionBranch) {
|
|
3481
|
+
await releaseRetainedThread(binding.threadId);
|
|
2886
3482
|
const pendingBinding = binding;
|
|
2887
3483
|
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2888
3484
|
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2889
3485
|
configPatch: params.finalConfigPatch,
|
|
2890
3486
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2891
3487
|
};
|
|
2892
|
-
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));
|
|
2893
3489
|
return await materializePendingSupervisionBranch({
|
|
2894
3490
|
client: params.client,
|
|
2895
3491
|
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
@@ -2907,6 +3503,7 @@ async function startOrResumeThread(params) {
|
|
|
2907
3503
|
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2908
3504
|
webSearchAllowed: params.webSearchAllowed,
|
|
2909
3505
|
environmentSelection: params.environmentSelection,
|
|
3506
|
+
provisionalAppIds: pluginThreadConfig?.provisionalAppIds,
|
|
2910
3507
|
signal: params.signal,
|
|
2911
3508
|
throwIfAborted,
|
|
2912
3509
|
lifecycleTiming,
|
|
@@ -2919,6 +3516,7 @@ async function startOrResumeThread(params) {
|
|
|
2919
3516
|
dynamicToolsFingerprint,
|
|
2920
3517
|
dynamicToolsContainDeferred,
|
|
2921
3518
|
webSearchThreadConfigFingerprint,
|
|
3519
|
+
nativeSkillIsolationFingerprint,
|
|
2922
3520
|
userMcpServersFingerprint,
|
|
2923
3521
|
mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
|
|
2924
3522
|
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
@@ -2944,6 +3542,10 @@ async function startOrResumeThread(params) {
|
|
|
2944
3542
|
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
2945
3543
|
binding = void 0;
|
|
2946
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
|
+
}
|
|
2947
3549
|
if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
|
|
2948
3550
|
embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
|
|
2949
3551
|
await clearCurrentBinding("rotating a ring-zero thread binding");
|
|
@@ -3130,6 +3732,32 @@ async function startOrResumeThread(params) {
|
|
|
3130
3732
|
await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
|
|
3131
3733
|
binding = void 0;
|
|
3132
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);
|
|
3133
3761
|
const resumed = await resumeExistingCodexThread(params, {
|
|
3134
3762
|
binding,
|
|
3135
3763
|
bindingIdentity,
|
|
@@ -3139,6 +3767,7 @@ async function startOrResumeThread(params) {
|
|
|
3139
3767
|
dynamicToolsFingerprint,
|
|
3140
3768
|
dynamicToolsContainDeferred,
|
|
3141
3769
|
webSearchThreadConfigFingerprint,
|
|
3770
|
+
nativeSkillIsolationFingerprint,
|
|
3142
3771
|
userMcpServersFingerprint,
|
|
3143
3772
|
ringZeroConfigFingerprint,
|
|
3144
3773
|
ringZeroClientInstanceId,
|
|
@@ -3148,13 +3777,16 @@ async function startOrResumeThread(params) {
|
|
|
3148
3777
|
hostSystemAgentActive,
|
|
3149
3778
|
ringZeroActive,
|
|
3150
3779
|
ringZeroInheritedMcpServerNames,
|
|
3780
|
+
nativeSkillIsolation,
|
|
3151
3781
|
lifecycleTiming,
|
|
3152
3782
|
normalizeBindingModelProvider,
|
|
3153
3783
|
throwIfAborted,
|
|
3154
|
-
clearCurrentBinding
|
|
3784
|
+
clearCurrentBinding,
|
|
3785
|
+
prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch
|
|
3155
3786
|
});
|
|
3156
3787
|
if (resumed) return resumed;
|
|
3157
3788
|
}
|
|
3789
|
+
if (initialBoundThreadId) await releaseRetainedThread(initialBoundThreadId);
|
|
3158
3790
|
return await startFreshCodexThread(params, {
|
|
3159
3791
|
bindingIdentity,
|
|
3160
3792
|
startModelSelection,
|
|
@@ -3163,6 +3795,7 @@ async function startOrResumeThread(params) {
|
|
|
3163
3795
|
dynamicToolsFingerprint,
|
|
3164
3796
|
dynamicToolsContainDeferred,
|
|
3165
3797
|
webSearchThreadConfigFingerprint,
|
|
3798
|
+
nativeSkillIsolationFingerprint,
|
|
3166
3799
|
userMcpServersFingerprint,
|
|
3167
3800
|
ringZeroConfigFingerprint,
|
|
3168
3801
|
ringZeroClientInstanceId,
|
|
@@ -3172,6 +3805,7 @@ async function startOrResumeThread(params) {
|
|
|
3172
3805
|
hostSystemAgentActive,
|
|
3173
3806
|
ringZeroActive,
|
|
3174
3807
|
ringZeroInheritedMcpServerNames,
|
|
3808
|
+
nativeSkillIsolation,
|
|
3175
3809
|
lifecycleTiming,
|
|
3176
3810
|
normalizeBindingModelProvider,
|
|
3177
3811
|
throwIfAborted,
|
|
@@ -3282,7 +3916,7 @@ function buildTurnStartParams(params, options) {
|
|
|
3282
3916
|
cwd: options.cwd,
|
|
3283
3917
|
approvalPolicy: options.appServer.approvalPolicy,
|
|
3284
3918
|
approvalsReviewer: options.appServer.approvalsReviewer,
|
|
3285
|
-
...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.cwd) },
|
|
3919
|
+
...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.cwd, options.appServer.start?.args) },
|
|
3286
3920
|
...modelSelection ? {
|
|
3287
3921
|
model: modelSelection.model,
|
|
3288
3922
|
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
@@ -3350,4 +3984,4 @@ function joinPresentSections(...sections) {
|
|
|
3350
3984
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
3351
3985
|
}
|
|
3352
3986
|
//#endregion
|
|
3353
|
-
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 };
|