@openclaw/codex 2026.7.2-beta.5 → 2026.8.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{app-server-policy-BmDgseo_.js → app-server-policy-zcBDc1JU.js} +1 -1
- package/dist/attempt-client-cleanup-DMLXUfa8.js +735 -0
- package/dist/binding-connection-I5Bo-AsX.js +33 -0
- package/dist/{bounded-turn-FEbUXoYT.js → bounded-turn-qgZmzy_4.js} +71 -63
- package/dist/{capabilities-BHRSI6Cn.js → capabilities-CybGI1oz.js} +8 -2
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-ico4ZFnk.js → command-formatters-B1AEz8ZA.js} +18 -18
- package/dist/{command-handlers-CRmnwI2N.js → command-handlers-Db8pu_ds.js} +484 -95
- package/dist/{compact-dEN49UH6.js → compact-LspQaGfl.js} +213 -199
- package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
- package/dist/doctor-contract-api.js +40 -43
- package/dist/{dynamic-tools-LIt9ojep.js → dynamic-tools-BVdyWkUD.js} +2984 -1221
- package/dist/effective-mcp-catalog-DIIhAtOZ.js +119 -0
- package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
- package/dist/{attempt-notifications-CMR2_fmR.js → event-projector-usage-e5huIQWu.js} +113 -29
- package/dist/harness.js +83 -13
- package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
- package/dist/index.js +703 -329
- package/dist/isolated-completion-C58NbL40.js +75 -0
- package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
- package/dist/media-understanding-provider.js +5 -5
- package/dist/{models-BA01NEez.js → models-BFlpn1Ex.js} +12 -17
- package/dist/native-subagent-monitor-DyTGmTUx.js +3111 -0
- package/dist/{plugin-app-cache-key-DSXX-rXO.js → plugin-app-cache-key-C3H2CVp6.js} +135 -42
- package/dist/plugin-marketplace-discovery-CzrGJdCR.js +385 -0
- package/dist/{computer-use-BjGylO-E.js → plugin-metadata-cache-CNJPj4_b.js} +211 -113
- package/dist/protocol-validators-B4F7L8Rf.js +2346 -0
- package/dist/{rate-limits-S_PXs0ul.js → rate-limits-DZXmOL8-.js} +17 -35
- package/dist/{request-CXpPennK.js → request-BRl5_UfX.js} +4 -4
- package/dist/{run-attempt-C5GTPJOE.js → run-attempt-P8BdxC1I.js} +1996 -5728
- package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-BVSPdd6g.js} +91 -40
- package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-Cmwc9eIk.js} +10 -13
- package/dist/{session-binding-DlVQ-Pxs.js → session-binding-DPxIvMgE.js} +305 -271
- package/dist/{session-catalog-CP2oCcEb.js → session-catalog-xwNi_SDM.js} +418 -144
- package/dist/{session-cli-CqZ0Fp2a.js → session-cli-7hgXZDV7.js} +1 -1
- package/dist/session-retirement-DoMMJFKm.js +36 -0
- package/dist/{session-upstream-marker-QE-m5QFG.js → session-upstream-marker-CrIFX06J.js} +1 -1
- package/dist/{settled-turn-finalizer-CA_2QXIE.js → settled-turn-finalizer-BRWBO4MO.js} +36 -15
- package/dist/{shared-client-BVKE40c-.js → shared-client-kIBbGjk4.js} +721 -240
- package/dist/{side-question-CO12sHxo.js → side-question-9HAL26l9.js} +102 -53
- package/dist/{thread-lifecycle-DRg_FdzC.js → thread-lifecycle-Bu1BTDXr.js} +1355 -660
- package/dist/thread-ownership-6kOQHlyC.js +92 -0
- package/dist/thread-resume-CPrJNQVM.js +30 -0
- package/dist/{transcript-mirror-uKA-A4tB.js → transcript-mirror-B9gGgnJS.js} +202 -182
- package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DIImwDVS.js} +11 -0
- package/dist/upstream-prompt-provenance-ClRBije0.js +41 -0
- package/dist/{upstream-session-fork-_Vrz4WJr.js → upstream-session-fork-CE-_LdnP.js} +5 -5
- package/dist/{usage-UpVbdX5G.js → usage-CHlV0e-o.js} +3 -3
- package/dist/{web-search-provider.runtime-CaQMYDbp.js → web-search-provider.runtime-DRw2Vl42.js} +8 -9
- package/openclaw.plugin.json +18 -3
- package/package.json +6 -7
- package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
- package/dist/conversation-control-DE7d9DOj.js +0 -287
- package/dist/event-projector-usage-tvgb1NCX.js +0 -69
- package/dist/notification-correlation-Bj_Ak_jI.js +0 -67
- package/dist/protocol-validators-Dm4eXZmt.js +0 -6337
- package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
- package/dist/realtime-voice-api.js +0 -2
|
@@ -1,103 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError,
|
|
4
|
-
import {
|
|
5
|
-
import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-
|
|
1
|
+
import { D as CODEX_PLUGINS_MARKETPLACE_NAME, E as resolveCodexPluginsPolicy, O as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, m as codexSandboxPolicyForTurn, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-DPxIvMgE.js";
|
|
2
|
+
import { M as isCodexAppServerConnectionClosedError, R as resolveCodexAppServerClientInstanceId, V as consumeCodexAppServerLiveThread, ct as isJsonObject, d as retainSharedCodexAppServerClientByInstanceId, k as getCodexAppServerClientInstanceId, s as isCodexAppServerStartSelectionChangedError, st as flattenCodexDynamicToolFunctions, z as CodexAppServerRpcError } from "./shared-client-kIBbGjk4.js";
|
|
3
|
+
import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, d as unsubscribeCodexThreadBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-DMLXUfa8.js";
|
|
4
|
+
import { a as assertCodexThreadStartResponse, r as assertCodexThreadForkResponse } from "./protocol-validators-B4F7L8Rf.js";
|
|
5
|
+
import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-C3H2CVp6.js";
|
|
6
6
|
import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { t as resumeCodexAppServerThread } from "./thread-resume-CPrJNQVM.js";
|
|
8
|
+
import { u as projectBoundedCodexThreadHistory } from "./transcript-mirror-B9gGgnJS.js";
|
|
9
|
+
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
|
|
10
|
+
import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
11
|
+
import fs from "node:fs/promises";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { SKILL_WORKSHOP_TOOL_NAME, TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
9
14
|
import * as crypto$1 from "node:crypto";
|
|
10
15
|
import crypto from "node:crypto";
|
|
11
|
-
import
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
12
18
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
13
19
|
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
20
|
+
import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
21
|
+
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
14
22
|
import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
15
23
|
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
16
|
-
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
17
24
|
import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
|
|
18
|
-
import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
|
|
19
|
-
//#region extensions/codex/src/app-server/event-projector-values.ts
|
|
20
|
-
function readString(record, key) {
|
|
21
|
-
const value = record[key];
|
|
22
|
-
return typeof value === "string" ? value : void 0;
|
|
23
|
-
}
|
|
24
|
-
function normalizeNonEmptyString(value) {
|
|
25
|
-
if (typeof value !== "string") return;
|
|
26
|
-
return value.trim() || void 0;
|
|
27
|
-
}
|
|
28
|
-
function readNonEmptyString(record, key) {
|
|
29
|
-
return normalizeNonEmptyString(record[key]);
|
|
30
|
-
}
|
|
31
|
-
function readNonEmptyStringArray(record, key) {
|
|
32
|
-
const value = record[key];
|
|
33
|
-
if (!Array.isArray(value)) return [];
|
|
34
|
-
const entries = [];
|
|
35
|
-
for (const entry of value) {
|
|
36
|
-
const normalized = normalizeNonEmptyString(entry);
|
|
37
|
-
if (normalized) entries.push(normalized);
|
|
38
|
-
}
|
|
39
|
-
return entries;
|
|
40
|
-
}
|
|
41
|
-
function readNullableString(record, key) {
|
|
42
|
-
const value = record[key];
|
|
43
|
-
if (value === null) return null;
|
|
44
|
-
return typeof value === "string" ? value : void 0;
|
|
45
|
-
}
|
|
46
|
-
function readNumber(record, key) {
|
|
47
|
-
const value = record[key];
|
|
48
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
49
|
-
}
|
|
50
|
-
function readNonNegativeInteger(record, key) {
|
|
51
|
-
const value = readNumber(record, key);
|
|
52
|
-
return value !== void 0 && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
53
|
-
}
|
|
54
|
-
function readCodexErrorNotificationMessage(record) {
|
|
55
|
-
const error = record.error;
|
|
56
|
-
return isJsonObject(error) ? readString(error, "message") : void 0;
|
|
57
|
-
}
|
|
58
|
-
function readHookOutputEntries(value) {
|
|
59
|
-
if (!Array.isArray(value)) return [];
|
|
60
|
-
return value.flatMap((entry) => {
|
|
61
|
-
if (!isJsonObject(entry)) return [];
|
|
62
|
-
const text = readString(entry, "text");
|
|
63
|
-
if (!text) return [];
|
|
64
|
-
const kind = readString(entry, "kind");
|
|
65
|
-
return [{
|
|
66
|
-
...kind ? { kind } : {},
|
|
67
|
-
text
|
|
68
|
-
}];
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
function splitPlanText(text) {
|
|
72
|
-
return text.split(/\r?\n/).map((line) => line.trim().replace(/^[-*]\s+/, "")).filter((line) => line.length > 0);
|
|
73
|
-
}
|
|
74
|
-
function extractRawAssistantText(item) {
|
|
75
|
-
return (Array.isArray(item.content) ? item.content : []).flatMap((entry) => {
|
|
76
|
-
if (!isJsonObject(entry)) return [];
|
|
77
|
-
const type = readString(entry, "type");
|
|
78
|
-
if (type !== "output_text" && type !== "text") return [];
|
|
79
|
-
const value = readString(entry, "text");
|
|
80
|
-
return value ? [value] : [];
|
|
81
|
-
}).join("").trim() || void 0;
|
|
82
|
-
}
|
|
83
|
-
function readItemString(item, key) {
|
|
84
|
-
const value = item[key];
|
|
85
|
-
return typeof value === "string" ? value : void 0;
|
|
86
|
-
}
|
|
87
|
-
function readItem(value) {
|
|
88
|
-
if (!isJsonObject(value)) return;
|
|
89
|
-
const type = typeof value.type === "string" ? value.type : void 0;
|
|
90
|
-
const id = typeof value.id === "string" ? value.id : void 0;
|
|
91
|
-
if (!type || !id) return;
|
|
92
|
-
return value;
|
|
93
|
-
}
|
|
94
|
-
//#endregion
|
|
95
25
|
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
96
26
|
/**
|
|
97
27
|
* Reads Codex plugin marketplace state and app inventory to decide which
|
|
98
28
|
* plugin-owned apps can be exposed to a native Codex thread.
|
|
99
29
|
*/
|
|
100
30
|
const CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME = `${CODEX_PLUGINS_MARKETPLACE_NAME}-remote`;
|
|
31
|
+
const CODEX_PLUGINS_API_MARKETPLACE_NAME = "openai-api-curated";
|
|
101
32
|
/** Reads configured Codex plugin state and maps owned apps to readiness diagnostics. */
|
|
102
33
|
async function readCodexPluginInventory(params) {
|
|
103
34
|
const policy = params.policy ?? resolveCodexPluginsPolicy(params.pluginConfig);
|
|
@@ -110,21 +41,11 @@ async function readCodexPluginInventory(params) {
|
|
|
110
41
|
}]
|
|
111
42
|
};
|
|
112
43
|
const appInventory = readCachedAppInventory(params);
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
kind: "listed",
|
|
119
|
-
response: await listCodexPluginMetadata(params, "workspace-directory", {
|
|
120
|
-
cwds: [],
|
|
121
|
-
marketplaceKinds: [CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME]
|
|
122
|
-
})
|
|
123
|
-
};
|
|
124
|
-
} catch (error) {
|
|
125
|
-
if (!(error instanceof CodexAppServerRpcError)) throw error;
|
|
126
|
-
workspaceListResult = { kind: "rejected" };
|
|
127
|
-
}
|
|
44
|
+
const installedPlugins = await readInstalledCodexPluginMetadata({
|
|
45
|
+
...params,
|
|
46
|
+
policy
|
|
47
|
+
});
|
|
48
|
+
const pluginCatalogs = /* @__PURE__ */ new Map();
|
|
128
49
|
const diagnostics = [];
|
|
129
50
|
const records = [];
|
|
130
51
|
if (appInventory?.state === "missing") diagnostics.push({
|
|
@@ -136,10 +57,21 @@ async function readCodexPluginInventory(params) {
|
|
|
136
57
|
message: "Cached Codex app inventory is stale; using stale app readiness and refreshing."
|
|
137
58
|
});
|
|
138
59
|
for (const pluginPolicy of policy.pluginPolicies) {
|
|
139
|
-
if (!pluginPolicy.enabled) continue;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (!
|
|
60
|
+
if (!pluginPolicy.enabled && !policy.allowAllPlugins) continue;
|
|
61
|
+
let listed = installedPlugins;
|
|
62
|
+
let resolvedPlugin = findConfiguredMarketplacePlugin(listed, pluginPolicy);
|
|
63
|
+
if (!resolvedPlugin && pluginPolicy.enabled && pluginPolicy.marketplaceName !== "workspace-directory") {
|
|
64
|
+
const requestParams = buildPluginCatalogRequestParams(params, pluginPolicy.marketplaceName);
|
|
65
|
+
const catalogKey = JSON.stringify([requestParams, pluginMetadataCatalogScope(pluginPolicy.marketplaceName)]);
|
|
66
|
+
let catalog = pluginCatalogs.get(catalogKey);
|
|
67
|
+
if (!catalog) {
|
|
68
|
+
catalog = listCodexPluginMetadata(params, pluginPolicy.marketplaceName);
|
|
69
|
+
pluginCatalogs.set(catalogKey, catalog);
|
|
70
|
+
}
|
|
71
|
+
listed = await catalog;
|
|
72
|
+
resolvedPlugin = findConfiguredMarketplacePlugin(listed, pluginPolicy);
|
|
73
|
+
}
|
|
74
|
+
if (!listed.marketplaces.some((marketplace) => marketplaceMatchesConfiguredName(marketplace, pluginPolicy.marketplaceName))) {
|
|
143
75
|
diagnostics.push({
|
|
144
76
|
code: "marketplace_missing",
|
|
145
77
|
plugin: pluginPolicy,
|
|
@@ -147,7 +79,6 @@ async function readCodexPluginInventory(params) {
|
|
|
147
79
|
});
|
|
148
80
|
continue;
|
|
149
81
|
}
|
|
150
|
-
const resolvedPlugin = pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(listed, pluginPolicy.pluginName) : findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
|
|
151
82
|
if (!resolvedPlugin) {
|
|
152
83
|
diagnostics.push({
|
|
153
84
|
code: "plugin_missing",
|
|
@@ -157,6 +88,15 @@ async function readCodexPluginInventory(params) {
|
|
|
157
88
|
continue;
|
|
158
89
|
}
|
|
159
90
|
const { summary } = resolvedPlugin;
|
|
91
|
+
const unavailableByMarketplacePolicy = summary.availability === "DISABLED_BY_ADMIN" || summary.installPolicy === "NOT_AVAILABLE";
|
|
92
|
+
if (unavailableByMarketplacePolicy) {
|
|
93
|
+
diagnostics.push({
|
|
94
|
+
code: "plugin_disabled",
|
|
95
|
+
plugin: pluginPolicy,
|
|
96
|
+
message: `${pluginPolicy.pluginName} is unavailable in ${pluginPolicy.marketplaceName}.`
|
|
97
|
+
});
|
|
98
|
+
if (!summary.installed) continue;
|
|
99
|
+
}
|
|
160
100
|
const detail = await readPluginDetail(params, marketplaceRef(resolvedPlugin.marketplace, pluginPolicy.marketplaceName), pluginPolicy, summary, diagnostics);
|
|
161
101
|
const ownedAppIds = detail?.apps.map((app) => app.id).filter(Boolean).toSorted() ?? [];
|
|
162
102
|
const appOwnership = resolveAppOwnership({
|
|
@@ -183,7 +123,7 @@ async function readCodexPluginInventory(params) {
|
|
|
183
123
|
policy: pluginPolicy,
|
|
184
124
|
summary,
|
|
185
125
|
...detail ? { detail } : {},
|
|
186
|
-
activationRequired: !summary.installed || !summary.enabled,
|
|
126
|
+
activationRequired: pluginPolicy.enabled && (unavailableByMarketplacePolicy || !summary.installed || !summary.enabled),
|
|
187
127
|
authRequired: apps.some((app) => app.needsAuth || !app.accessible),
|
|
188
128
|
appOwnership,
|
|
189
129
|
ownedAppIds,
|
|
@@ -197,11 +137,14 @@ async function readCodexPluginInventory(params) {
|
|
|
197
137
|
...appInventory ? { appInventory } : {}
|
|
198
138
|
};
|
|
199
139
|
}
|
|
200
|
-
/** Finds
|
|
201
|
-
function
|
|
202
|
-
const resolved =
|
|
140
|
+
/** Finds a configured plugin only in its authorized marketplace identity. */
|
|
141
|
+
function findCodexMarketplacePluginSummary(listed, marketplaceName, pluginName) {
|
|
142
|
+
const resolved = findConfiguredMarketplacePlugin(listed, {
|
|
143
|
+
marketplaceName,
|
|
144
|
+
pluginName
|
|
145
|
+
});
|
|
203
146
|
return resolved ? {
|
|
204
|
-
marketplace: marketplaceRef(resolved.marketplace,
|
|
147
|
+
marketplace: marketplaceRef(resolved.marketplace, marketplaceName),
|
|
205
148
|
summary: resolved.summary
|
|
206
149
|
} : void 0;
|
|
207
150
|
}
|
|
@@ -218,25 +161,52 @@ function resolveRecoverableCodexPluginConfigKeys(params) {
|
|
|
218
161
|
return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
|
|
219
162
|
pluginPolicy,
|
|
220
163
|
metadataCache: params.metadataCache,
|
|
221
|
-
appCacheKey: params.appCacheKey
|
|
164
|
+
appCacheKey: params.appCacheKey,
|
|
165
|
+
configCwd: params.configCwd
|
|
222
166
|
})).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
|
|
223
167
|
}
|
|
224
|
-
async function listCodexPluginMetadata(params,
|
|
225
|
-
|
|
168
|
+
async function listCodexPluginMetadata(params, marketplaceName) {
|
|
169
|
+
const requestParams = buildPluginCatalogRequestParams(params, marketplaceName);
|
|
170
|
+
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
|
|
226
171
|
return (await params.metadataCache.load({
|
|
227
172
|
appCacheKey: params.appCacheKey,
|
|
228
|
-
queryKind,
|
|
173
|
+
queryKind: "curated-global",
|
|
229
174
|
requestParams,
|
|
175
|
+
catalogScope: pluginMetadataCatalogScope(marketplaceName),
|
|
230
176
|
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
231
|
-
cacheable: (response) =>
|
|
177
|
+
cacheable: (response) => response.marketplaces.some((marketplace) => marketplaceMatchesConfiguredName(marketplace, marketplaceName))
|
|
178
|
+
})).response;
|
|
179
|
+
}
|
|
180
|
+
async function readInstalledCodexPluginMetadata(params) {
|
|
181
|
+
const requestParams = params.configCwd ? { cwds: [params.configCwd] } : {};
|
|
182
|
+
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/installed", requestParams);
|
|
183
|
+
return (await params.metadataCache.load({
|
|
184
|
+
appCacheKey: params.appCacheKey,
|
|
185
|
+
queryKind: "installed",
|
|
186
|
+
requestParams,
|
|
187
|
+
request: async (method, installedParams) => await params.request(method, installedParams),
|
|
188
|
+
cacheable: (response) => params.policy.pluginPolicies.every((pluginPolicy) => {
|
|
189
|
+
if (!pluginPolicy.enabled && !params.policy.allowAllPlugins) return true;
|
|
190
|
+
return Boolean(findConfiguredMarketplacePlugin(response, pluginPolicy));
|
|
191
|
+
})
|
|
232
192
|
})).response;
|
|
233
193
|
}
|
|
234
194
|
function isSettledMissingPluginPolicy(params) {
|
|
235
|
-
const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "
|
|
236
|
-
const
|
|
195
|
+
const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "installed" : "curated-global";
|
|
196
|
+
const requestParams = queryKind === "installed" ? params.configCwd ? { cwds: [params.configCwd] } : {} : buildPluginCatalogRequestParams(params, params.pluginPolicy.marketplaceName);
|
|
197
|
+
const listed = params.metadataCache.read(params.appCacheKey, queryKind, requestParams, queryKind === "curated-global" ? pluginMetadataCatalogScope(params.pluginPolicy.marketplaceName) : void 0)?.response;
|
|
237
198
|
if (!listed) return false;
|
|
238
|
-
|
|
239
|
-
|
|
199
|
+
return !findConfiguredMarketplacePlugin(listed, params.pluginPolicy);
|
|
200
|
+
}
|
|
201
|
+
function pluginMetadataCatalogScope(marketplaceName) {
|
|
202
|
+
return isOpenAiCuratedMarketplaceName(marketplaceName) ? void 0 : marketplaceName;
|
|
203
|
+
}
|
|
204
|
+
function buildPluginCatalogRequestParams(params, marketplaceName) {
|
|
205
|
+
const marketplaceKinds = marketplaceName === "created-by-me-remote" ? ["created-by-me-remote"] : marketplaceName.startsWith("workspace-shared-with-me") ? ["shared-with-me"] : void 0;
|
|
206
|
+
return {
|
|
207
|
+
...params.configCwd ? { cwds: [params.configCwd] } : {},
|
|
208
|
+
...marketplaceKinds ? { marketplaceKinds: [...marketplaceKinds] } : {}
|
|
209
|
+
};
|
|
240
210
|
}
|
|
241
211
|
function readCachedAppInventory(params) {
|
|
242
212
|
if (!params.appCache || !params.appCacheKey) return;
|
|
@@ -250,7 +220,7 @@ function readCachedAppInventory(params) {
|
|
|
250
220
|
}
|
|
251
221
|
async function readPluginDetail(params, marketplace, pluginPolicy, summary, diagnostics) {
|
|
252
222
|
if (params.readPluginDetails === false) return;
|
|
253
|
-
if (marketplace.
|
|
223
|
+
if (marketplace.remoteMarketplaceName && !summary.remotePluginId) {
|
|
254
224
|
diagnostics.push({
|
|
255
225
|
code: "plugin_detail_unavailable",
|
|
256
226
|
plugin: pluginPolicy,
|
|
@@ -299,23 +269,30 @@ function resolveOwnedApps(params) {
|
|
|
299
269
|
name: app.name,
|
|
300
270
|
accessible: info.isAccessible,
|
|
301
271
|
enabled: info.isEnabled,
|
|
302
|
-
needsAuth:
|
|
272
|
+
needsAuth: !info.isAccessible
|
|
303
273
|
};
|
|
304
274
|
}).toSorted((left, right) => left.id.localeCompare(right.id));
|
|
305
275
|
}
|
|
306
276
|
function findPluginSummary(marketplace, pluginName) {
|
|
307
|
-
|
|
277
|
+
const exact = marketplace.plugins.find((plugin) => plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}`);
|
|
278
|
+
if (exact) return exact;
|
|
279
|
+
const matches = marketplace.plugins.filter((plugin) => plugin.name === pluginName || pluginNameFromPluginId(plugin.id, marketplace.name) === pluginName);
|
|
280
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
308
281
|
}
|
|
309
|
-
function
|
|
282
|
+
function findConfiguredMarketplacePlugin(listed, plugin) {
|
|
283
|
+
if (plugin.marketplaceName === "workspace-directory") return findWorkspaceMarketplacePlugin(listed, plugin.pluginName);
|
|
310
284
|
for (const marketplace of listed.marketplaces) {
|
|
311
|
-
if (!
|
|
312
|
-
const summary = findPluginSummary(marketplace, pluginName);
|
|
285
|
+
if (!marketplaceMatchesConfiguredName(marketplace, plugin.marketplaceName)) continue;
|
|
286
|
+
const summary = findPluginSummary(marketplace, plugin.pluginName);
|
|
313
287
|
if (summary) return {
|
|
314
288
|
marketplace,
|
|
315
289
|
summary
|
|
316
290
|
};
|
|
317
291
|
}
|
|
318
292
|
}
|
|
293
|
+
function marketplaceMatchesConfiguredName(marketplace, configuredMarketplaceName) {
|
|
294
|
+
return isOpenAiCuratedMarketplaceName(configuredMarketplaceName) ? isOpenAiCuratedMarketplace(marketplace) : marketplace.name === configuredMarketplaceName;
|
|
295
|
+
}
|
|
319
296
|
function findWorkspaceMarketplacePlugin(listed, pluginName) {
|
|
320
297
|
const marketplace = listed.marketplaces.find((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME);
|
|
321
298
|
const summary = marketplace?.plugins.find((plugin) => plugin.id === pluginName);
|
|
@@ -337,21 +314,35 @@ function marketplaceRef(marketplace, name) {
|
|
|
337
314
|
...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
|
|
338
315
|
};
|
|
339
316
|
}
|
|
340
|
-
/** True for
|
|
317
|
+
/** True for any supported OpenAI curated marketplace wire name, matching Codex's own curated predicate. */
|
|
341
318
|
function isOpenAiCuratedMarketplace(marketplace) {
|
|
342
|
-
return marketplace.name
|
|
319
|
+
return isOpenAiCuratedMarketplaceName(marketplace.name);
|
|
320
|
+
}
|
|
321
|
+
/** True for all Codex wire aliases of the same OpenAI-curated catalog. */
|
|
322
|
+
function isOpenAiCuratedMarketplaceName(marketplaceName) {
|
|
323
|
+
return marketplaceName === "openai-curated" || marketplaceName === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME || marketplaceName === CODEX_PLUGINS_API_MARKETPLACE_NAME;
|
|
343
324
|
}
|
|
344
325
|
//#endregion
|
|
345
326
|
//#region extensions/codex/src/app-server/plugin-activation.ts
|
|
346
|
-
/**
|
|
327
|
+
/**
|
|
328
|
+
* Activates legacy curated Codex plugins while requiring owner-managed
|
|
329
|
+
* installation for every other marketplace.
|
|
330
|
+
*/
|
|
331
|
+
/** Activates legacy curated plugins without granting install authority to other marketplaces. */
|
|
347
332
|
async function ensureCodexPluginActivation(params) {
|
|
348
333
|
if (params.identity.marketplaceName === "workspace-directory") return activationFailure(params.identity, "disabled", { message: "workspace-directory plugins must be installed and enabled outside OpenClaw before use." });
|
|
334
|
+
if (!isOpenAiCuratedMarketplaceName(params.identity.marketplaceName)) {
|
|
335
|
+
const target = params.identity.pluginName.endsWith(`@${params.identity.marketplaceName}`) ? params.identity.pluginName : `${params.identity.pluginName}@${params.identity.marketplaceName}`;
|
|
336
|
+
return activationFailure(params.identity, "disabled", { message: `${params.identity.marketplaceName} plugins must be installed and enabled by an owner before use. Run /codex plugins install ${target}.` });
|
|
337
|
+
}
|
|
349
338
|
const listed = await listCuratedCodexPluginMetadata(params);
|
|
350
|
-
const resolved =
|
|
339
|
+
const resolved = findCodexMarketplacePluginSummary(listed, params.identity.marketplaceName, params.identity.pluginName);
|
|
351
340
|
if (!resolved) {
|
|
352
|
-
if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
|
|
341
|
+
if (!listed.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
|
|
353
342
|
return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
|
|
354
343
|
}
|
|
344
|
+
if (resolved.marketplace.remoteMarketplaceName && !resolved.summary.remotePluginId) return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} detail unavailable: Codex did not return a remote plugin id.` });
|
|
345
|
+
if (resolved.summary.availability === "DISABLED_BY_ADMIN" || resolved.summary.installPolicy === "NOT_AVAILABLE") return activationFailure(params.identity, "disabled", { message: `${params.identity.pluginName} was disabled or made unavailable by its marketplace administrator.` });
|
|
355
346
|
if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
|
|
356
347
|
identity: params.identity,
|
|
357
348
|
ok: true,
|
|
@@ -360,7 +351,21 @@ async function ensureCodexPluginActivation(params) {
|
|
|
360
351
|
marketplace: resolved.marketplace,
|
|
361
352
|
diagnostics: []
|
|
362
353
|
};
|
|
363
|
-
const
|
|
354
|
+
const remotePluginId = resolved.marketplace.remoteMarketplaceName ? resolved.summary.remotePluginId : void 0;
|
|
355
|
+
let installResponse;
|
|
356
|
+
try {
|
|
357
|
+
installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, remotePluginId ?? params.identity.pluginName));
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (!(error instanceof CodexAppServerRpcError) || error.code !== -32600 || !remotePluginId || error.message !== `remote plugin ${remotePluginId} is disabled by admin` && error.message !== `remote plugin ${remotePluginId} is not available for install`) throw error;
|
|
360
|
+
return {
|
|
361
|
+
identity: params.identity,
|
|
362
|
+
ok: false,
|
|
363
|
+
reason: "install_failed",
|
|
364
|
+
installAttempted: true,
|
|
365
|
+
marketplace: resolved.marketplace,
|
|
366
|
+
diagnostics: [{ message: `Codex plugin install failed: ${coerceErrorMessage(error)}` }]
|
|
367
|
+
};
|
|
368
|
+
}
|
|
364
369
|
if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
|
|
365
370
|
const refreshDiagnostics = [];
|
|
366
371
|
let refreshFailed = false;
|
|
@@ -369,13 +374,15 @@ async function ensureCodexPluginActivation(params) {
|
|
|
369
374
|
request: params.request,
|
|
370
375
|
appCache: params.appCache,
|
|
371
376
|
appCacheKey: params.appCacheKey,
|
|
377
|
+
configCwd: params.configCwd,
|
|
372
378
|
metadataCache: params.metadataCache,
|
|
379
|
+
deferAppInventoryRefresh: params.deferAppInventoryRefresh,
|
|
373
380
|
targetAppIds: params.targetAppIds
|
|
374
381
|
});
|
|
375
382
|
refreshDiagnostics.push(...refreshResult.diagnostics);
|
|
376
383
|
} catch (error) {
|
|
377
384
|
refreshFailed = true;
|
|
378
|
-
refreshDiagnostics.push({ message: `Codex plugin runtime refresh failed after install: ${
|
|
385
|
+
refreshDiagnostics.push({ message: `Codex plugin runtime refresh failed after install: ${coerceErrorMessage(error)}` });
|
|
379
386
|
}
|
|
380
387
|
const authRequired = installResponse.appsNeedingAuth.length > 0;
|
|
381
388
|
return {
|
|
@@ -391,19 +398,20 @@ async function ensureCodexPluginActivation(params) {
|
|
|
391
398
|
/** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
|
|
392
399
|
async function refreshCodexPluginRuntimeState(params) {
|
|
393
400
|
const diagnostics = [];
|
|
394
|
-
await listCuratedCodexPluginMetadata(params);
|
|
401
|
+
await listCuratedCodexPluginMetadata(params, { forceRefetch: true });
|
|
395
402
|
await params.request("skills/list", {
|
|
396
|
-
cwds: [],
|
|
403
|
+
cwds: params.configCwd ? [params.configCwd] : [],
|
|
397
404
|
forceReload: true
|
|
398
405
|
});
|
|
399
406
|
try {
|
|
400
|
-
await params.request("hooks/list", { cwds: [] });
|
|
407
|
+
await params.request("hooks/list", { cwds: params.configCwd ? [params.configCwd] : [] });
|
|
401
408
|
} catch (error) {
|
|
402
|
-
diagnostics.push({ message: `Codex hooks refresh skipped: ${
|
|
409
|
+
diagnostics.push({ message: `Codex hooks refresh skipped: ${coerceErrorMessage(error)}` });
|
|
403
410
|
}
|
|
404
411
|
await params.request("config/mcpServer/reload", void 0);
|
|
405
412
|
if (params.appCache && params.appCacheKey) {
|
|
406
|
-
params.appCache.invalidate(params.appCacheKey, "Codex plugin activation changed app inventory");
|
|
413
|
+
params.appCache.invalidate(params.appCacheKey, "Codex plugin activation changed app inventory", void 0, params.targetAppIds);
|
|
414
|
+
if (params.deferAppInventoryRefresh) return { diagnostics };
|
|
407
415
|
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
408
416
|
try {
|
|
409
417
|
await params.appCache.refreshNow({
|
|
@@ -413,20 +421,23 @@ async function refreshCodexPluginRuntimeState(params) {
|
|
|
413
421
|
targetAppIds: params.targetAppIds
|
|
414
422
|
});
|
|
415
423
|
} catch (error) {
|
|
416
|
-
diagnostics.push({ message: `Codex app inventory refresh skipped: ${
|
|
424
|
+
diagnostics.push({ message: `Codex app inventory refresh skipped: ${coerceErrorMessage(error)}` });
|
|
417
425
|
}
|
|
418
426
|
}
|
|
419
427
|
return { diagnostics };
|
|
420
428
|
}
|
|
421
|
-
async function listCuratedCodexPluginMetadata(params) {
|
|
422
|
-
const requestParams = {
|
|
429
|
+
async function listCuratedCodexPluginMetadata(params, options = {}) {
|
|
430
|
+
const requestParams = {
|
|
431
|
+
...params.configCwd ? { cwds: [params.configCwd] } : {},
|
|
432
|
+
...options.forceRefetch ? { forceRefetch: true } : {}
|
|
433
|
+
};
|
|
423
434
|
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
|
|
424
435
|
return (await params.metadataCache.load({
|
|
425
436
|
appCacheKey: params.appCacheKey,
|
|
426
437
|
queryKind: "curated-global",
|
|
427
438
|
requestParams,
|
|
428
439
|
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
429
|
-
cacheable: (response) =>
|
|
440
|
+
cacheable: (response) => response.marketplaces.some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
430
441
|
})).response;
|
|
431
442
|
}
|
|
432
443
|
function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
|
|
@@ -439,6 +450,129 @@ function activationFailure(identity, reason, diagnostic, extraDiagnostics = [])
|
|
|
439
450
|
};
|
|
440
451
|
}
|
|
441
452
|
//#endregion
|
|
453
|
+
//#region extensions/codex/src/app-server/plugin-thread-app-admission.ts
|
|
454
|
+
async function refreshCodexPluginAppInventory(params, appCache, options = {}) {
|
|
455
|
+
if (!params.appCacheKey) return;
|
|
456
|
+
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
457
|
+
try {
|
|
458
|
+
return await appCache.refreshNow({
|
|
459
|
+
key: params.appCacheKey,
|
|
460
|
+
request,
|
|
461
|
+
nowMs: params.nowMs,
|
|
462
|
+
forceRefetch: options.forceRefetch,
|
|
463
|
+
targetAppIds: options.targetAppIds
|
|
464
|
+
});
|
|
465
|
+
} catch (error) {
|
|
466
|
+
embeddedAgentLog.warn("codex plugin thread config app inventory refresh failed", {
|
|
467
|
+
reason: options.reason,
|
|
468
|
+
forceRefetch: options.forceRefetch === true,
|
|
469
|
+
error: serializeCodexAppInventoryError(error)
|
|
470
|
+
});
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function collectCodexPluginOwnedAppIds(inventory) {
|
|
475
|
+
return Array.from(new Set(inventory.records.flatMap((record) => record.ownedAppIds).filter(Boolean))).toSorted();
|
|
476
|
+
}
|
|
477
|
+
function collectCodexReservedPluginAppIds(params) {
|
|
478
|
+
const reserved = new Set(params.inventory.records.flatMap((record) => record.appOwnership === "proven" ? record.ownedAppIds : []));
|
|
479
|
+
const recordsByConfigKey = new Map(params.inventory.records.map((record) => [record.policy.configKey, record]));
|
|
480
|
+
const configuredOwnerNames = new Set(params.policy.pluginPolicies.flatMap((policy) => {
|
|
481
|
+
const record = recordsByConfigKey.get(policy.configKey);
|
|
482
|
+
return [
|
|
483
|
+
policy.configKey,
|
|
484
|
+
policy.pluginName,
|
|
485
|
+
record?.summary.name,
|
|
486
|
+
record?.summary.id
|
|
487
|
+
].filter((name) => Boolean(name)).map(normalizeCodexPluginOwnerName);
|
|
488
|
+
}));
|
|
489
|
+
for (const app of params.accountApps) if (app.pluginDisplayNames.some((name) => configuredOwnerNames.has(normalizeCodexPluginOwnerName(name)))) reserved.add(app.id);
|
|
490
|
+
return reserved;
|
|
491
|
+
}
|
|
492
|
+
function normalizeCodexPluginOwnerName(name) {
|
|
493
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
494
|
+
}
|
|
495
|
+
async function readCodexThreadAdmissibleAccountApps(params, appCache) {
|
|
496
|
+
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
497
|
+
const cachedInventory = appCache.read({
|
|
498
|
+
key: params.appCacheKey,
|
|
499
|
+
request,
|
|
500
|
+
nowMs: params.nowMs,
|
|
501
|
+
suppressRefresh: true
|
|
502
|
+
});
|
|
503
|
+
const snapshot = cachedInventory.state === "fresh" && !cachedInventory.snapshot?.targetAppIds?.length ? cachedInventory.snapshot : await refreshCodexPluginAppInventory(params, appCache, {
|
|
504
|
+
forceRefetch: false,
|
|
505
|
+
reason: "account_apps_all",
|
|
506
|
+
targetAppIds: []
|
|
507
|
+
});
|
|
508
|
+
if (!snapshot) return {
|
|
509
|
+
apps: [],
|
|
510
|
+
diagnostic: {
|
|
511
|
+
code: "account_app_inventory_unavailable",
|
|
512
|
+
message: "Codex account app inventory was unavailable; account apps were not exposed."
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const installedAppsById = new Map(snapshot.installedApps.map((app) => [app.id, app]));
|
|
516
|
+
return { apps: snapshot.apps.filter((app) => resolveCodexInstalledAppThreadAdmission(toCodexPluginOwnedAccountApp(app), installedAppsById.get(app.id)) !== "blocked").toSorted((left, right) => left.id.localeCompare(right.id)) };
|
|
517
|
+
}
|
|
518
|
+
function toCodexPluginOwnedAccountApp(app) {
|
|
519
|
+
return {
|
|
520
|
+
id: app.id,
|
|
521
|
+
name: app.name,
|
|
522
|
+
accessible: app.isAccessible,
|
|
523
|
+
enabled: app.isEnabled,
|
|
524
|
+
needsAuth: !app.isAccessible
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function resolveCodexThreadConfigAppsForRecord(params) {
|
|
528
|
+
return params.inventory.appInventory?.state === "missing" ? [] : params.record.apps;
|
|
529
|
+
}
|
|
530
|
+
function resolveCodexPluginAppThreadAdmission(app, inventory) {
|
|
531
|
+
const snapshot = inventory.appInventory?.snapshot;
|
|
532
|
+
if (!snapshot) return "blocked";
|
|
533
|
+
return resolveCodexInstalledAppThreadAdmission(app, snapshot.installedApps.find((candidate) => candidate.id === app.id));
|
|
534
|
+
}
|
|
535
|
+
function resolveCodexInstalledAppThreadAdmission(app, installed) {
|
|
536
|
+
if (!app.accessible || app.needsAuth || !installed) return "blocked";
|
|
537
|
+
if (installed.enabled && installed.callable) return "ready";
|
|
538
|
+
return !installed.enabled && !installed.callable ? "provisional" : "blocked";
|
|
539
|
+
}
|
|
540
|
+
async function readCodexConfigForAppAdmission(params) {
|
|
541
|
+
try {
|
|
542
|
+
const response = await params.request("config/read", {
|
|
543
|
+
includeLayers: true,
|
|
544
|
+
...params.configCwd ? { cwd: params.configCwd } : {}
|
|
545
|
+
});
|
|
546
|
+
if (!isJsonObject(response) || !isJsonObject(response.config) || !Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config or config layers");
|
|
547
|
+
return {
|
|
548
|
+
config: response.config,
|
|
549
|
+
layers: response.layers.flatMap((layer) => {
|
|
550
|
+
if (!isJsonObject(layer)) throw new Error("Codex config/read returned an invalid config layer");
|
|
551
|
+
if (layer.disabledReason !== void 0 && layer.disabledReason !== null) {
|
|
552
|
+
if (typeof layer.disabledReason !== "string") throw new Error("Codex config/read returned an invalid disabled layer");
|
|
553
|
+
return [];
|
|
554
|
+
}
|
|
555
|
+
if (!isJsonObject(layer.config)) throw new Error("Codex config/read returned an invalid layer config");
|
|
556
|
+
return [layer.config];
|
|
557
|
+
})
|
|
558
|
+
};
|
|
559
|
+
} catch (error) {
|
|
560
|
+
embeddedAgentLog.warn("codex plugin app admission config read failed", { error: serializeCodexAppInventoryError(error) });
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function resolveCodexExplicitAppEnablement(layersHighestPrecedenceFirst, appId) {
|
|
565
|
+
for (const layer of layersHighestPrecedenceFirst) {
|
|
566
|
+
const apps = layer.apps;
|
|
567
|
+
const app = isJsonObject(apps) ? apps[appId] : void 0;
|
|
568
|
+
if (isJsonObject(app) && Object.hasOwn(app, "enabled")) return app.enabled === true;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
|
|
572
|
+
if (!params.appCacheKey || !policy.pluginPolicies.some((plugin) => plugin.enabled) || inventory.appInventory?.state === "missing") return false;
|
|
573
|
+
return inventory.records.some((record) => record.appOwnership === "proven" && record.ownedAppIds.length > 0 && (record.apps.length === 0 || record.apps.some((app) => !app.accessible)));
|
|
574
|
+
}
|
|
575
|
+
//#endregion
|
|
442
576
|
//#region extensions/codex/src/app-server/plugin-thread-config.ts
|
|
443
577
|
/**
|
|
444
578
|
* Builds Codex thread config patches that expose only policy-approved apps
|
|
@@ -492,16 +626,17 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
492
626
|
request: params.request,
|
|
493
627
|
appCache,
|
|
494
628
|
appCacheKey: params.appCacheKey,
|
|
629
|
+
configCwd: params.configCwd,
|
|
495
630
|
metadataCache: params.metadataCache,
|
|
496
631
|
nowMs: params.nowMs,
|
|
497
632
|
suppressAppInventoryRefresh: true
|
|
498
633
|
}) : emptyCodexPluginInventory(policy);
|
|
499
634
|
const appInventoryRefreshDeferredForActivation = inventory.records.some((record) => record.activationRequired) && shouldRefreshMissingAppInventory(params, policy, inventory);
|
|
500
635
|
if (shouldWaitForInitialAppInventory(params, policy, inventory)) {
|
|
501
|
-
await
|
|
636
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
502
637
|
forceRefetch: false,
|
|
503
638
|
reason: "initial_missing",
|
|
504
|
-
targetAppIds:
|
|
639
|
+
targetAppIds: collectCodexPluginOwnedAppIds(inventory)
|
|
505
640
|
});
|
|
506
641
|
inventory = await readCodexPluginInventory({
|
|
507
642
|
pluginConfig: params.pluginConfig,
|
|
@@ -509,6 +644,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
509
644
|
request: params.request,
|
|
510
645
|
appCache,
|
|
511
646
|
appCacheKey: params.appCacheKey,
|
|
647
|
+
configCwd: params.configCwd,
|
|
512
648
|
metadataCache: params.metadataCache,
|
|
513
649
|
nowMs: params.nowMs
|
|
514
650
|
});
|
|
@@ -526,7 +662,9 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
526
662
|
request: params.request,
|
|
527
663
|
appCache,
|
|
528
664
|
appCacheKey: params.appCacheKey,
|
|
665
|
+
configCwd: params.configCwd,
|
|
529
666
|
metadataCache: params.metadataCache,
|
|
667
|
+
deferAppInventoryRefresh: true,
|
|
530
668
|
targetAppIds: record.ownedAppIds
|
|
531
669
|
});
|
|
532
670
|
activationResults.push(activation);
|
|
@@ -539,10 +677,10 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
539
677
|
const postInstallRefreshRequired = activationResults.some((activation) => activation.ok && activation.installAttempted);
|
|
540
678
|
const deferredMissingRefreshRequired = appInventoryRefreshDeferredForActivation && !postInstallRefreshRequired && shouldRefreshMissingAppInventory(params, policy, inventory);
|
|
541
679
|
if (postInstallRefreshRequired || deferredMissingRefreshRequired) {
|
|
542
|
-
await
|
|
680
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
543
681
|
forceRefetch: true,
|
|
544
682
|
reason: postInstallRefreshRequired ? "post_install" : "deferred_missing",
|
|
545
|
-
targetAppIds:
|
|
683
|
+
targetAppIds: collectCodexPluginOwnedAppIds(inventory)
|
|
546
684
|
});
|
|
547
685
|
inventory = await readCodexPluginInventory({
|
|
548
686
|
pluginConfig: params.pluginConfig,
|
|
@@ -550,6 +688,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
550
688
|
request: params.request,
|
|
551
689
|
appCache,
|
|
552
690
|
appCacheKey: params.appCacheKey,
|
|
691
|
+
configCwd: params.configCwd,
|
|
553
692
|
metadataCache: params.metadataCache,
|
|
554
693
|
nowMs: params.nowMs
|
|
555
694
|
});
|
|
@@ -558,11 +697,11 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
558
697
|
appCacheKey: params.appCacheKey
|
|
559
698
|
});
|
|
560
699
|
}
|
|
561
|
-
if (
|
|
562
|
-
await
|
|
700
|
+
if (shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory)) {
|
|
701
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
563
702
|
forceRefetch: true,
|
|
564
703
|
reason: "not_ready_plugin_apps",
|
|
565
|
-
targetAppIds:
|
|
704
|
+
targetAppIds: collectCodexPluginOwnedAppIds(inventory)
|
|
566
705
|
});
|
|
567
706
|
inventory = await readCodexPluginInventory({
|
|
568
707
|
pluginConfig: params.pluginConfig,
|
|
@@ -570,6 +709,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
570
709
|
request: params.request,
|
|
571
710
|
appCache,
|
|
572
711
|
appCacheKey: params.appCacheKey,
|
|
712
|
+
configCwd: params.configCwd,
|
|
573
713
|
metadataCache: params.metadataCache,
|
|
574
714
|
nowMs: params.nowMs
|
|
575
715
|
});
|
|
@@ -578,12 +718,13 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
578
718
|
appCacheKey: params.appCacheKey
|
|
579
719
|
});
|
|
580
720
|
}
|
|
581
|
-
const accountAppsResult = policy.allowAllPlugins ? await
|
|
721
|
+
const accountAppsResult = policy.allowAllPlugins ? await readCodexThreadAdmissibleAccountApps(params, appCache) : { apps: [] };
|
|
582
722
|
const diagnostics = [
|
|
583
723
|
...inventory.diagnostics,
|
|
584
724
|
...activationDiagnostics,
|
|
585
725
|
...accountAppsResult.diagnostic ? [accountAppsResult.diagnostic] : []
|
|
586
726
|
];
|
|
727
|
+
const provisionalAppIds = /* @__PURE__ */ new Set();
|
|
587
728
|
const apps = { _default: {
|
|
588
729
|
enabled: false,
|
|
589
730
|
destructive_enabled: false,
|
|
@@ -591,17 +732,35 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
591
732
|
} };
|
|
592
733
|
const policyApps = {};
|
|
593
734
|
const pluginAppIds = {};
|
|
594
|
-
|
|
735
|
+
let configForAppAdmission;
|
|
736
|
+
const pluginOwnedAppIds = collectCodexReservedPluginAppIds({
|
|
737
|
+
policy,
|
|
738
|
+
inventory,
|
|
739
|
+
accountApps: accountAppsResult.apps
|
|
740
|
+
});
|
|
741
|
+
const unresolvedDisabledPluginOwnership = policy.allowAllPlugins ? policy.pluginPolicies.find((pluginPolicy) => {
|
|
742
|
+
const record = inventory.records.find((candidate) => candidate.policy.configKey === pluginPolicy.configKey);
|
|
743
|
+
const disabledByMarketplacePolicy = record?.summary.availability === "DISABLED_BY_ADMIN" || record?.summary.installPolicy === "NOT_AVAILABLE";
|
|
744
|
+
const unresolvedPluginIdentity = !record && inventory.diagnostics.some((diagnostic) => diagnostic.plugin?.configKey === pluginPolicy.configKey && (diagnostic.code === "plugin_disabled" || diagnostic.code === "plugin_missing" || diagnostic.code === "marketplace_missing"));
|
|
745
|
+
return (!pluginPolicy.enabled || disabledByMarketplacePolicy || unresolvedPluginIdentity) && !record?.detail;
|
|
746
|
+
}) : void 0;
|
|
747
|
+
if (unresolvedDisabledPluginOwnership) diagnostics.push({
|
|
748
|
+
code: "account_app_ownership_unavailable",
|
|
749
|
+
plugin: unresolvedDisabledPluginOwnership,
|
|
750
|
+
message: `Could not verify disabled Codex plugin app ownership for ${unresolvedDisabledPluginOwnership.pluginName}; account apps were not exposed.`
|
|
751
|
+
});
|
|
595
752
|
for (const record of inventory.records) {
|
|
753
|
+
if (!record.policy.enabled) continue;
|
|
596
754
|
const activation = activationResults.find((item) => item.identity.configKey === record.policy.configKey);
|
|
597
755
|
if (activation?.ok === false || record.activationRequired && !activation?.ok) continue;
|
|
598
756
|
if (record.appOwnership !== "proven") continue;
|
|
599
757
|
pluginAppIds[record.policy.configKey] = [...record.ownedAppIds].toSorted();
|
|
600
|
-
for (const app of
|
|
758
|
+
for (const app of resolveCodexThreadConfigAppsForRecord({
|
|
601
759
|
record,
|
|
602
760
|
inventory
|
|
603
761
|
})) {
|
|
604
|
-
|
|
762
|
+
const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await (configForAppAdmission ??= readCodexConfigForAppAdmission(params));
|
|
763
|
+
if (!admissionConfig || resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) {
|
|
605
764
|
diagnostics.push({
|
|
606
765
|
code: "app_not_ready",
|
|
607
766
|
plugin: record.policy,
|
|
@@ -612,35 +771,51 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
612
771
|
if (record.policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
|
|
613
772
|
request: params.request,
|
|
614
773
|
configCwd: params.configCwd,
|
|
774
|
+
config: admissionConfig.config,
|
|
615
775
|
plugin: record.policy,
|
|
616
776
|
app,
|
|
617
777
|
diagnostics
|
|
618
778
|
})) continue;
|
|
779
|
+
provisionalAppIds.add(app.id);
|
|
619
780
|
apps[app.id] = buildEnabledAppConfig(record.policy);
|
|
620
781
|
policyApps[app.id] = {
|
|
621
782
|
configKey: record.policy.configKey,
|
|
622
783
|
marketplaceName: record.policy.marketplaceName,
|
|
623
784
|
pluginName: record.policy.pluginName,
|
|
624
785
|
allowDestructiveActions: record.policy.allowDestructiveActions,
|
|
786
|
+
allowOpenWorld: true,
|
|
625
787
|
destructiveApprovalMode: record.policy.destructiveApprovalMode,
|
|
626
788
|
mcpServerNames: [...record.detail?.mcpServers ?? []].toSorted()
|
|
627
789
|
};
|
|
628
790
|
}
|
|
629
791
|
}
|
|
630
|
-
for (const app of accountAppsResult.apps) {
|
|
792
|
+
for (const app of unresolvedDisabledPluginOwnership ? [] : accountAppsResult.apps) {
|
|
631
793
|
if (pluginOwnedAppIds.has(app.id)) continue;
|
|
632
|
-
|
|
794
|
+
configForAppAdmission ??= readCodexConfigForAppAdmission(params);
|
|
795
|
+
const admissionConfig = await configForAppAdmission;
|
|
796
|
+
if (!admissionConfig) {
|
|
797
|
+
diagnostics.push({
|
|
798
|
+
code: "account_app_config_unavailable",
|
|
799
|
+
message: "Codex account app configuration was unavailable; account apps were not exposed."
|
|
800
|
+
});
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
if (resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) continue;
|
|
804
|
+
const accountApp = toCodexPluginOwnedAccountApp(app);
|
|
633
805
|
if (policy.destructiveApprovalMode === "ask" && !await clearPersistedAppToolApprovalOverrides({
|
|
634
806
|
request: params.request,
|
|
635
807
|
configCwd: params.configCwd,
|
|
808
|
+
config: admissionConfig.config,
|
|
636
809
|
app: accountApp,
|
|
637
810
|
diagnostics
|
|
638
811
|
})) continue;
|
|
812
|
+
provisionalAppIds.add(app.id);
|
|
639
813
|
apps[app.id] = buildEnabledAppConfig(policy);
|
|
640
814
|
policyApps[app.id] = {
|
|
641
815
|
source: "account",
|
|
642
816
|
appName: app.name,
|
|
643
817
|
allowDestructiveActions: policy.allowDestructiveActions,
|
|
818
|
+
allowOpenWorld: true,
|
|
644
819
|
destructiveApprovalMode: policy.destructiveApprovalMode,
|
|
645
820
|
mcpServerNames: []
|
|
646
821
|
};
|
|
@@ -650,6 +825,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
650
825
|
return {
|
|
651
826
|
enabled: true,
|
|
652
827
|
configPatch,
|
|
828
|
+
...provisionalAppIds.size > 0 ? { provisionalAppIds: Array.from(provisionalAppIds).toSorted() } : {},
|
|
653
829
|
fingerprint: fingerprintJson({
|
|
654
830
|
version: CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION,
|
|
655
831
|
inputFingerprint,
|
|
@@ -719,7 +895,7 @@ function buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext) {
|
|
|
719
895
|
for (const [appId, policy] of Object.entries(policyContext.apps).toSorted(([left], [right]) => left.localeCompare(right))) apps[appId] = {
|
|
720
896
|
enabled: true,
|
|
721
897
|
destructive_enabled: policy.allowDestructiveActions,
|
|
722
|
-
open_world_enabled:
|
|
898
|
+
open_world_enabled: policy.allowOpenWorld !== false,
|
|
723
899
|
default_tools_approval_mode: "auto",
|
|
724
900
|
...policy.destructiveApprovalMode === "ask" ? { approvals_reviewer: "user" } : {}
|
|
725
901
|
};
|
|
@@ -738,13 +914,22 @@ function buildPluginAppPolicyContext(apps, pluginAppIds) {
|
|
|
738
914
|
}
|
|
739
915
|
async function clearPersistedAppToolApprovalOverrides(params) {
|
|
740
916
|
try {
|
|
741
|
-
const overrideNames =
|
|
742
|
-
|
|
917
|
+
const overrideNames = readPersistedAppToolApprovalOverrideNames(params.config, params.app);
|
|
918
|
+
if (overrideNames.length === 0) return true;
|
|
919
|
+
const edits = overrideNames.map((toolName) => ({
|
|
743
920
|
keyPath: `apps.${quoteConfigKeyPathSegment(params.app.id)}.tools.${quoteConfigKeyPathSegment(toolName)}.approval_mode`,
|
|
744
921
|
value: null,
|
|
745
922
|
mergeStrategy: "replace"
|
|
746
|
-
}))
|
|
747
|
-
const
|
|
923
|
+
}));
|
|
924
|
+
const response = await params.request("config/batchWrite", { edits });
|
|
925
|
+
if (!isJsonObject(response) || response.status !== "ok" && response.status !== "okOverridden") throw new Error("Codex did not confirm the approval override batch");
|
|
926
|
+
if (response.status === "okOverridden") throw new Error(`approval override for ${overrideNames.join(", ")} is controlled by another config layer`);
|
|
927
|
+
const confirmed = await params.request("config/read", {
|
|
928
|
+
includeLayers: false,
|
|
929
|
+
...params.configCwd ? { cwd: params.configCwd } : {}
|
|
930
|
+
});
|
|
931
|
+
if (!isJsonObject(confirmed) || !isJsonObject(confirmed.config)) throw new Error("Codex did not confirm effective app approval configuration");
|
|
932
|
+
const remainingOverrideNames = readPersistedAppToolApprovalOverrideNames(confirmed.config, params.app);
|
|
748
933
|
if (remainingOverrideNames.length > 0) throw new Error(`effective approval overrides remain for ${remainingOverrideNames.join(", ")}`);
|
|
749
934
|
return true;
|
|
750
935
|
} catch (error) {
|
|
@@ -756,24 +941,15 @@ async function clearPersistedAppToolApprovalOverrides(params) {
|
|
|
756
941
|
return false;
|
|
757
942
|
}
|
|
758
943
|
}
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
...params.configCwd ? { cwd: params.configCwd } : {}
|
|
763
|
-
});
|
|
764
|
-
const config = isJsonObject(response) ? response.config : void 0;
|
|
765
|
-
const appsRoot = isJsonObject(config) ? config.apps : void 0;
|
|
766
|
-
const nestedApps = isJsonObject(appsRoot) ? appsRoot.apps : void 0;
|
|
767
|
-
const appConfig = isJsonObject(appsRoot) ? appsRoot[params.app.id] ?? (isJsonObject(nestedApps) ? nestedApps[params.app.id] : void 0) : void 0;
|
|
944
|
+
function readPersistedAppToolApprovalOverrideNames(config, app) {
|
|
945
|
+
const appsRoot = config.apps;
|
|
946
|
+
const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
|
|
768
947
|
const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
|
|
769
948
|
if (!isJsonObject(tools)) return [];
|
|
770
949
|
return Object.entries(tools).filter(([, value]) => hasPersistedToolApprovalOverride(value)).map(([toolName]) => toolName).toSorted();
|
|
771
950
|
}
|
|
772
951
|
function hasPersistedToolApprovalOverride(value) {
|
|
773
|
-
return isJsonObject(value) &&
|
|
774
|
-
}
|
|
775
|
-
function isOverriddenConfigWriteResponse(response) {
|
|
776
|
-
return isJsonObject(response) && response.status === "okOverridden";
|
|
952
|
+
return isJsonObject(value) && value.approval_mode !== void 0;
|
|
777
953
|
}
|
|
778
954
|
function quoteConfigKeyPathSegment(segment) {
|
|
779
955
|
return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
|
|
@@ -785,30 +961,6 @@ function shouldWaitForInitialAppInventory(params, policy, inventory) {
|
|
|
785
961
|
function shouldRefreshMissingAppInventory(params, policy, inventory) {
|
|
786
962
|
return Boolean(params.appCacheKey && policy.pluginPolicies.some((plugin) => plugin.enabled) && inventory.appInventory?.state === "missing");
|
|
787
963
|
}
|
|
788
|
-
async function refreshAppInventoryNow(params, appCache, options = {}) {
|
|
789
|
-
const appCacheKey = params.appCacheKey;
|
|
790
|
-
if (!appCacheKey) return;
|
|
791
|
-
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
792
|
-
try {
|
|
793
|
-
return await appCache.refreshNow({
|
|
794
|
-
key: appCacheKey,
|
|
795
|
-
request,
|
|
796
|
-
nowMs: params.nowMs,
|
|
797
|
-
forceRefetch: options.forceRefetch,
|
|
798
|
-
targetAppIds: options.targetAppIds
|
|
799
|
-
});
|
|
800
|
-
} catch (error) {
|
|
801
|
-
embeddedAgentLog.warn("codex plugin thread config app inventory refresh failed", {
|
|
802
|
-
reason: options.reason,
|
|
803
|
-
forceRefetch: options.forceRefetch === true,
|
|
804
|
-
error: serializeCodexAppInventoryError(error)
|
|
805
|
-
});
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
function collectInventoryOwnedAppIds(inventory) {
|
|
810
|
-
return Array.from(new Set(inventory.records.flatMap((record) => record.ownedAppIds).filter(Boolean))).toSorted();
|
|
811
|
-
}
|
|
812
964
|
function emptyCodexPluginInventory(policy) {
|
|
813
965
|
return {
|
|
814
966
|
policy,
|
|
@@ -816,42 +968,6 @@ function emptyCodexPluginInventory(policy) {
|
|
|
816
968
|
diagnostics: []
|
|
817
969
|
};
|
|
818
970
|
}
|
|
819
|
-
async function readAccessibleAccountApps(params, appCache) {
|
|
820
|
-
const snapshot = await refreshAppInventoryNow(params, appCache, {
|
|
821
|
-
forceRefetch: false,
|
|
822
|
-
reason: "account_apps_all",
|
|
823
|
-
targetAppIds: []
|
|
824
|
-
});
|
|
825
|
-
if (!snapshot) return {
|
|
826
|
-
apps: [],
|
|
827
|
-
diagnostic: {
|
|
828
|
-
code: "account_app_inventory_unavailable",
|
|
829
|
-
message: "Codex account app inventory was unavailable; account apps were not exposed."
|
|
830
|
-
}
|
|
831
|
-
};
|
|
832
|
-
return { apps: snapshot.apps.filter((app) => app.isAccessible).toSorted((left, right) => left.id.localeCompare(right.id)) };
|
|
833
|
-
}
|
|
834
|
-
function toOwnedAccountApp(app) {
|
|
835
|
-
return {
|
|
836
|
-
id: app.id,
|
|
837
|
-
name: app.name,
|
|
838
|
-
accessible: app.isAccessible,
|
|
839
|
-
enabled: app.isEnabled,
|
|
840
|
-
needsAuth: !app.isAccessible
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
function resolveThreadConfigAppsForRecord(params) {
|
|
844
|
-
if (params.inventory.appInventory?.state === "missing") return [];
|
|
845
|
-
return params.record.apps;
|
|
846
|
-
}
|
|
847
|
-
function isPluginAppReadyForThreadStart(app) {
|
|
848
|
-
return app.accessible;
|
|
849
|
-
}
|
|
850
|
-
function shouldForceRefreshForNotReadyPluginApps(params, policy, inventory) {
|
|
851
|
-
if (!params.appCacheKey || !policy.pluginPolicies.some((plugin) => plugin.enabled)) return false;
|
|
852
|
-
if (inventory.appInventory?.state === "missing") return false;
|
|
853
|
-
return inventory.records.some((record) => record.appOwnership === "proven" && record.ownedAppIds.length > 0 && (record.apps.length === 0 || record.apps.some((app) => !app.accessible)));
|
|
854
|
-
}
|
|
855
971
|
function policyFingerprint(policy) {
|
|
856
972
|
return {
|
|
857
973
|
enabled: policy.enabled,
|
|
@@ -872,13 +988,10 @@ function mergeJsonObjects(left, right) {
|
|
|
872
988
|
const merged = { ...left };
|
|
873
989
|
for (const [key, value] of Object.entries(right)) {
|
|
874
990
|
const existing = merged[key];
|
|
875
|
-
merged[key] =
|
|
991
|
+
merged[key] = isJsonObject(existing) && isJsonObject(value) ? mergeJsonObjects(existing, value) : value;
|
|
876
992
|
}
|
|
877
993
|
return merged;
|
|
878
994
|
}
|
|
879
|
-
function isPlainJsonObject(value) {
|
|
880
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
881
|
-
}
|
|
882
995
|
function fingerprintJson(value) {
|
|
883
996
|
return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
884
997
|
}
|
|
@@ -904,15 +1017,18 @@ const CODEX_TURN_START_TEXT_INPUT_MAX_CHARS = 1 << 20;
|
|
|
904
1017
|
const DEFAULT_CODEX_PROJECTION_RESERVE_TOKENS = 2e4;
|
|
905
1018
|
const MIN_PROMPT_BUDGET_RATIO = .5;
|
|
906
1019
|
const MIN_PROMPT_BUDGET_TOKENS = 8e3;
|
|
1020
|
+
function neutralizeCodexExplicitMentionSigils(text) {
|
|
1021
|
+
return text.replace(/\$(?=[A-Za-z0-9_:-])/gu, "$").replace(/\[@(?=[A-Za-z0-9_:-]+\]\()/gu, "[@");
|
|
1022
|
+
}
|
|
907
1023
|
/** Projects assembled OpenClaw context-engine messages into Codex prompt inputs. */
|
|
908
1024
|
function projectContextEngineAssemblyForCodex(params) {
|
|
909
1025
|
const prompt = params.prompt.trim();
|
|
910
1026
|
const contextMessages = dropDuplicateTrailingPrompt(params.assembledMessages, prompt);
|
|
911
1027
|
const maxRenderedContextChars = normalizeRenderedContextMaxChars(params.maxRenderedContextChars);
|
|
912
|
-
const renderedContext = renderMessagesForCodexContext(contextMessages, {
|
|
1028
|
+
const renderedContext = neutralizeCodexExplicitMentionSigils(renderMessagesForCodexContext(contextMessages, {
|
|
913
1029
|
maxTextPartChars: resolveTextPartMaxChars(maxRenderedContextChars),
|
|
914
1030
|
toolPayloadMode: params.toolPayloadMode ?? "elide"
|
|
915
|
-
});
|
|
1031
|
+
}));
|
|
916
1032
|
const boundedContext = renderedContext ? truncateOlderContext(renderedContext, maxRenderedContextChars) : void 0;
|
|
917
1033
|
const promptPrefix = boundedContext ? [
|
|
918
1034
|
CONTEXT_HEADER,
|
|
@@ -1113,8 +1229,8 @@ function hasMessageContent(message) {
|
|
|
1113
1229
|
return "content" in message;
|
|
1114
1230
|
}
|
|
1115
1231
|
function normalizeRenderedContextMaxChars(value) {
|
|
1116
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RENDERED_CONTEXT_CHARS;
|
|
1117
|
-
return Math.min(MAX_RENDERED_CONTEXT_CHARS, Math.max(
|
|
1232
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return DEFAULT_RENDERED_CONTEXT_CHARS;
|
|
1233
|
+
return Math.min(MAX_RENDERED_CONTEXT_CHARS, Math.max(1, Math.floor(value)));
|
|
1118
1234
|
}
|
|
1119
1235
|
function resolveTextPartMaxChars(maxRenderedContextChars) {
|
|
1120
1236
|
return Math.min(MAX_TEXT_PART_CHARS, Math.max(DEFAULT_TEXT_PART_CHARS, Math.floor(maxRenderedContextChars / 4)));
|
|
@@ -1176,107 +1292,15 @@ function areContextEngineProjectionBindingsCompatible(previous, next) {
|
|
|
1176
1292
|
return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
|
|
1177
1293
|
}
|
|
1178
1294
|
function resolveContextEngineCitationsMode(config) {
|
|
1179
|
-
const rootConfig =
|
|
1180
|
-
const citations = (
|
|
1295
|
+
const rootConfig = isRecord(config) ? config : void 0;
|
|
1296
|
+
const citations = (isRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
|
|
1181
1297
|
return isJsonConfigValue(citations) ? citations : void 0;
|
|
1182
1298
|
}
|
|
1183
|
-
function isUnknownRecord(value) {
|
|
1184
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1185
|
-
}
|
|
1186
1299
|
function isJsonConfigValue(value) {
|
|
1187
1300
|
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1188
1301
|
if (typeof value === "number") return Number.isFinite(value);
|
|
1189
1302
|
if (Array.isArray(value)) return value.every(isJsonConfigValue);
|
|
1190
|
-
return
|
|
1191
|
-
}
|
|
1192
|
-
//#endregion
|
|
1193
|
-
//#region extensions/codex/src/app-server/thread-fingerprints.ts
|
|
1194
|
-
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
1195
|
-
return fingerprintDynamicTools(dynamicTools);
|
|
1196
|
-
}
|
|
1197
|
-
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
1198
|
-
return legacyFingerprintDynamicTools(dynamicTools);
|
|
1199
|
-
}
|
|
1200
|
-
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
1201
|
-
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
1202
|
-
}
|
|
1203
|
-
function fingerprintDynamicTools(dynamicTools) {
|
|
1204
|
-
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
1205
|
-
}
|
|
1206
|
-
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
1207
|
-
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
1208
|
-
}
|
|
1209
|
-
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1210
|
-
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
1211
|
-
}
|
|
1212
|
-
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1213
|
-
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
|
|
1214
|
-
}
|
|
1215
|
-
function redactUserMcpServersFingerprintSecrets(value) {
|
|
1216
|
-
if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
|
|
1217
|
-
if (!value || typeof value !== "object") return value;
|
|
1218
|
-
const next = {};
|
|
1219
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1220
|
-
if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1221
|
-
next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
|
|
1222
|
-
continue;
|
|
1223
|
-
}
|
|
1224
|
-
next[key] = redactUserMcpServersFingerprintSecrets(entry);
|
|
1225
|
-
}
|
|
1226
|
-
return next;
|
|
1227
|
-
}
|
|
1228
|
-
function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
1229
|
-
return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
|
|
1230
|
-
}
|
|
1231
|
-
function fingerprintJsonObject(value) {
|
|
1232
|
-
return JSON.stringify(stabilizeJsonValue(value));
|
|
1233
|
-
}
|
|
1234
|
-
function fingerprintEnvironmentSelection(environments) {
|
|
1235
|
-
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
1236
|
-
}
|
|
1237
|
-
function fingerprintDynamicToolSpec(tool) {
|
|
1238
|
-
return stabilizeDynamicToolFingerprintValue(tool);
|
|
1239
|
-
}
|
|
1240
|
-
function stabilizeDynamicToolFingerprintValue(value) {
|
|
1241
|
-
if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
|
|
1242
|
-
if (!isJsonObject(value)) return value;
|
|
1243
|
-
const stable = {};
|
|
1244
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
1245
|
-
if (key === "description") continue;
|
|
1246
|
-
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
|
1247
|
-
}
|
|
1248
|
-
return stable;
|
|
1249
|
-
}
|
|
1250
|
-
function stabilizeJsonValue(value) {
|
|
1251
|
-
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
1252
|
-
if (!isJsonObject(value)) return value;
|
|
1253
|
-
const stable = {};
|
|
1254
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
1255
|
-
return stable;
|
|
1256
|
-
}
|
|
1257
|
-
function readActiveCodexTurnIds(thread) {
|
|
1258
|
-
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
1259
|
-
}
|
|
1260
|
-
function readActiveCodexTurnIdsFromResume(response) {
|
|
1261
|
-
const pagedTurns = response.initialTurnsPage?.data;
|
|
1262
|
-
return readActiveCodexTurnIds(Array.isArray(pagedTurns) ? { turns: pagedTurns } : response.thread);
|
|
1263
|
-
}
|
|
1264
|
-
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
1265
|
-
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
1266
|
-
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
1267
|
-
return !previous || previous === next || previous === nextLegacy;
|
|
1268
|
-
}
|
|
1269
|
-
function areUserMcpServersFingerprintsCompatible(params) {
|
|
1270
|
-
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
1271
|
-
}
|
|
1272
|
-
function shouldStartTransientNoToolThread(params) {
|
|
1273
|
-
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
1274
|
-
}
|
|
1275
|
-
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
1276
|
-
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
1277
|
-
}
|
|
1278
|
-
function compareJsonFingerprint(left, right) {
|
|
1279
|
-
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1303
|
+
return isRecord(value) && Object.values(value).every(isJsonConfigValue);
|
|
1280
1304
|
}
|
|
1281
1305
|
//#endregion
|
|
1282
1306
|
//#region extensions/codex/src/app-server/dynamic-tool-profile.ts
|
|
@@ -1299,6 +1323,14 @@ const CODEX_NATIVE_GOAL_TOOL_EXCLUDES = [
|
|
|
1299
1323
|
"create_goal",
|
|
1300
1324
|
"update_goal"
|
|
1301
1325
|
];
|
|
1326
|
+
const CODEX_APP_SERVER_OWNED_REPLACEABLE_TOOL_EXCLUDES = /* @__PURE__ */ new Set([
|
|
1327
|
+
"read",
|
|
1328
|
+
"write",
|
|
1329
|
+
"edit",
|
|
1330
|
+
"apply_patch",
|
|
1331
|
+
"update_plan",
|
|
1332
|
+
...CODEX_NATIVE_GOAL_TOOL_EXCLUDES
|
|
1333
|
+
]);
|
|
1302
1334
|
const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
|
|
1303
1335
|
const DYNAMIC_TOOL_NAME_ALIASES = {
|
|
1304
1336
|
bash: "exec",
|
|
@@ -1313,6 +1345,10 @@ function normalizeCodexDynamicToolName(name) {
|
|
|
1313
1345
|
function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
|
|
1314
1346
|
return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
|
|
1315
1347
|
}
|
|
1348
|
+
/** True when a private source reply may use the message delivery tool only. */
|
|
1349
|
+
function isMessageOnlyCodexSourceReply(params) {
|
|
1350
|
+
return params.sourceReplyDeliveryMode === "message_tool_only" && params.toolsAllow?.length === 1 && normalizeCodexDynamicToolName(params.toolsAllow[0] ?? "") === "message";
|
|
1351
|
+
}
|
|
1316
1352
|
/** Returns true for private QA runs that force the Codex runtime profile. */
|
|
1317
1353
|
function isForcedPrivateQaCodexRuntime(env = process.env) {
|
|
1318
1354
|
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
@@ -1346,17 +1382,24 @@ function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}
|
|
|
1346
1382
|
}
|
|
1347
1383
|
/** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
|
|
1348
1384
|
function filterCodexDynamicTools(tools, config, env = process.env) {
|
|
1349
|
-
return filterCodexDynamicToolsWithOptions(tools, config, env, {
|
|
1385
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, {
|
|
1386
|
+
preserveOpenClawReplacements: false,
|
|
1387
|
+
preserveOpenClawShell: false
|
|
1388
|
+
});
|
|
1350
1389
|
}
|
|
1351
|
-
/** Keeps
|
|
1352
|
-
function
|
|
1353
|
-
return filterCodexDynamicToolsWithOptions(tools, config, env, {
|
|
1390
|
+
/** Keeps OpenClaw coding tools that replace a disabled Codex native surface. */
|
|
1391
|
+
function filterCodexDynamicToolsForDisabledNativeSurface(tools, config, options, env = process.env) {
|
|
1392
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, {
|
|
1393
|
+
preserveOpenClawReplacements: true,
|
|
1394
|
+
preserveOpenClawShell: options.preserveShell
|
|
1395
|
+
});
|
|
1354
1396
|
}
|
|
1355
1397
|
function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
1356
1398
|
const excludes = /* @__PURE__ */ new Set();
|
|
1357
|
-
for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
|
|
1399
|
+
if (!options.preserveOpenClawReplacements) for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
|
|
1358
1400
|
if (isForcedPrivateQaCodexRuntime(env)) excludes.add("apply_patch");
|
|
1359
1401
|
else for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
|
|
1402
|
+
if (options.preserveOpenClawReplacements && CODEX_APP_SERVER_OWNED_REPLACEABLE_TOOL_EXCLUDES.has(name)) continue;
|
|
1360
1403
|
if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
|
|
1361
1404
|
excludes.add(name);
|
|
1362
1405
|
}
|
|
@@ -1367,13 +1410,6 @@ function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
|
1367
1410
|
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
1368
1411
|
}
|
|
1369
1412
|
//#endregion
|
|
1370
|
-
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
1371
|
-
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
1372
|
-
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
1373
|
-
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
1374
|
-
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
1375
|
-
}
|
|
1376
|
-
//#endregion
|
|
1377
1413
|
//#region extensions/codex/src/app-server/thread-binding-policy.ts
|
|
1378
1414
|
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
1379
1415
|
if (!params.current) return false;
|
|
@@ -1424,6 +1460,299 @@ function shouldRecheckRecoverablePluginBinding(params) {
|
|
|
1424
1460
|
return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
|
|
1425
1461
|
}
|
|
1426
1462
|
//#endregion
|
|
1463
|
+
//#region extensions/codex/src/app-server/thread-fingerprints.ts
|
|
1464
|
+
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
1465
|
+
return fingerprintDynamicTools(dynamicTools);
|
|
1466
|
+
}
|
|
1467
|
+
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
1468
|
+
return legacyFingerprintDynamicTools(dynamicTools);
|
|
1469
|
+
}
|
|
1470
|
+
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
1471
|
+
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
1472
|
+
}
|
|
1473
|
+
function fingerprintDynamicTools(dynamicTools) {
|
|
1474
|
+
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
1475
|
+
}
|
|
1476
|
+
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
1477
|
+
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
1478
|
+
}
|
|
1479
|
+
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1480
|
+
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
1481
|
+
}
|
|
1482
|
+
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1483
|
+
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
|
|
1484
|
+
}
|
|
1485
|
+
function redactUserMcpServersFingerprintSecrets(value) {
|
|
1486
|
+
if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
|
|
1487
|
+
if (!value || typeof value !== "object") return value;
|
|
1488
|
+
const next = {};
|
|
1489
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1490
|
+
if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1491
|
+
next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
next[key] = redactUserMcpServersFingerprintSecrets(entry);
|
|
1495
|
+
}
|
|
1496
|
+
return next;
|
|
1497
|
+
}
|
|
1498
|
+
function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
1499
|
+
return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
|
|
1500
|
+
}
|
|
1501
|
+
function fingerprintJsonObject(value) {
|
|
1502
|
+
return JSON.stringify(stabilizeJsonValue(value));
|
|
1503
|
+
}
|
|
1504
|
+
/** Hash thread-creation identity; settings already applied by turn/start must not restart Codex. */
|
|
1505
|
+
function fingerprintCodexThreadConfig(request, authProfileId, dynamicToolsFingerprint) {
|
|
1506
|
+
return hashCodexAppServerBindingFingerprint(fingerprintJsonObject({
|
|
1507
|
+
authProfileId: authProfileId ?? null,
|
|
1508
|
+
dynamicToolsFingerprint: dynamicToolsFingerprint ?? null,
|
|
1509
|
+
nativeMultiAgentVersion: resolveCodexGpt56MultiAgentVersion(typeof request.requestedModel === "string" ? request.requestedModel : typeof request.model === "string" ? request.model : void 0) ?? null,
|
|
1510
|
+
modelProvider: request.modelProvider ?? null,
|
|
1511
|
+
requestedModelProvider: request.requestedModelProvider === void 0 ? request.modelProvider ?? null : request.requestedModelProvider,
|
|
1512
|
+
permissions: request.permissions ?? null,
|
|
1513
|
+
baseInstructions: request.baseInstructions ?? null,
|
|
1514
|
+
developerInstructions: request.developerInstructions ?? null,
|
|
1515
|
+
config: request.config ?? {}
|
|
1516
|
+
}));
|
|
1517
|
+
}
|
|
1518
|
+
function fingerprintEnvironmentSelection(environments) {
|
|
1519
|
+
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
1520
|
+
}
|
|
1521
|
+
function fingerprintDynamicToolSpec(tool) {
|
|
1522
|
+
return stabilizeJsonValue(tool);
|
|
1523
|
+
}
|
|
1524
|
+
function stabilizeJsonValue(value) {
|
|
1525
|
+
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
1526
|
+
if (!isJsonObject(value)) return value;
|
|
1527
|
+
const stable = {};
|
|
1528
|
+
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
1529
|
+
return stable;
|
|
1530
|
+
}
|
|
1531
|
+
function readActiveCodexTurnIds(thread) {
|
|
1532
|
+
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
1533
|
+
}
|
|
1534
|
+
function readActiveCodexTurnIdsFromResume(response) {
|
|
1535
|
+
const pagedTurns = response.initialTurnsPage?.data;
|
|
1536
|
+
return readActiveCodexTurnIds(Array.isArray(pagedTurns) ? { turns: pagedTurns } : response.thread);
|
|
1537
|
+
}
|
|
1538
|
+
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
1539
|
+
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
1540
|
+
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
1541
|
+
return !previous || previous === next || previous === nextLegacy;
|
|
1542
|
+
}
|
|
1543
|
+
function areUserMcpServersFingerprintsCompatible(params) {
|
|
1544
|
+
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
1545
|
+
}
|
|
1546
|
+
function shouldStartTransientNoToolThread(params) {
|
|
1547
|
+
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
1548
|
+
}
|
|
1549
|
+
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
1550
|
+
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
1551
|
+
}
|
|
1552
|
+
function compareJsonFingerprint(left, right) {
|
|
1553
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1554
|
+
}
|
|
1555
|
+
//#endregion
|
|
1556
|
+
//#region extensions/codex/src/app-server/native-skill-isolation.ts
|
|
1557
|
+
const MAX_PERSONAL_SKILL_DIRECTORIES = 2e3;
|
|
1558
|
+
const MAX_PERSONAL_SKILL_DEPTH = 6;
|
|
1559
|
+
const MAX_PERSONAL_SKILL_ENTRIES = 1e4;
|
|
1560
|
+
const nativeSkillIsolationByClient = /* @__PURE__ */ new WeakMap();
|
|
1561
|
+
function isMissingPathError(error) {
|
|
1562
|
+
return error.code === "ENOENT";
|
|
1563
|
+
}
|
|
1564
|
+
function isPathWithin(root, candidate) {
|
|
1565
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
1566
|
+
return relative === "" || !path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== "..";
|
|
1567
|
+
}
|
|
1568
|
+
async function canonicalizeExistingPath(candidate) {
|
|
1569
|
+
try {
|
|
1570
|
+
return await fs.realpath(candidate);
|
|
1571
|
+
} catch {
|
|
1572
|
+
return path.resolve(candidate);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
async function usesDefaultStateDir() {
|
|
1576
|
+
if (!process.env.OPENCLAW_STATE_DIR?.trim()) return true;
|
|
1577
|
+
const home = resolveRequiredHomeDir();
|
|
1578
|
+
const [stateDir, defaultStateDir] = await Promise.all([canonicalizeExistingPath(resolveStateDir()), canonicalizeExistingPath(path.join(home, ".openclaw"))]);
|
|
1579
|
+
return stateDir === defaultStateDir;
|
|
1580
|
+
}
|
|
1581
|
+
async function collectPersonalSkillRealPaths(homes, codexHome) {
|
|
1582
|
+
const realStateDir = await canonicalizeExistingPath(resolveStateDir());
|
|
1583
|
+
const roots = [];
|
|
1584
|
+
for (const home of homes) {
|
|
1585
|
+
for (const dir of [".agents", ".claude"]) roots.push({
|
|
1586
|
+
dir: path.join(home, dir, "skills"),
|
|
1587
|
+
onlyEscapedStateTargets: false
|
|
1588
|
+
});
|
|
1589
|
+
const defaultCodexHome = path.join(home, ".codex");
|
|
1590
|
+
const realDefaultCodexHome = await canonicalizeExistingPath(defaultCodexHome);
|
|
1591
|
+
roots.push({
|
|
1592
|
+
dir: path.join(defaultCodexHome, "skills"),
|
|
1593
|
+
onlyEscapedStateTargets: isPathWithin(realStateDir, realDefaultCodexHome)
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
const configuredCodexHome = codexHome?.trim() || process.env.CODEX_HOME?.trim();
|
|
1597
|
+
if (configuredCodexHome) {
|
|
1598
|
+
const stateOwned = isPathWithin(realStateDir, await canonicalizeExistingPath(configuredCodexHome));
|
|
1599
|
+
roots.push({
|
|
1600
|
+
dir: path.join(configuredCodexHome, "skills"),
|
|
1601
|
+
onlyEscapedStateTargets: stateOwned
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
const skillPaths = /* @__PURE__ */ new Set();
|
|
1605
|
+
let complete = true;
|
|
1606
|
+
const seenDirectories = /* @__PURE__ */ new Set();
|
|
1607
|
+
const queue = roots.map((root) => ({
|
|
1608
|
+
dir: root.dir,
|
|
1609
|
+
onlyEscapedStateTargets: root.onlyEscapedStateTargets,
|
|
1610
|
+
depth: 0
|
|
1611
|
+
}));
|
|
1612
|
+
let entryCount = 0;
|
|
1613
|
+
const recordSkillFile = async (filePath, onlyEscapedStateTargets) => {
|
|
1614
|
+
try {
|
|
1615
|
+
const skillRealPath = await fs.realpath(filePath);
|
|
1616
|
+
if (!onlyEscapedStateTargets || !isPathWithin(realStateDir, skillRealPath)) skillPaths.add(skillRealPath);
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
if (!isMissingPathError(error)) complete = false;
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
for (const current of queue) {
|
|
1622
|
+
let realDir;
|
|
1623
|
+
try {
|
|
1624
|
+
realDir = await fs.realpath(current.dir);
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
if (isMissingPathError(error)) continue;
|
|
1627
|
+
complete = false;
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
if (seenDirectories.has(realDir)) continue;
|
|
1631
|
+
seenDirectories.add(realDir);
|
|
1632
|
+
if (seenDirectories.size > MAX_PERSONAL_SKILL_DIRECTORIES) {
|
|
1633
|
+
complete = false;
|
|
1634
|
+
break;
|
|
1635
|
+
}
|
|
1636
|
+
let directory;
|
|
1637
|
+
try {
|
|
1638
|
+
directory = await fs.opendir(current.dir);
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
if (!isMissingPathError(error)) complete = false;
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
for await (const entry of directory) {
|
|
1645
|
+
entryCount += 1;
|
|
1646
|
+
if (entryCount > MAX_PERSONAL_SKILL_ENTRIES) {
|
|
1647
|
+
complete = false;
|
|
1648
|
+
queue.length = 0;
|
|
1649
|
+
break;
|
|
1650
|
+
}
|
|
1651
|
+
if (entry.name.startsWith(".")) continue;
|
|
1652
|
+
const entryPath = path.join(current.dir, entry.name);
|
|
1653
|
+
if (entry.name === "SKILL.md" && entry.isFile()) {
|
|
1654
|
+
await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
if (entry.isSymbolicLink()) {
|
|
1658
|
+
try {
|
|
1659
|
+
const stat = await fs.stat(entryPath);
|
|
1660
|
+
if (entry.name === "SKILL.md" && stat.isFile()) await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
|
|
1661
|
+
else if (stat.isDirectory()) if (current.depth < MAX_PERSONAL_SKILL_DEPTH) queue.push({
|
|
1662
|
+
dir: entryPath,
|
|
1663
|
+
depth: current.depth + 1,
|
|
1664
|
+
onlyEscapedStateTargets: current.onlyEscapedStateTargets
|
|
1665
|
+
});
|
|
1666
|
+
else complete = false;
|
|
1667
|
+
} catch (error) {
|
|
1668
|
+
if (!isMissingPathError(error)) complete = false;
|
|
1669
|
+
}
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
if (current.depth >= MAX_PERSONAL_SKILL_DEPTH) {
|
|
1673
|
+
if (entry.isDirectory()) complete = false;
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
if (entry.isDirectory()) {
|
|
1677
|
+
queue.push({
|
|
1678
|
+
dir: entryPath,
|
|
1679
|
+
depth: current.depth + 1,
|
|
1680
|
+
onlyEscapedStateTargets: current.onlyEscapedStateTargets
|
|
1681
|
+
});
|
|
1682
|
+
continue;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
if (!isMissingPathError(error)) complete = false;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return {
|
|
1690
|
+
complete,
|
|
1691
|
+
skillPaths
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
/** Resolves the native user-scope skills that an isolated OpenClaw thread must disable. */
|
|
1695
|
+
async function resolveCodexNativeSkillIsolation(params) {
|
|
1696
|
+
params.signal?.throwIfAborted();
|
|
1697
|
+
if (!process.env.OPENCLAW_STATE_DIR?.trim()) return;
|
|
1698
|
+
const key = JSON.stringify([
|
|
1699
|
+
path.resolve(resolveStateDir()),
|
|
1700
|
+
path.resolve(params.cwd),
|
|
1701
|
+
params.codexHome?.trim() || process.env.CODEX_HOME?.trim() || "",
|
|
1702
|
+
params.home?.trim() || process.env.HOME?.trim() || "",
|
|
1703
|
+
params.userProfile?.trim() || process.env.USERPROFILE?.trim() || ""
|
|
1704
|
+
]);
|
|
1705
|
+
const cached = nativeSkillIsolationByClient.get(params.client);
|
|
1706
|
+
if (cached?.key === key && (cached.settled || cached.signal === params.signal)) {
|
|
1707
|
+
const isolation = await cached.result;
|
|
1708
|
+
params.signal?.throwIfAborted();
|
|
1709
|
+
return isolation;
|
|
1710
|
+
}
|
|
1711
|
+
const result = resolveUncachedCodexNativeSkillIsolation(params);
|
|
1712
|
+
const entry = {
|
|
1713
|
+
key,
|
|
1714
|
+
result,
|
|
1715
|
+
settled: false,
|
|
1716
|
+
signal: params.signal
|
|
1717
|
+
};
|
|
1718
|
+
nativeSkillIsolationByClient.set(params.client, entry);
|
|
1719
|
+
try {
|
|
1720
|
+
const isolation = await result;
|
|
1721
|
+
entry.settled = true;
|
|
1722
|
+
params.signal?.throwIfAborted();
|
|
1723
|
+
return isolation;
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
if (nativeSkillIsolationByClient.get(params.client)?.result === result) nativeSkillIsolationByClient.delete(params.client);
|
|
1726
|
+
throw error;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
async function resolveUncachedCodexNativeSkillIsolation(params) {
|
|
1730
|
+
if (await usesDefaultStateDir()) return;
|
|
1731
|
+
const response = await params.client.request("skills/list", {
|
|
1732
|
+
cwds: [params.cwd],
|
|
1733
|
+
forceReload: true
|
|
1734
|
+
}, { signal: params.signal });
|
|
1735
|
+
const homes = [params.home?.trim() || process.env.HOME?.trim() || process.env.USERPROFILE?.trim() || os.homedir()];
|
|
1736
|
+
if (process.platform === "win32") homes.push(params.userProfile?.trim() || os.homedir());
|
|
1737
|
+
const personalSkills = await collectPersonalSkillRealPaths([...new Set(homes.map((home) => path.resolve(home)))], params.codexHome);
|
|
1738
|
+
return { disabledUserSkillPaths: [...personalSkills.complete ? personalSkills.skillPaths : /* @__PURE__ */ new Set([...personalSkills.skillPaths, ...response.data.flatMap((entry) => entry.skills.filter((skill) => skill.scope === "user").map((skill) => skill.path))])].toSorted((left, right) => left.localeCompare(right)) };
|
|
1739
|
+
}
|
|
1740
|
+
/** Applies path-exact session rules after caller config so isolated user skills stay disabled. */
|
|
1741
|
+
function applyCodexNativeSkillIsolation(config, isolation) {
|
|
1742
|
+
if (!isolation) return config;
|
|
1743
|
+
const existingRules = config?.["skills.config"];
|
|
1744
|
+
if (existingRules !== void 0 && !Array.isArray(existingRules)) throw new Error("Codex thread skills.config must be an array");
|
|
1745
|
+
const disabledRules = isolation.disabledUserSkillPaths.map((skillPath) => ({
|
|
1746
|
+
path: skillPath,
|
|
1747
|
+
enabled: false
|
|
1748
|
+
}));
|
|
1749
|
+
return {
|
|
1750
|
+
...config,
|
|
1751
|
+
"skills.include_instructions": false,
|
|
1752
|
+
"skills.config": [...existingRules ?? [], ...disabledRules]
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
//#endregion
|
|
1427
1756
|
//#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
|
|
1428
1757
|
var CodexThreadStartRequestError = class extends Error {
|
|
1429
1758
|
constructor(cause) {
|
|
@@ -1437,10 +1766,10 @@ var CodexThreadBindingConflictError = class extends Error {
|
|
|
1437
1766
|
this.name = "CodexThreadBindingConflictError";
|
|
1438
1767
|
}
|
|
1439
1768
|
};
|
|
1440
|
-
var
|
|
1769
|
+
var CodexRestrictedToolSurfaceAttestationError = class extends Error {
|
|
1441
1770
|
constructor(cause) {
|
|
1442
|
-
super("Codex
|
|
1443
|
-
this.name = "
|
|
1771
|
+
super("Codex restricted-tool-surface MCP attestation failed", { cause });
|
|
1772
|
+
this.name = "CodexRestrictedToolSurfaceAttestationError";
|
|
1444
1773
|
}
|
|
1445
1774
|
};
|
|
1446
1775
|
var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
|
|
@@ -1449,7 +1778,62 @@ var CodexAdoptedThreadActiveError = class extends Error {
|
|
|
1449
1778
|
super("Codex session became active in another runner; wait for it to finish before continuing");
|
|
1450
1779
|
this.name = "CodexAdoptedThreadActiveError";
|
|
1451
1780
|
}
|
|
1452
|
-
};
|
|
1781
|
+
};
|
|
1782
|
+
//#endregion
|
|
1783
|
+
//#region extensions/codex/src/app-server/plugin-thread-attestation.ts
|
|
1784
|
+
/**
|
|
1785
|
+
* Confirms admitted plugin and account apps against their actual Codex thread before
|
|
1786
|
+
* OpenClaw commits a binding or starts a turn.
|
|
1787
|
+
*/
|
|
1788
|
+
var CodexPluginThreadAppAttestationError = class extends Error {
|
|
1789
|
+
constructor(message, options) {
|
|
1790
|
+
super(message, options);
|
|
1791
|
+
this.name = "CodexPluginThreadAppAttestationError";
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
/** Reads the existing runtime snapshot with the started thread's effective app policy. */
|
|
1795
|
+
async function attestCodexPluginThreadApps(params) {
|
|
1796
|
+
const appIds = Array.from(new Set(params.appIds.filter(Boolean))).toSorted();
|
|
1797
|
+
if (appIds.length === 0) return;
|
|
1798
|
+
let response;
|
|
1799
|
+
try {
|
|
1800
|
+
response = await params.client.request("app/installed", {
|
|
1801
|
+
threadId: params.threadId,
|
|
1802
|
+
forceRefresh: false
|
|
1803
|
+
}, { signal: params.signal });
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
throw new CodexPluginThreadAppAttestationError(`Codex could not confirm admitted apps for thread ${params.threadId}`, { cause: error });
|
|
1806
|
+
}
|
|
1807
|
+
const installedById = new Map(response.apps.map((app) => [app.id, app]));
|
|
1808
|
+
const failures = appIds.flatMap((appId) => {
|
|
1809
|
+
const app = installedById.get(appId);
|
|
1810
|
+
if (!app) return [`${appId}:missing`];
|
|
1811
|
+
if (!app.enabled) return [`${appId}:disabled`];
|
|
1812
|
+
return app.callable ? [] : [`${appId}:not-callable`];
|
|
1813
|
+
});
|
|
1814
|
+
if (failures.length > 0) throw new CodexPluginThreadAppAttestationError(`Codex thread ${params.threadId} did not expose admitted apps: ${failures.join(", ")}`);
|
|
1815
|
+
}
|
|
1816
|
+
/** Deletes a persistent pre-turn thread; ephemeral threads can only be unsubscribed. */
|
|
1817
|
+
async function discardUnattestedCodexPluginThread(params) {
|
|
1818
|
+
if (params.ephemeral) return await unsubscribeCodexThreadBestEffort(params.client, {
|
|
1819
|
+
threadId: params.threadId,
|
|
1820
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
1821
|
+
});
|
|
1822
|
+
try {
|
|
1823
|
+
await params.client.request("thread/delete", { threadId: params.threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
|
|
1824
|
+
return true;
|
|
1825
|
+
} catch (error) {
|
|
1826
|
+
embeddedAgentLog.debug("codex plugin app attestation thread deletion failed", {
|
|
1827
|
+
threadId: params.threadId,
|
|
1828
|
+
error
|
|
1829
|
+
});
|
|
1830
|
+
await unsubscribeCodexThreadBestEffort(params.client, {
|
|
1831
|
+
threadId: params.threadId,
|
|
1832
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
1833
|
+
});
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1453
1837
|
//#endregion
|
|
1454
1838
|
//#region extensions/codex/src/app-server/reasoning-effort.ts
|
|
1455
1839
|
const CODEX_REASONING_EFFORTS = [
|
|
@@ -1461,30 +1845,13 @@ const CODEX_REASONING_EFFORTS = [
|
|
|
1461
1845
|
"max",
|
|
1462
1846
|
"ultra"
|
|
1463
1847
|
];
|
|
1464
|
-
const
|
|
1465
|
-
"low",
|
|
1466
|
-
"medium",
|
|
1467
|
-
"high",
|
|
1468
|
-
"xhigh",
|
|
1469
|
-
"max"
|
|
1470
|
-
];
|
|
1471
|
-
const GPT_56_ULTRA_REASONING_EFFORTS = [...GPT_56_MAX_REASONING_EFFORTS, "ultra"];
|
|
1472
|
-
const GPT_5_PRO_REASONING_EFFORTS = [
|
|
1848
|
+
const LEGACY_PRO_REASONING_EFFORTS = [
|
|
1473
1849
|
"medium",
|
|
1474
1850
|
"high",
|
|
1475
1851
|
"xhigh"
|
|
1476
1852
|
];
|
|
1477
|
-
const
|
|
1478
|
-
const
|
|
1479
|
-
const MODERN_CODEX_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
1480
|
-
...GPT_56_MAX_MODEL_IDS,
|
|
1481
|
-
"gpt-5.5",
|
|
1482
|
-
"gpt-5.5-pro",
|
|
1483
|
-
"gpt-5.4",
|
|
1484
|
-
"gpt-5.4-pro",
|
|
1485
|
-
"gpt-5.4-mini",
|
|
1486
|
-
"gpt-5.3-codex-spark"
|
|
1487
|
-
]);
|
|
1853
|
+
const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
|
|
1854
|
+
const MODERN_GPT_5_MODEL_ID_RE = /^gpt-5\.(?:[3-9]|[1-9]\d)(?:$|-)/u;
|
|
1488
1855
|
function normalizeCodexReasoningEfforts(efforts) {
|
|
1489
1856
|
if (!efforts) return [];
|
|
1490
1857
|
const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
|
|
@@ -1504,24 +1871,21 @@ function resolveSupportedReasoningEffort(params) {
|
|
|
1504
1871
|
const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
|
|
1505
1872
|
return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
|
|
1506
1873
|
}
|
|
1507
|
-
|
|
1508
|
-
const normalized = modelId.trim().toLowerCase();
|
|
1509
|
-
if (GPT_56_ULTRA_MODEL_IDS.has(normalized)) return GPT_56_ULTRA_REASONING_EFFORTS;
|
|
1510
|
-
if (normalized === "gpt-5.6-luna") return GPT_56_MAX_REASONING_EFFORTS;
|
|
1511
|
-
if (normalized === "gpt-5.5-pro" || normalized === "gpt-5.4-pro") return GPT_5_PRO_REASONING_EFFORTS;
|
|
1512
|
-
}
|
|
1513
|
-
/** Resolve a turn effort from app-server metadata, with exact-name offline fallbacks. */
|
|
1874
|
+
/** Resolve a turn effort from the selected model's provider-owned metadata. */
|
|
1514
1875
|
function resolveCodexAppServerReasoningEffort(params) {
|
|
1515
1876
|
if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
|
|
1516
|
-
|
|
1517
|
-
if (supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
1877
|
+
if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
1518
1878
|
requested: params.thinkLevel,
|
|
1519
|
-
supportedReasoningEfforts
|
|
1879
|
+
supportedReasoningEfforts: params.supportedReasoningEfforts
|
|
1880
|
+
}) ?? null;
|
|
1881
|
+
const modelId = params.modelId.trim().toLowerCase();
|
|
1882
|
+
if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
|
|
1883
|
+
requested: params.thinkLevel,
|
|
1884
|
+
supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
|
|
1520
1885
|
}) ?? null;
|
|
1521
|
-
|
|
1522
|
-
if (params.thinkLevel === "minimal"
|
|
1523
|
-
|
|
1524
|
-
return params.thinkLevel === "max" && GPT_56_MAX_MODEL_IDS.has(normalizedModelId) ? "max" : null;
|
|
1886
|
+
if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
|
|
1887
|
+
if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
|
|
1888
|
+
return null;
|
|
1525
1889
|
}
|
|
1526
1890
|
//#endregion
|
|
1527
1891
|
//#region extensions/codex/src/app-server/thread-model-selection.ts
|
|
@@ -1601,33 +1965,46 @@ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts)
|
|
|
1601
1965
|
//#endregion
|
|
1602
1966
|
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
1603
1967
|
function buildDeveloperInstructions(params, options = {}) {
|
|
1968
|
+
const deferredToolNames = /* @__PURE__ */ new Set();
|
|
1969
|
+
let hasSkillWorkshop = false;
|
|
1970
|
+
let hasSessionsSpawn = false;
|
|
1971
|
+
let hasSessionsYield = false;
|
|
1972
|
+
let hasSeenDirectNamespace = false;
|
|
1973
|
+
let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
|
|
1974
|
+
for (const spec of options.dynamicTools ?? []) {
|
|
1975
|
+
const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
|
|
1976
|
+
if (isDirectNamespace) hasSeenDirectNamespace = true;
|
|
1977
|
+
for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
|
|
1978
|
+
const name = tool.name.trim();
|
|
1979
|
+
if (tool.deferLoading === true && name) deferredToolNames.add(name);
|
|
1980
|
+
hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
|
|
1981
|
+
hasSessionsSpawn ||= name === "sessions_spawn";
|
|
1982
|
+
hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
|
|
1983
|
+
messageToolAvailable ||= name === "message";
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1604
1986
|
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
1605
1987
|
surface: "codex_app_server",
|
|
1606
1988
|
includeLegacyGlobalGuidance: false
|
|
1607
1989
|
}).join("\n");
|
|
1990
|
+
const nativeDelegationAvailable = params.disableTools !== true && params.delegationCapability !== "report_only" && !isMessageOnlyCodexSourceReply(params) && !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) && !shouldDisableCodexToolSearchForModel(params.modelId);
|
|
1991
|
+
const deferredToolDiscoveryGuidance = deferredToolNames.size > 0 || nativeDelegationAvailable ? "Deferred tools may be absent from the direct tool list. Use `tool_search` when directly callable. On code-mode-only models, use `exec` instead: filter `ALL_TOOLS` by name and description, then call the matching entry through `tools`." : void 0;
|
|
1608
1992
|
return [
|
|
1609
1993
|
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1994
|
+
deferredToolNames.size > 0 ? `Deferred searchable OpenClaw dynamic tools available: ${[...deferredToolNames].toSorted((left, right) => left.localeCompare(right)).join(", ")}.` : void 0,
|
|
1995
|
+
deferredToolDiscoveryGuidance,
|
|
1996
|
+
hasSkillWorkshop ? buildSkillWorkshopPromptSection().join("\n") : void 0,
|
|
1997
|
+
nativeDelegationAvailable ? `Use Codex native \`spawn_agent\` for Codex subagents. \`spawn_agent\` and the other native collaboration tools may be deferred.${hasSessionsSpawn ? " Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`." : ""}` : void 0,
|
|
1998
|
+
hasSessionsYield && nativeDelegationAvailable ? "When a native child's result belongs in a later turn, end the current turn with `openclaw_direct.sessions_yield`; the completion arrives as the next model-visible input. Use native `wait_agent` only for an intentional same-turn wait when the immediate next step is blocked on the child. Never loop-poll for native child completion." : void 0,
|
|
1999
|
+
buildVisibleReplyInstruction(params, messageToolAvailable),
|
|
2000
|
+
TRANSCRIPT_CREDENTIAL_SAFETY_PROMPT,
|
|
1614
2001
|
nativeCommandGuidance,
|
|
1615
2002
|
params.extraSystemPrompt
|
|
1616
2003
|
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
1617
2004
|
}
|
|
1618
|
-
function
|
|
1619
|
-
|
|
1620
|
-
if (
|
|
1621
|
-
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
1622
|
-
}
|
|
1623
|
-
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
1624
|
-
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
1625
|
-
return buildSkillWorkshopPromptSection().join("\n");
|
|
1626
|
-
}
|
|
1627
|
-
function buildVisibleReplyInstruction(params, dynamicTools) {
|
|
1628
|
-
const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
|
|
1629
|
-
if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. If `final` is omitted, OpenClaw continues and resolves the latest omitted source reply only when the turn ends successfully. Do not repeat visible message content in your final answer.";
|
|
1630
|
-
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.";
|
|
2005
|
+
function buildVisibleReplyInstruction(params, messageToolAvailable) {
|
|
2006
|
+
if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. Set `final=true`, or omit it, for the completed reply to the current source conversation; OpenClaw stops after confirming delivery. Do not repeat visible message content in your final answer.";
|
|
2007
|
+
if (messageToolAvailable) return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation. Use `message` for supported non-text actions in the current conversation, such as reacting to its current message. Reserve other `message` actions for explicit out-of-band sends or media/file delivery. Reactions are not delivered automatically.";
|
|
1631
2008
|
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
|
|
1632
2009
|
}
|
|
1633
2010
|
//#endregion
|
|
@@ -1636,16 +2013,13 @@ const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG = {
|
|
|
1636
2013
|
"features.standalone_web_search": false,
|
|
1637
2014
|
web_search: "disabled"
|
|
1638
2015
|
};
|
|
1639
|
-
function normalizeOptionalString$1(value) {
|
|
1640
|
-
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
1641
|
-
}
|
|
1642
2016
|
function normalizeUniqueStrings(value) {
|
|
1643
2017
|
if (!Array.isArray(value)) return;
|
|
1644
|
-
const normalized = [...new Set(value.map(normalizeOptionalString
|
|
2018
|
+
const normalized = [...new Set(value.map(normalizeOptionalString).filter((entry) => Boolean(entry)))];
|
|
1645
2019
|
return normalized.length > 0 ? normalized : void 0;
|
|
1646
2020
|
}
|
|
1647
2021
|
function hasManagedSearchProvider(config) {
|
|
1648
|
-
return normalizeOptionalString
|
|
2022
|
+
return normalizeOptionalString(config?.tools?.web?.search?.provider) !== void 0;
|
|
1649
2023
|
}
|
|
1650
2024
|
function hasNativeDomainRestrictions(config) {
|
|
1651
2025
|
return normalizeUniqueStrings(config?.tools?.web?.search?.openaiCodex?.allowedDomains) !== void 0;
|
|
@@ -1660,10 +2034,10 @@ function buildCodexNativeWebSearchThreadConfig(config) {
|
|
|
1660
2034
|
if (allowedDomains) threadConfig["tools.web_search.allowed_domains"] = allowedDomains;
|
|
1661
2035
|
if (nativeConfig?.contextSize) threadConfig["tools.web_search.context_size"] = nativeConfig.contextSize;
|
|
1662
2036
|
const location = nativeConfig?.userLocation;
|
|
1663
|
-
const country = normalizeOptionalString
|
|
1664
|
-
const region = normalizeOptionalString
|
|
1665
|
-
const city = normalizeOptionalString
|
|
1666
|
-
const timezone = normalizeOptionalString
|
|
2037
|
+
const country = normalizeOptionalString(location?.country);
|
|
2038
|
+
const region = normalizeOptionalString(location?.region);
|
|
2039
|
+
const city = normalizeOptionalString(location?.city);
|
|
2040
|
+
const timezone = normalizeOptionalString(location?.timezone);
|
|
1667
2041
|
if (country) threadConfig["tools.web_search.location.country"] = country;
|
|
1668
2042
|
if (region) threadConfig["tools.web_search.location.region"] = region;
|
|
1669
2043
|
if (city) threadConfig["tools.web_search.location.city"] = city;
|
|
@@ -1710,26 +2084,50 @@ const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
|
|
|
1710
2084
|
const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
|
|
1711
2085
|
const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
|
|
1712
2086
|
const CODEX_DELEGATION_DISABLED_THREAD_CONFIG = {
|
|
2087
|
+
"agents.enabled": false,
|
|
1713
2088
|
"features.multi_agent": false,
|
|
1714
2089
|
"features.multi_agent_v2": false
|
|
1715
2090
|
};
|
|
2091
|
+
const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
|
|
2092
|
+
"apps",
|
|
2093
|
+
"artifact",
|
|
2094
|
+
"browser_use",
|
|
2095
|
+
"browser_use_external",
|
|
2096
|
+
"browser_use_full_cdp_access",
|
|
2097
|
+
"chronicle",
|
|
2098
|
+
"code_mode",
|
|
2099
|
+
"code_mode_only",
|
|
2100
|
+
"computer_use",
|
|
2101
|
+
"current_time_reminder",
|
|
2102
|
+
"default_mode_request_user_input",
|
|
2103
|
+
"deferred_executor",
|
|
2104
|
+
"goals",
|
|
2105
|
+
"hooks",
|
|
2106
|
+
"image_generation",
|
|
2107
|
+
"memories",
|
|
2108
|
+
"multi_agent",
|
|
2109
|
+
"multi_agent_v2",
|
|
2110
|
+
"plugins",
|
|
2111
|
+
"request_permissions_tool",
|
|
2112
|
+
"skill_search",
|
|
2113
|
+
"shell_tool",
|
|
2114
|
+
"standalone_web_search",
|
|
2115
|
+
"token_budget",
|
|
2116
|
+
"unified_exec",
|
|
2117
|
+
"view_image",
|
|
2118
|
+
"web_search_cached",
|
|
2119
|
+
"web_search_request",
|
|
2120
|
+
"workspace_dependencies"
|
|
2121
|
+
]);
|
|
1716
2122
|
const CODEX_RING_ZERO_THREAD_CONFIG = {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
"features.deferred_executor": false,
|
|
1720
|
-
"features.enable_fanout": false,
|
|
1721
|
-
"features.goals": false,
|
|
1722
|
-
"features.hooks": false,
|
|
1723
|
-
"features.image_generation": false,
|
|
1724
|
-
"features.memories": false,
|
|
1725
|
-
"features.multi_agent": false,
|
|
1726
|
-
"features.multi_agent_v2": false,
|
|
1727
|
-
"features.plugins": false,
|
|
1728
|
-
"features.standalone_web_search": false,
|
|
1729
|
-
"features.token_budget": false,
|
|
2123
|
+
...CODEX_DELEGATION_DISABLED_THREAD_CONFIG,
|
|
2124
|
+
...Object.fromEntries([...CODEX_RING_ZERO_RESTRICTED_FEATURES].map((feature) => [`features.${feature}`, false])),
|
|
1730
2125
|
"orchestrator.mcp.enabled": false,
|
|
1731
2126
|
"orchestrator.skills.enabled": false,
|
|
2127
|
+
"skills.bundled.enabled": false,
|
|
2128
|
+
"skills.include_instructions": false,
|
|
1732
2129
|
"tools.experimental_request_user_input.enabled": false,
|
|
2130
|
+
"tools.update_plan.enabled": false,
|
|
1733
2131
|
hooks: {
|
|
1734
2132
|
PreToolUse: [],
|
|
1735
2133
|
PermissionRequest: [],
|
|
@@ -1746,22 +2144,13 @@ const CODEX_RING_ZERO_THREAD_CONFIG = {
|
|
|
1746
2144
|
notify: [],
|
|
1747
2145
|
web_search: "disabled"
|
|
1748
2146
|
};
|
|
1749
|
-
const
|
|
1750
|
-
"apps",
|
|
1751
|
-
"
|
|
1752
|
-
"
|
|
1753
|
-
"
|
|
1754
|
-
"
|
|
1755
|
-
"
|
|
1756
|
-
"goals",
|
|
1757
|
-
"hooks",
|
|
1758
|
-
"image_generation",
|
|
1759
|
-
"memories",
|
|
1760
|
-
"multi_agent",
|
|
1761
|
-
"multi_agent_v2",
|
|
1762
|
-
"plugins",
|
|
1763
|
-
"standalone_web_search",
|
|
1764
|
-
"token_budget"
|
|
2147
|
+
const CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES = /* @__PURE__ */ new Map([
|
|
2148
|
+
["connectors", "apps"],
|
|
2149
|
+
["imagegenext", "image_generation"],
|
|
2150
|
+
["collab", "multi_agent"],
|
|
2151
|
+
["memory_tool", "memories"],
|
|
2152
|
+
["telepathy", "chronicle"],
|
|
2153
|
+
["codex_hooks", "hooks"]
|
|
1765
2154
|
]);
|
|
1766
2155
|
const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
1767
2156
|
"mdm",
|
|
@@ -1807,7 +2196,7 @@ function buildThreadStartParams(params, options) {
|
|
|
1807
2196
|
webSearchAllowed: options.webSearchAllowed,
|
|
1808
2197
|
appServer: options.appServer,
|
|
1809
2198
|
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1810
|
-
|
|
2199
|
+
restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames
|
|
1811
2200
|
}),
|
|
1812
2201
|
...resolveCodexThreadEnvironmentSelection(options),
|
|
1813
2202
|
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
@@ -1856,7 +2245,7 @@ function buildThreadResumeParams(params, options) {
|
|
|
1856
2245
|
webSearchAllowed: options.webSearchAllowed,
|
|
1857
2246
|
appServer: options.appServer,
|
|
1858
2247
|
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1859
|
-
|
|
2248
|
+
restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames
|
|
1860
2249
|
}),
|
|
1861
2250
|
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
1862
2251
|
};
|
|
@@ -1895,9 +2284,15 @@ function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
|
1895
2284
|
}
|
|
1896
2285
|
function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
1897
2286
|
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
2287
|
+
const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params);
|
|
2288
|
+
const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true;
|
|
1898
2289
|
const configMcpServers = config?.mcp_servers;
|
|
1899
|
-
if (
|
|
1900
|
-
const
|
|
2290
|
+
if (restrictedToolSurface && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex restricted tool surface received invalid thread mcp_servers config");
|
|
2291
|
+
const restrictedToolSurfaceMcpServerNames = [...options.restrictedToolSurfaceInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
2292
|
+
const restrictedRunConfig = restrictedToolSurface && isJsonObject(configMcpServers) ? {
|
|
2293
|
+
...config,
|
|
2294
|
+
mcp_servers: {}
|
|
2295
|
+
} : config;
|
|
1901
2296
|
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
1902
2297
|
config: params.config,
|
|
1903
2298
|
disableTools: params.disableTools,
|
|
@@ -1905,8 +2300,8 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
1905
2300
|
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1906
2301
|
webSearchAllowed: options.webSearchAllowed
|
|
1907
2302
|
}).threadConfig;
|
|
1908
|
-
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(
|
|
1909
|
-
const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive,
|
|
2303
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(restrictedRunConfig, webSearchConfig), options);
|
|
2304
|
+
const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildCodexRestrictedToolThreadConfigPatch(restrictedToolSurfaceMcpServerNames) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames)) ?? baseConfig;
|
|
1910
2305
|
if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
|
|
1911
2306
|
return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
|
|
1912
2307
|
...runtimeConfig,
|
|
@@ -1915,6 +2310,9 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
1915
2310
|
}
|
|
1916
2311
|
function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
|
|
1917
2312
|
if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
|
|
2313
|
+
return buildCodexRestrictedToolThreadConfigPatch(inheritedMcpServerNames);
|
|
2314
|
+
}
|
|
2315
|
+
function buildCodexRestrictedToolThreadConfigPatch(inheritedMcpServerNames) {
|
|
1918
2316
|
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
1919
2317
|
return {
|
|
1920
2318
|
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
@@ -1930,15 +2328,15 @@ async function readCodexInheritedMcpServerNames(client, cwd, signal) {
|
|
|
1930
2328
|
if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
|
|
1931
2329
|
for (const layer of response.layers) {
|
|
1932
2330
|
if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
|
|
1933
|
-
if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex
|
|
1934
|
-
if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex
|
|
2331
|
+
if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex restricted tool surface cannot override config layer ${layer.name.type}`);
|
|
2332
|
+
if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex restricted tool surface does not recognize config layer ${layer.name.type}`);
|
|
1935
2333
|
}
|
|
1936
2334
|
const configuredServers = response.config.mcp_servers;
|
|
1937
2335
|
if (configuredServers === void 0) return [];
|
|
1938
2336
|
if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
|
|
1939
2337
|
return Object.keys(configuredServers).toSorted();
|
|
1940
2338
|
}
|
|
1941
|
-
async function
|
|
2339
|
+
async function assertCodexRestrictedToolSurfaceHasNoManagedHooks(client, signal) {
|
|
1942
2340
|
const response = await client.request("configRequirements/read", void 0, { signal });
|
|
1943
2341
|
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
|
|
1944
2342
|
if (response.requirements === null) return;
|
|
@@ -1951,7 +2349,7 @@ async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
|
|
|
1951
2349
|
const hooks = response.requirements[key];
|
|
1952
2350
|
if (hooks === void 0 || hooks === null) continue;
|
|
1953
2351
|
if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
|
|
1954
|
-
if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex
|
|
2352
|
+
if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex restricted tool surface cannot override managed hooks");
|
|
1955
2353
|
}
|
|
1956
2354
|
for (const key of ["featureRequirements", "feature_requirements"]) {
|
|
1957
2355
|
const requirements = response.requirements[key];
|
|
@@ -1959,22 +2357,35 @@ async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
|
|
|
1959
2357
|
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
1960
2358
|
for (const [feature, enabled] of Object.entries(requirements)) {
|
|
1961
2359
|
if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
1962
|
-
|
|
2360
|
+
const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
|
|
2361
|
+
if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature)) throw new Error(`Codex restricted tool surface cannot override required feature ${feature}`);
|
|
1963
2362
|
}
|
|
1964
2363
|
}
|
|
1965
2364
|
}
|
|
1966
|
-
async function
|
|
2365
|
+
async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal) {
|
|
2366
|
+
const configuredServers = threadConfig?.mcp_servers;
|
|
2367
|
+
if (configuredServers !== void 0 && !isJsonObject(configuredServers)) throw new Error("Codex restricted-tool-surface thread config has invalid mcp_servers");
|
|
2368
|
+
const expectedDisabledServerNames = /* @__PURE__ */ new Set();
|
|
2369
|
+
for (const [name, serverConfig] of Object.entries(configuredServers ?? {})) {
|
|
2370
|
+
if (!isJsonObject(serverConfig) || serverConfig.enabled !== false) throw new Error(`Codex restricted-tool-surface MCP server ${name} is not disabled`);
|
|
2371
|
+
expectedDisabledServerNames.add(name);
|
|
2372
|
+
}
|
|
1967
2373
|
const response = await client.request("mcpServerStatus/list", {
|
|
1968
2374
|
threadId,
|
|
1969
|
-
limit: 1,
|
|
1970
2375
|
detail: "toolsAndAuthOnly"
|
|
1971
2376
|
}, { signal });
|
|
1972
|
-
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
throw new Error(`Codex
|
|
2377
|
+
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
|
|
2378
|
+
const observedDisabledServerNames = /* @__PURE__ */ new Set();
|
|
2379
|
+
for (const status of response.data) {
|
|
2380
|
+
if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
|
|
2381
|
+
if (!expectedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
|
|
2382
|
+
if (observedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
|
|
2383
|
+
observedDisabledServerNames.add(status.name);
|
|
2384
|
+
if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
|
|
2385
|
+
if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
|
|
2386
|
+
if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
|
|
1977
2387
|
}
|
|
2388
|
+
for (const expectedName of expectedDisabledServerNames) if (!observedDisabledServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing server ${expectedName}`);
|
|
1978
2389
|
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
1979
2390
|
}
|
|
1980
2391
|
function hasNonEmptyJsonValue(value) {
|
|
@@ -1996,47 +2407,26 @@ function resolveCodexThreadEnvironmentSelection(options) {
|
|
|
1996
2407
|
return {};
|
|
1997
2408
|
}
|
|
1998
2409
|
//#endregion
|
|
1999
|
-
//#region extensions/codex/src/app-server/thread-resume.ts
|
|
2000
|
-
/** Owns Codex thread/resume subscription safety. */
|
|
2001
|
-
/** Resumes one thread and retires the physical client when acceptance is indeterminate. */
|
|
2002
|
-
async function resumeCodexAppServerThread(params) {
|
|
2003
|
-
const threadId = params.request.threadId;
|
|
2004
|
-
let response;
|
|
2005
|
-
try {
|
|
2006
|
-
response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
|
|
2007
|
-
...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
|
|
2008
|
-
...params.signal ? { signal: params.signal } : {}
|
|
2009
|
-
}));
|
|
2010
|
-
assertCodexThreadResumeSubscription(threadId, response.thread.id);
|
|
2011
|
-
} catch (error) {
|
|
2012
|
-
if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
|
|
2013
|
-
if (error instanceof CodexAppServerRpcError) throw error;
|
|
2014
|
-
try {
|
|
2015
|
-
await params.abandonClient();
|
|
2016
|
-
} catch (abandonError) {
|
|
2017
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
|
|
2018
|
-
}
|
|
2019
|
-
if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
|
|
2020
|
-
throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
|
|
2021
|
-
}
|
|
2022
|
-
return response;
|
|
2023
|
-
}
|
|
2024
|
-
//#endregion
|
|
2025
2410
|
//#region extensions/codex/src/app-server/thread-lifecycle-io.ts
|
|
2411
|
+
function resolveCodexThreadRolloutPath(thread) {
|
|
2412
|
+
const rolloutPath = thread.path?.trim();
|
|
2413
|
+
if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
|
|
2414
|
+
return rolloutPath;
|
|
2415
|
+
}
|
|
2026
2416
|
async function resumeExistingCodexThread(params, context) {
|
|
2027
|
-
const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive,
|
|
2417
|
+
const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
|
|
2028
2418
|
let resumeReservation;
|
|
2029
2419
|
try {
|
|
2030
2420
|
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
2031
|
-
const finalConfigPatch = params.buildFinalConfigPatch?.({
|
|
2421
|
+
const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
|
|
2032
2422
|
action: "resume",
|
|
2033
2423
|
binding: resumeBinding
|
|
2034
2424
|
}) ?? {
|
|
2035
2425
|
configPatch: params.finalConfigPatch,
|
|
2036
2426
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2037
2427
|
};
|
|
2038
|
-
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
|
|
2039
|
-
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
|
|
2428
|
+
const pluginAppsConfigPatch = context.prebuiltPluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
|
|
2429
|
+
const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
|
|
2040
2430
|
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2041
2431
|
threadId: resumeBinding.threadId,
|
|
2042
2432
|
authProfileId,
|
|
@@ -2052,7 +2442,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2052
2442
|
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2053
2443
|
webSearchAllowed: params.webSearchAllowed,
|
|
2054
2444
|
hostSystemAgentActive,
|
|
2055
|
-
|
|
2445
|
+
restrictedToolSurfaceInheritedMcpServerNames
|
|
2056
2446
|
}));
|
|
2057
2447
|
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2058
2448
|
throwIfAborted();
|
|
@@ -2071,17 +2461,19 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2071
2461
|
request: resumeParams,
|
|
2072
2462
|
signal: params.signal
|
|
2073
2463
|
}));
|
|
2074
|
-
if (ringZeroActive) try {
|
|
2075
|
-
await lifecycleTiming.measure("
|
|
2464
|
+
if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
|
|
2465
|
+
await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
|
|
2076
2466
|
} catch (error) {
|
|
2077
2467
|
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2078
|
-
throw new
|
|
2468
|
+
throw new CodexRestrictedToolSurfaceAttestationError(error);
|
|
2079
2469
|
}
|
|
2080
2470
|
throwIfAborted();
|
|
2081
2471
|
const boundAuthProfileId = authProfileId;
|
|
2082
2472
|
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2083
2473
|
const resumePatch = {
|
|
2474
|
+
clientId: resolveCodexAppServerClientInstanceId(params.client),
|
|
2084
2475
|
cwd: params.cwd,
|
|
2476
|
+
rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
|
|
2085
2477
|
authProfileId: boundAuthProfileId,
|
|
2086
2478
|
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
2087
2479
|
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
@@ -2089,17 +2481,20 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2089
2481
|
dynamicToolsFingerprint,
|
|
2090
2482
|
dynamicToolsContainDeferred,
|
|
2091
2483
|
webSearchThreadConfigFingerprint,
|
|
2484
|
+
nativeSkillIsolationFingerprint,
|
|
2092
2485
|
userMcpServersFingerprint,
|
|
2093
2486
|
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2487
|
+
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2094
2488
|
ringZeroConfigFingerprint,
|
|
2095
2489
|
ringZeroClientInstanceId,
|
|
2490
|
+
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
2096
2491
|
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2097
2492
|
networkProxyConfigFingerprint,
|
|
2098
2493
|
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
2099
2494
|
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2100
|
-
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
|
2101
|
-
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
|
2102
|
-
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
|
2495
|
+
pluginAppsFingerprint: context.prebuiltPluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
|
|
2496
|
+
pluginAppsInputFingerprint: context.prebuiltPluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
|
|
2497
|
+
pluginAppPolicyContext: context.prebuiltPluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
|
|
2103
2498
|
contextEngine: contextEngineBinding,
|
|
2104
2499
|
environmentSelectionFingerprint
|
|
2105
2500
|
};
|
|
@@ -2130,6 +2525,13 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2130
2525
|
...resumeBinding,
|
|
2131
2526
|
threadId: response.thread.id,
|
|
2132
2527
|
...resumePatch,
|
|
2528
|
+
liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
|
|
2529
|
+
...resumeParams,
|
|
2530
|
+
model: resumeBinding.preserveNativeModel === true ? null : response.model ?? resumeParams.model ?? null,
|
|
2531
|
+
requestedModel: resumeBinding.preserveNativeModel === true ? null : resumeParams.model ?? null,
|
|
2532
|
+
modelProvider: resumeBinding.preserveNativeModel === true ? null : resumePatch.modelProvider ?? null,
|
|
2533
|
+
requestedModelProvider: resumeBinding.preserveNativeModel === true ? null : resumeParams.modelProvider ?? resumePatch.modelProvider ?? null
|
|
2534
|
+
}, authProfileId, dynamicToolsFingerprint),
|
|
2133
2535
|
lifecycle: {
|
|
2134
2536
|
action: "resumed",
|
|
2135
2537
|
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
@@ -2138,8 +2540,8 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2138
2540
|
} catch (error) {
|
|
2139
2541
|
resumeReservation?.release();
|
|
2140
2542
|
if (isCodexAppServerStartSelectionChangedError(error)) throw error;
|
|
2141
|
-
if (error instanceof
|
|
2142
|
-
await clearCurrentBinding("retiring a failed
|
|
2543
|
+
if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
|
|
2544
|
+
await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
|
|
2143
2545
|
throw error;
|
|
2144
2546
|
}
|
|
2145
2547
|
if (error instanceof CodexAdoptedThreadActiveError) throw error;
|
|
@@ -2156,13 +2558,13 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2156
2558
|
}
|
|
2157
2559
|
async function startFreshCodexThread(params, context) {
|
|
2158
2560
|
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
2159
|
-
const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive,
|
|
2561
|
+
const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding, replacementPredecessor } = context;
|
|
2160
2562
|
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2161
2563
|
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2162
2564
|
configPatch: params.finalConfigPatch,
|
|
2163
2565
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2164
2566
|
};
|
|
2165
|
-
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
2567
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
|
|
2166
2568
|
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
2167
2569
|
cwd: params.cwd,
|
|
2168
2570
|
dynamicTools: params.dynamicTools,
|
|
@@ -2177,7 +2579,7 @@ async function startFreshCodexThread(params, context) {
|
|
|
2177
2579
|
model: startModelSelection.model,
|
|
2178
2580
|
modelProvider: startModelProvider,
|
|
2179
2581
|
hostSystemAgentActive,
|
|
2180
|
-
|
|
2582
|
+
restrictedToolSurfaceInheritedMcpServerNames
|
|
2181
2583
|
}));
|
|
2182
2584
|
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2183
2585
|
const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
|
|
@@ -2188,13 +2590,47 @@ async function startFreshCodexThread(params, context) {
|
|
|
2188
2590
|
throw error;
|
|
2189
2591
|
}
|
|
2190
2592
|
}));
|
|
2191
|
-
|
|
2192
|
-
|
|
2593
|
+
const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
|
|
2594
|
+
if (provisionalAppIds?.length) try {
|
|
2595
|
+
await lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
|
|
2596
|
+
client: params.client,
|
|
2597
|
+
threadId: response.thread.id,
|
|
2598
|
+
appIds: provisionalAppIds,
|
|
2599
|
+
signal: params.signal
|
|
2600
|
+
}));
|
|
2601
|
+
} catch (error) {
|
|
2602
|
+
if (!await discardUnattestedCodexPluginThread({
|
|
2603
|
+
client: params.client,
|
|
2604
|
+
threadId: response.thread.id,
|
|
2605
|
+
ephemeral: startParams.ephemeral === true
|
|
2606
|
+
})) {
|
|
2607
|
+
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2608
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex plugin app attestation cleanup failed", { cause: error });
|
|
2609
|
+
}
|
|
2610
|
+
throw error;
|
|
2611
|
+
}
|
|
2612
|
+
const rolloutPath = resolveCodexThreadRolloutPath(response.thread);
|
|
2613
|
+
if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
|
|
2614
|
+
await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, startParams.config, params.signal));
|
|
2193
2615
|
} catch (error) {
|
|
2194
2616
|
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2195
2617
|
throw error;
|
|
2196
2618
|
}
|
|
2197
|
-
|
|
2619
|
+
try {
|
|
2620
|
+
throwIfAborted();
|
|
2621
|
+
} catch (error) {
|
|
2622
|
+
if (replacementPredecessor) {
|
|
2623
|
+
if (!await discardUnattestedCodexPluginThread({
|
|
2624
|
+
client: params.client,
|
|
2625
|
+
threadId: response.thread.id,
|
|
2626
|
+
ephemeral: startParams.ephemeral === true
|
|
2627
|
+
})) {
|
|
2628
|
+
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2629
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after an aborted binding replacement", { cause: error });
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
throw error;
|
|
2633
|
+
}
|
|
2198
2634
|
const modelProvider = resolveCodexAppServerModelProvider({
|
|
2199
2635
|
provider: params.params.provider,
|
|
2200
2636
|
authProfileId: params.params.authProfileId,
|
|
@@ -2202,36 +2638,66 @@ async function startFreshCodexThread(params, context) {
|
|
|
2202
2638
|
agentDir: params.params.agentDir,
|
|
2203
2639
|
config: params.params.config
|
|
2204
2640
|
});
|
|
2641
|
+
const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
|
|
2205
2642
|
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2206
2643
|
if (!preserveExistingBinding) {
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2644
|
+
const nextBinding = {
|
|
2645
|
+
threadId: response.thread.id,
|
|
2646
|
+
...clientId ? { clientId } : {},
|
|
2647
|
+
cwd: params.cwd,
|
|
2648
|
+
...rolloutPath ? { rolloutPath } : {},
|
|
2649
|
+
authProfileId: params.params.authProfileId,
|
|
2650
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2651
|
+
modelProvider: bindingModelProvider,
|
|
2652
|
+
dynamicToolsFingerprint,
|
|
2653
|
+
dynamicToolsContainDeferred,
|
|
2654
|
+
webSearchThreadConfigFingerprint,
|
|
2655
|
+
nativeSkillIsolationFingerprint,
|
|
2656
|
+
userMcpServersFingerprint,
|
|
2657
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2658
|
+
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2659
|
+
ringZeroConfigFingerprint,
|
|
2660
|
+
ringZeroClientInstanceId,
|
|
2661
|
+
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
2662
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2663
|
+
networkProxyConfigFingerprint,
|
|
2664
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2665
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2666
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2667
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2668
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2669
|
+
contextEngine: contextEngineBinding,
|
|
2670
|
+
environmentSelectionFingerprint
|
|
2671
|
+
};
|
|
2672
|
+
const cleanupUncommittedSuccessor = async (cause) => {
|
|
2673
|
+
if (!await discardUnattestedCodexPluginThread({
|
|
2674
|
+
client: params.client,
|
|
2211
2675
|
threadId: response.thread.id,
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
|
|
2217
|
-
dynamicToolsFingerprint,
|
|
2218
|
-
dynamicToolsContainDeferred,
|
|
2219
|
-
webSearchThreadConfigFingerprint,
|
|
2220
|
-
userMcpServersFingerprint,
|
|
2221
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2222
|
-
ringZeroConfigFingerprint,
|
|
2223
|
-
ringZeroClientInstanceId,
|
|
2224
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2225
|
-
networkProxyConfigFingerprint,
|
|
2226
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2227
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2228
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2229
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2230
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2231
|
-
contextEngine: contextEngineBinding,
|
|
2232
|
-
environmentSelectionFingerprint
|
|
2676
|
+
ephemeral: startParams.ephemeral === true
|
|
2677
|
+
})) {
|
|
2678
|
+
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2679
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
|
|
2233
2680
|
}
|
|
2234
|
-
}
|
|
2681
|
+
};
|
|
2682
|
+
let committed;
|
|
2683
|
+
try {
|
|
2684
|
+
committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
|
|
2685
|
+
kind: "replace-thread",
|
|
2686
|
+
expectedThreadId: replacementPredecessor.threadId,
|
|
2687
|
+
binding: nextBinding
|
|
2688
|
+
} : {
|
|
2689
|
+
kind: "set",
|
|
2690
|
+
if: { kind: "absent" },
|
|
2691
|
+
binding: nextBinding
|
|
2692
|
+
}));
|
|
2693
|
+
} catch (error) {
|
|
2694
|
+
if (replacementPredecessor) await cleanupUncommittedSuccessor(error);
|
|
2695
|
+
throw error;
|
|
2696
|
+
}
|
|
2697
|
+
if (!committed) {
|
|
2698
|
+
if (replacementPredecessor) await cleanupUncommittedSuccessor();
|
|
2699
|
+
throw new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread");
|
|
2700
|
+
}
|
|
2235
2701
|
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2236
2702
|
sessionId: params.params.sessionId,
|
|
2237
2703
|
sessionKey: params.params.sessionKey,
|
|
@@ -2254,13 +2720,16 @@ async function startFreshCodexThread(params, context) {
|
|
|
2254
2720
|
threadId: response.thread.id,
|
|
2255
2721
|
...clientId ? { clientId } : {},
|
|
2256
2722
|
cwd: params.cwd,
|
|
2723
|
+
...rolloutPath ? { rolloutPath } : {},
|
|
2257
2724
|
authProfileId: params.params.authProfileId,
|
|
2258
2725
|
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2259
2726
|
modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
|
|
2260
2727
|
dynamicToolsFingerprint,
|
|
2261
2728
|
dynamicToolsContainDeferred,
|
|
2729
|
+
nativeSkillIsolationFingerprint,
|
|
2262
2730
|
userMcpServersFingerprint,
|
|
2263
2731
|
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2732
|
+
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2264
2733
|
ringZeroConfigFingerprint,
|
|
2265
2734
|
ringZeroClientInstanceId,
|
|
2266
2735
|
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
@@ -2272,6 +2741,13 @@ async function startFreshCodexThread(params, context) {
|
|
|
2272
2741
|
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2273
2742
|
contextEngine: contextEngineBinding,
|
|
2274
2743
|
environmentSelectionFingerprint,
|
|
2744
|
+
...!preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
|
|
2745
|
+
...startParams,
|
|
2746
|
+
model: response.model ?? startParams.model ?? null,
|
|
2747
|
+
requestedModel: startParams.model ?? null,
|
|
2748
|
+
modelProvider: bindingModelProvider ?? null,
|
|
2749
|
+
requestedModelProvider: startParams.modelProvider ?? bindingModelProvider ?? null
|
|
2750
|
+
}, params.params.authProfileId, dynamicToolsFingerprint) } : {},
|
|
2275
2751
|
lifecycle: {
|
|
2276
2752
|
action: "started",
|
|
2277
2753
|
...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
|
|
@@ -2279,6 +2755,13 @@ async function startFreshCodexThread(params, context) {
|
|
|
2279
2755
|
};
|
|
2280
2756
|
}
|
|
2281
2757
|
//#endregion
|
|
2758
|
+
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
2759
|
+
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
2760
|
+
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
2761
|
+
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
2762
|
+
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
2763
|
+
}
|
|
2764
|
+
//#endregion
|
|
2282
2765
|
//#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
|
|
2283
2766
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
|
|
2284
2767
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
|
|
@@ -2368,6 +2851,193 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
|
2368
2851
|
};
|
|
2369
2852
|
}
|
|
2370
2853
|
//#endregion
|
|
2854
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
|
|
2855
|
+
async function prepareCodexThreadLifecyclePreflight(params) {
|
|
2856
|
+
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
2857
|
+
...params.timing,
|
|
2858
|
+
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
2859
|
+
});
|
|
2860
|
+
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
2861
|
+
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
2862
|
+
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
2863
|
+
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
2864
|
+
config: params.params.config,
|
|
2865
|
+
disableTools: params.params.disableTools,
|
|
2866
|
+
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
2867
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2868
|
+
webSearchAllowed: params.webSearchAllowed
|
|
2869
|
+
})).threadConfig);
|
|
2870
|
+
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
2871
|
+
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
2872
|
+
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
|
|
2873
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
2874
|
+
agentDir: params.params.agentDir,
|
|
2875
|
+
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
2876
|
+
toolOverrides: params.params.toolOverrides,
|
|
2877
|
+
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
2878
|
+
serverName,
|
|
2879
|
+
error: formatErrorMessage(error)
|
|
2880
|
+
})
|
|
2881
|
+
});
|
|
2882
|
+
const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
|
|
2883
|
+
client: params.client,
|
|
2884
|
+
codexHome: params.appServer.start.env?.CODEX_HOME,
|
|
2885
|
+
cwd: params.cwd,
|
|
2886
|
+
home: params.appServer.start.env?.HOME,
|
|
2887
|
+
signal: params.signal,
|
|
2888
|
+
userProfile: params.appServer.start.env?.USERPROFILE
|
|
2889
|
+
}));
|
|
2890
|
+
const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
|
|
2891
|
+
version: 1,
|
|
2892
|
+
disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
|
|
2893
|
+
}) : void 0;
|
|
2894
|
+
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2895
|
+
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2896
|
+
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
2897
|
+
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
2898
|
+
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
2899
|
+
const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
|
|
2900
|
+
const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
|
|
2901
|
+
if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
|
|
2902
|
+
const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
2903
|
+
if (restrictedToolSurface) await lifecycleTiming.measure("restricted-tool-surface-config-requirements-read", () => assertCodexRestrictedToolSurfaceHasNoManagedHooks(params.client, params.signal));
|
|
2904
|
+
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
2905
|
+
version: 1,
|
|
2906
|
+
baseInstructions: "",
|
|
2907
|
+
config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
|
|
2908
|
+
}) : void 0;
|
|
2909
|
+
return {
|
|
2910
|
+
contextEngineBinding,
|
|
2911
|
+
dynamicToolsContainDeferred,
|
|
2912
|
+
dynamicToolsFingerprint,
|
|
2913
|
+
environmentSelectionFingerprint,
|
|
2914
|
+
hostSystemAgentActive,
|
|
2915
|
+
legacyDynamicToolsFingerprint,
|
|
2916
|
+
legacyUserMcpServersFingerprint,
|
|
2917
|
+
lifecycleTiming,
|
|
2918
|
+
nativeSkillIsolation,
|
|
2919
|
+
nativeSkillIsolationFingerprint,
|
|
2920
|
+
networkProxyConfigFingerprint,
|
|
2921
|
+
ringZeroActive,
|
|
2922
|
+
ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
|
|
2923
|
+
ringZeroConfigFingerprint,
|
|
2924
|
+
restrictedToolSurface,
|
|
2925
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
2926
|
+
userMcpServersConfigPatch,
|
|
2927
|
+
userMcpServersFingerprint,
|
|
2928
|
+
webSearchThreadConfigFingerprint
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
//#endregion
|
|
2932
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
|
|
2933
|
+
/** Preserves the caller's abort reason across thread ownership transitions. */
|
|
2934
|
+
function throwIfCodexThreadLifecycleAborted(signal) {
|
|
2935
|
+
if (!signal?.aborted) return;
|
|
2936
|
+
const reason = signal.reason;
|
|
2937
|
+
if (reason instanceof Error) throw reason;
|
|
2938
|
+
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
2939
|
+
error.name = "AbortError";
|
|
2940
|
+
throw error;
|
|
2941
|
+
}
|
|
2942
|
+
/** Releases consumed subscription ownership or retires an unsafe client. */
|
|
2943
|
+
async function releaseCodexConsumedLiveThread(options) {
|
|
2944
|
+
if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
|
|
2945
|
+
threadId: options.threadId,
|
|
2946
|
+
timeoutMs: 5e3
|
|
2947
|
+
}))) return;
|
|
2948
|
+
await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
|
|
2949
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, options.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
2950
|
+
}
|
|
2951
|
+
/** Transfers retained-slot ownership before unconditionally releasing it. */
|
|
2952
|
+
async function releaseCodexRetainedLiveThread(options) {
|
|
2953
|
+
if (await consumeCodexAppServerLiveThread(options.client, options.threadId)) await releaseCodexConsumedLiveThread(options);
|
|
2954
|
+
}
|
|
2955
|
+
/** Reuses one safely owned, fully matching subscription on its original client. */
|
|
2956
|
+
async function tryReuseCodexLiveThread(options) {
|
|
2957
|
+
const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, releaseConsumedThread, ringZeroActive, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
|
|
2958
|
+
if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive) return {};
|
|
2959
|
+
const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
|
|
2960
|
+
action: "resume",
|
|
2961
|
+
binding
|
|
2962
|
+
}) ?? {
|
|
2963
|
+
configPatch: params.finalConfigPatch,
|
|
2964
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2965
|
+
};
|
|
2966
|
+
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0;
|
|
2967
|
+
const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
|
|
2968
|
+
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
|
|
2969
|
+
const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2970
|
+
threadId: binding.threadId,
|
|
2971
|
+
authProfileId: resumeAuthProfileId,
|
|
2972
|
+
model: startModelSelection.model,
|
|
2973
|
+
modelProvider: startModelProvider,
|
|
2974
|
+
preserveNativeModel: false,
|
|
2975
|
+
appServer: params.appServer,
|
|
2976
|
+
dynamicTools: params.dynamicTools,
|
|
2977
|
+
developerInstructions: params.developerInstructions,
|
|
2978
|
+
config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
|
|
2979
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2980
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2981
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2982
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2983
|
+
hostSystemAgentActive,
|
|
2984
|
+
restrictedToolSurfaceInheritedMcpServerNames
|
|
2985
|
+
}));
|
|
2986
|
+
const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
|
|
2987
|
+
...resumeParams,
|
|
2988
|
+
model: binding.model ?? resumeParams.model ?? null,
|
|
2989
|
+
requestedModel: resumeParams.model ?? null,
|
|
2990
|
+
modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
|
|
2991
|
+
requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
|
|
2992
|
+
}, resumeAuthProfileId, dynamicToolsFingerprint);
|
|
2993
|
+
const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint);
|
|
2994
|
+
if (!retainedThread) {
|
|
2995
|
+
const incompatibleOwnership = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
|
|
2996
|
+
if (incompatibleOwnership) await incompatibleOwnership.release(binding.threadId);
|
|
2997
|
+
return { prebuiltFinalConfigPatch };
|
|
2998
|
+
}
|
|
2999
|
+
try {
|
|
3000
|
+
const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
|
|
3001
|
+
const model = startModelSelection.model;
|
|
3002
|
+
if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
3003
|
+
kind: "patch",
|
|
3004
|
+
threadId: binding.threadId,
|
|
3005
|
+
patch: {
|
|
3006
|
+
cwd: params.cwd,
|
|
3007
|
+
model,
|
|
3008
|
+
nativeHookRelayGeneration,
|
|
3009
|
+
environmentSelectionFingerprint
|
|
3010
|
+
}
|
|
3011
|
+
}))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
|
|
3012
|
+
throwIfAborted();
|
|
3013
|
+
lifecycleTiming.mark("thread-ready");
|
|
3014
|
+
lifecycleTiming.logSummary({
|
|
3015
|
+
runId: params.params.runId,
|
|
3016
|
+
sessionId: params.params.sessionId,
|
|
3017
|
+
sessionKey: params.params.sessionKey,
|
|
3018
|
+
threadId: binding.threadId,
|
|
3019
|
+
action: "resumed"
|
|
3020
|
+
});
|
|
3021
|
+
return {
|
|
3022
|
+
binding: {
|
|
3023
|
+
...binding,
|
|
3024
|
+
cwd: params.cwd,
|
|
3025
|
+
model,
|
|
3026
|
+
nativeHookRelayGeneration,
|
|
3027
|
+
environmentSelectionFingerprint,
|
|
3028
|
+
liveThreadConfigFingerprint,
|
|
3029
|
+
liveThreadOwnership: retainedThread,
|
|
3030
|
+
...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
|
|
3031
|
+
lifecycle: { action: "resumed" }
|
|
3032
|
+
},
|
|
3033
|
+
prebuiltFinalConfigPatch
|
|
3034
|
+
};
|
|
3035
|
+
} catch (error) {
|
|
3036
|
+
await releaseConsumedThread(binding.threadId, error);
|
|
3037
|
+
throw error;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
//#endregion
|
|
2371
3041
|
//#region extensions/codex/src/app-server/thread-supervision.ts
|
|
2372
3042
|
async function materializePendingSupervisionBranch(params) {
|
|
2373
3043
|
let pending = params.binding.pendingSupervisionBranch;
|
|
@@ -2409,6 +3079,7 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
2409
3079
|
pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId]);
|
|
2410
3080
|
params.throwIfAborted();
|
|
2411
3081
|
const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
3082
|
+
if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
|
|
2412
3083
|
const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
|
|
2413
3084
|
const nativeModelProvider = requireNativeSupervisionModelProvider({
|
|
2414
3085
|
responseModelProvider: probeResponse.modelProvider,
|
|
@@ -2429,7 +3100,9 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
2429
3100
|
webSearchAllowed: params.webSearchAllowed,
|
|
2430
3101
|
environmentSelection: params.environmentSelection,
|
|
2431
3102
|
model: nativeModel,
|
|
2432
|
-
modelProvider: nativeModelProvider
|
|
3103
|
+
modelProvider: nativeModelProvider,
|
|
3104
|
+
hostSystemAgentActive: params.hostSystemAgentActive,
|
|
3105
|
+
restrictedToolSurfaceInheritedMcpServerNames: params.restrictedToolSurfaceInheritedMcpServerNames
|
|
2433
3106
|
});
|
|
2434
3107
|
assertExactSupervisionModelSelection(startParams, {
|
|
2435
3108
|
model: nativeModel,
|
|
@@ -2457,6 +3130,26 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
2457
3130
|
modelProvider: nativeModelProvider,
|
|
2458
3131
|
operation: "thread/start response"
|
|
2459
3132
|
});
|
|
3133
|
+
if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, finalThreadId, startParams.config, params.signal));
|
|
3134
|
+
if (params.provisionalAppIds?.length) try {
|
|
3135
|
+
await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
|
|
3136
|
+
client: params.client,
|
|
3137
|
+
threadId: finalThreadId,
|
|
3138
|
+
appIds: params.provisionalAppIds ?? [],
|
|
3139
|
+
signal: params.signal
|
|
3140
|
+
}));
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
if (!await discardUnattestedCodexPluginThread({
|
|
3143
|
+
client: params.client,
|
|
3144
|
+
threadId: finalThreadId,
|
|
3145
|
+
ephemeral: startParams.ephemeral === true
|
|
3146
|
+
}) || !await archiveSupervisionArtifact(params.client, probeThreadId)) {
|
|
3147
|
+
provisionalCleanupSafe = false;
|
|
3148
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex supervised plugin app attestation cleanup failed", { cause: error });
|
|
3149
|
+
}
|
|
3150
|
+
pending = await trackPendingSupervisionArtifacts(params, pending, []);
|
|
3151
|
+
throw error;
|
|
3152
|
+
}
|
|
2460
3153
|
if (history.responseItems.length > 0) {
|
|
2461
3154
|
await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
|
|
2462
3155
|
threadId: finalThreadId,
|
|
@@ -2563,7 +3256,9 @@ function buildPendingSupervisionProbeForkParams(params, pending) {
|
|
|
2563
3256
|
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2564
3257
|
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2565
3258
|
webSearchAllowed: params.webSearchAllowed,
|
|
2566
|
-
appServer: params.appServer
|
|
3259
|
+
appServer: params.appServer,
|
|
3260
|
+
hostSystemAgentActive: params.hostSystemAgentActive,
|
|
3261
|
+
restrictedToolSurfaceInheritedMcpServerNames: params.restrictedToolSurfaceInheritedMcpServerNames
|
|
2567
3262
|
});
|
|
2568
3263
|
return {
|
|
2569
3264
|
threadId: pending.sourceThreadId,
|
|
@@ -2700,46 +3395,11 @@ async function startOrResumeThread(params) {
|
|
|
2700
3395
|
config: params.params.config
|
|
2701
3396
|
});
|
|
2702
3397
|
return await params.bindingStore.withLease(bindingIdentity, async () => {
|
|
2703
|
-
const lifecycleTiming =
|
|
2704
|
-
...params.timing,
|
|
2705
|
-
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
2706
|
-
});
|
|
2707
|
-
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
2708
|
-
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
2709
|
-
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
2710
|
-
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
2711
|
-
config: params.params.config,
|
|
2712
|
-
disableTools: params.params.disableTools,
|
|
2713
|
-
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
2714
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2715
|
-
webSearchAllowed: params.webSearchAllowed
|
|
2716
|
-
})).threadConfig);
|
|
2717
|
-
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
2718
|
-
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
2719
|
-
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
|
|
2720
|
-
agentId: params.agentId ?? params.params.agentId,
|
|
2721
|
-
agentDir: params.params.agentDir,
|
|
2722
|
-
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
2723
|
-
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
2724
|
-
serverName,
|
|
2725
|
-
error: formatErrorMessage(error)
|
|
2726
|
-
})
|
|
2727
|
-
});
|
|
2728
|
-
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2729
|
-
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2730
|
-
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
2731
|
-
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
2732
|
-
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
2733
|
-
if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
|
|
2734
|
-
const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
2735
|
-
if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
|
|
2736
|
-
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
2737
|
-
version: 1,
|
|
2738
|
-
baseInstructions: "",
|
|
2739
|
-
config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
|
|
2740
|
-
}) : void 0;
|
|
2741
|
-
const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
|
|
3398
|
+
const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = await prepareCodexThreadLifecyclePreflight(params);
|
|
2742
3399
|
let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
|
|
3400
|
+
let replacementPredecessor;
|
|
3401
|
+
const initialBoundThreadId = binding?.threadId;
|
|
3402
|
+
const initialBoundClientId = binding?.clientId;
|
|
2743
3403
|
const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
|
|
2744
3404
|
authProfileId,
|
|
2745
3405
|
modelProvider,
|
|
@@ -2747,13 +3407,28 @@ async function startOrResumeThread(params) {
|
|
|
2747
3407
|
agentDir: params.params.agentDir,
|
|
2748
3408
|
config: params.params.config
|
|
2749
3409
|
});
|
|
2750
|
-
const throwIfAborted = () =>
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3410
|
+
const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
|
|
3411
|
+
const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId) => {
|
|
3412
|
+
if (ownerClientId && ownerClientId !== clientId) {
|
|
3413
|
+
const previousClient = retainSharedCodexAppServerClientByInstanceId(ownerClientId);
|
|
3414
|
+
if (!previousClient) return;
|
|
3415
|
+
try {
|
|
3416
|
+
await releaseCodexRetainedLiveThread({
|
|
3417
|
+
client: previousClient.client,
|
|
3418
|
+
lifecycleTiming,
|
|
3419
|
+
threadId
|
|
3420
|
+
});
|
|
3421
|
+
} finally {
|
|
3422
|
+
previousClient.release();
|
|
3423
|
+
}
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
await releaseCodexRetainedLiveThread({
|
|
3427
|
+
client: params.client,
|
|
3428
|
+
abandonClient: params.abandonClient,
|
|
3429
|
+
lifecycleTiming,
|
|
3430
|
+
threadId
|
|
3431
|
+
});
|
|
2757
3432
|
};
|
|
2758
3433
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
2759
3434
|
if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
|
|
@@ -2763,13 +3438,14 @@ async function startOrResumeThread(params) {
|
|
|
2763
3438
|
}))) throw createCodexSessionGenerationSupersededError(bindingIdentity.sessionId);
|
|
2764
3439
|
}
|
|
2765
3440
|
if (binding?.pendingSupervisionBranch) {
|
|
3441
|
+
await releaseRetainedThread(binding.threadId);
|
|
2766
3442
|
const pendingBinding = binding;
|
|
2767
3443
|
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2768
3444
|
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2769
3445
|
configPatch: params.finalConfigPatch,
|
|
2770
3446
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2771
3447
|
};
|
|
2772
|
-
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
3448
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
|
|
2773
3449
|
return await materializePendingSupervisionBranch({
|
|
2774
3450
|
client: params.client,
|
|
2775
3451
|
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
@@ -2786,7 +3462,11 @@ async function startOrResumeThread(params) {
|
|
|
2786
3462
|
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2787
3463
|
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2788
3464
|
webSearchAllowed: params.webSearchAllowed,
|
|
3465
|
+
hostSystemAgentActive,
|
|
3466
|
+
restrictedToolSurface,
|
|
3467
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
2789
3468
|
environmentSelection: params.environmentSelection,
|
|
3469
|
+
provisionalAppIds: pluginThreadConfig?.provisionalAppIds,
|
|
2790
3470
|
signal: params.signal,
|
|
2791
3471
|
throwIfAborted,
|
|
2792
3472
|
lifecycleTiming,
|
|
@@ -2799,8 +3479,10 @@ async function startOrResumeThread(params) {
|
|
|
2799
3479
|
dynamicToolsFingerprint,
|
|
2800
3480
|
dynamicToolsContainDeferred,
|
|
2801
3481
|
webSearchThreadConfigFingerprint,
|
|
3482
|
+
nativeSkillIsolationFingerprint,
|
|
2802
3483
|
userMcpServersFingerprint,
|
|
2803
3484
|
mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
|
|
3485
|
+
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2804
3486
|
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2805
3487
|
networkProxyConfigFingerprint,
|
|
2806
3488
|
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
@@ -2824,6 +3506,11 @@ async function startOrResumeThread(params) {
|
|
|
2824
3506
|
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
2825
3507
|
binding = void 0;
|
|
2826
3508
|
};
|
|
3509
|
+
if (binding?.threadId && !restrictedToolSurface && binding.nativeToolPolicyRestricted === true) await clearCurrentBinding("rotating a host-policy-restricted thread binding");
|
|
3510
|
+
if (binding?.threadId && binding.nativeSkillIsolationFingerprint !== nativeSkillIsolationFingerprint) {
|
|
3511
|
+
embeddedAgentLog.debug("codex app-server native skill isolation changed; starting a new thread", { threadId: binding.threadId });
|
|
3512
|
+
await clearCurrentBinding("rotating stale native skill isolation");
|
|
3513
|
+
}
|
|
2827
3514
|
if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
|
|
2828
3515
|
embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
|
|
2829
3516
|
await clearCurrentBinding("rotating a ring-zero thread binding");
|
|
@@ -2872,6 +3559,14 @@ async function startOrResumeThread(params) {
|
|
|
2872
3559
|
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
2873
3560
|
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
2874
3561
|
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
3562
|
+
if (binding?.threadId && (params.configuredMcpOwnershipVersion === 1 && (binding.configuredMcpOwnershipVersion !== 1 || binding.dynamicToolsFingerprint === void 0 || binding.mcpServersFingerprint !== void 0 || binding.userMcpServersFingerprint !== void 0) || params.configuredMcpOwnershipVersion !== 1 && binding.configuredMcpOwnershipVersion === 1) && binding?.threadId) {
|
|
3563
|
+
const predecessorBinding = binding;
|
|
3564
|
+
assertCodexBindingMayBeReplaced(predecessorBinding, "changing configured MCP ownership");
|
|
3565
|
+
embeddedAgentLog.debug("codex app-server configured MCP ownership changed; starting a new thread", { threadId: predecessorBinding.threadId });
|
|
3566
|
+
replacementPredecessor = predecessorBinding;
|
|
3567
|
+
binding = void 0;
|
|
3568
|
+
preserveExistingBinding = false;
|
|
3569
|
+
}
|
|
2875
3570
|
if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
|
|
2876
3571
|
assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
|
|
2877
3572
|
if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
|
|
@@ -2887,7 +3582,7 @@ async function startOrResumeThread(params) {
|
|
|
2887
3582
|
if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
|
|
2888
3583
|
assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
|
|
2889
3584
|
if (!ringZeroActive && transientWebSearchRestriction) {
|
|
2890
|
-
embeddedAgentLog.debug("codex app-server
|
|
3585
|
+
embeddedAgentLog.debug("codex app-server tool surface restricted for turn; starting transient thread", { threadId: binding.threadId });
|
|
2891
3586
|
preserveExistingBinding = true;
|
|
2892
3587
|
} else {
|
|
2893
3588
|
embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
|
|
@@ -2933,11 +3628,6 @@ async function startOrResumeThread(params) {
|
|
|
2933
3628
|
await clearCurrentBinding("rotating a stale thread binding");
|
|
2934
3629
|
binding = void 0;
|
|
2935
3630
|
}
|
|
2936
|
-
if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
|
|
2937
|
-
embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
|
|
2938
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
2939
|
-
binding = void 0;
|
|
2940
|
-
}
|
|
2941
3631
|
if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
|
|
2942
3632
|
embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
|
|
2943
3633
|
await clearCurrentBinding("rotating a stale thread binding");
|
|
@@ -2951,13 +3641,15 @@ async function startOrResumeThread(params) {
|
|
|
2951
3641
|
currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
|
|
2952
3642
|
hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
|
|
2953
3643
|
});
|
|
2954
|
-
if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
|
|
3644
|
+
if (!pluginBindingStale && (params.pluginThreadConfig?.requiresCurrentPolicyCheck || shouldRecheckRecoverablePluginBinding({
|
|
2955
3645
|
binding,
|
|
2956
3646
|
pluginThreadConfig: params.pluginThreadConfig
|
|
2957
|
-
})) try {
|
|
2958
|
-
|
|
3647
|
+
}))) try {
|
|
3648
|
+
const bindingThreadId = binding.threadId;
|
|
3649
|
+
prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: bindingThreadId }));
|
|
2959
3650
|
pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
|
|
2960
3651
|
} catch (error) {
|
|
3652
|
+
if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
|
|
2961
3653
|
embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
|
|
2962
3654
|
error,
|
|
2963
3655
|
threadId: binding.threadId
|
|
@@ -3010,6 +3702,32 @@ async function startOrResumeThread(params) {
|
|
|
3010
3702
|
await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
|
|
3011
3703
|
binding = void 0;
|
|
3012
3704
|
} else {
|
|
3705
|
+
const warmReuse = await tryReuseCodexLiveThread({
|
|
3706
|
+
params,
|
|
3707
|
+
binding,
|
|
3708
|
+
bindingIdentity,
|
|
3709
|
+
clientId,
|
|
3710
|
+
dynamicToolsFingerprint,
|
|
3711
|
+
environmentSelectionFingerprint,
|
|
3712
|
+
hostSystemAgentActive,
|
|
3713
|
+
lifecycleTiming,
|
|
3714
|
+
nativeSkillIsolation,
|
|
3715
|
+
releaseConsumedThread: (threadId, cause) => releaseCodexConsumedLiveThread({
|
|
3716
|
+
client: params.client,
|
|
3717
|
+
abandonClient: params.abandonClient,
|
|
3718
|
+
lifecycleTiming,
|
|
3719
|
+
threadId,
|
|
3720
|
+
cause
|
|
3721
|
+
}),
|
|
3722
|
+
ringZeroActive,
|
|
3723
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3724
|
+
startModelProvider,
|
|
3725
|
+
startModelSelection,
|
|
3726
|
+
throwIfAborted,
|
|
3727
|
+
userMcpServersConfigPatch
|
|
3728
|
+
});
|
|
3729
|
+
if (warmReuse.binding) return warmReuse.binding;
|
|
3730
|
+
await releaseRetainedThread(binding.threadId);
|
|
3013
3731
|
const resumed = await resumeExistingCodexThread(params, {
|
|
3014
3732
|
binding,
|
|
3015
3733
|
bindingIdentity,
|
|
@@ -3019,6 +3737,7 @@ async function startOrResumeThread(params) {
|
|
|
3019
3737
|
dynamicToolsFingerprint,
|
|
3020
3738
|
dynamicToolsContainDeferred,
|
|
3021
3739
|
webSearchThreadConfigFingerprint,
|
|
3740
|
+
nativeSkillIsolationFingerprint,
|
|
3022
3741
|
userMcpServersFingerprint,
|
|
3023
3742
|
ringZeroConfigFingerprint,
|
|
3024
3743
|
ringZeroClientInstanceId,
|
|
@@ -3027,15 +3746,20 @@ async function startOrResumeThread(params) {
|
|
|
3027
3746
|
environmentSelectionFingerprint,
|
|
3028
3747
|
hostSystemAgentActive,
|
|
3029
3748
|
ringZeroActive,
|
|
3030
|
-
|
|
3749
|
+
restrictedToolSurface,
|
|
3750
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3751
|
+
nativeSkillIsolation,
|
|
3031
3752
|
lifecycleTiming,
|
|
3032
3753
|
normalizeBindingModelProvider,
|
|
3033
3754
|
throwIfAborted,
|
|
3034
|
-
clearCurrentBinding
|
|
3755
|
+
clearCurrentBinding,
|
|
3756
|
+
prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
|
|
3757
|
+
prebuiltPluginThreadConfig
|
|
3035
3758
|
});
|
|
3036
3759
|
if (resumed) return resumed;
|
|
3037
3760
|
}
|
|
3038
|
-
|
|
3761
|
+
if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
|
|
3762
|
+
const started = await startFreshCodexThread(params, {
|
|
3039
3763
|
bindingIdentity,
|
|
3040
3764
|
startModelSelection,
|
|
3041
3765
|
startModelProvider,
|
|
@@ -3043,6 +3767,7 @@ async function startOrResumeThread(params) {
|
|
|
3043
3767
|
dynamicToolsFingerprint,
|
|
3044
3768
|
dynamicToolsContainDeferred,
|
|
3045
3769
|
webSearchThreadConfigFingerprint,
|
|
3770
|
+
nativeSkillIsolationFingerprint,
|
|
3046
3771
|
userMcpServersFingerprint,
|
|
3047
3772
|
ringZeroConfigFingerprint,
|
|
3048
3773
|
ringZeroClientInstanceId,
|
|
@@ -3051,84 +3776,27 @@ async function startOrResumeThread(params) {
|
|
|
3051
3776
|
environmentSelectionFingerprint,
|
|
3052
3777
|
hostSystemAgentActive,
|
|
3053
3778
|
ringZeroActive,
|
|
3054
|
-
|
|
3779
|
+
restrictedToolSurface,
|
|
3780
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3781
|
+
nativeSkillIsolation,
|
|
3055
3782
|
lifecycleTiming,
|
|
3056
3783
|
normalizeBindingModelProvider,
|
|
3057
3784
|
throwIfAborted,
|
|
3058
3785
|
prebuiltPluginThreadConfig,
|
|
3059
3786
|
preserveExistingBinding,
|
|
3060
|
-
rotatedContextEngineBinding
|
|
3787
|
+
rotatedContextEngineBinding,
|
|
3788
|
+
replacementPredecessor
|
|
3061
3789
|
});
|
|
3790
|
+
if (replacementPredecessor) await releaseRetainedThread(replacementPredecessor.threadId, replacementPredecessor.clientId);
|
|
3791
|
+
return started;
|
|
3062
3792
|
});
|
|
3063
3793
|
}
|
|
3064
3794
|
//#endregion
|
|
3065
|
-
//#region extensions/codex/src/app-server/image-payload-sanitizer.ts
|
|
3066
|
-
/**
|
|
3067
|
-
* Sanitizes inline image payloads mirrored through Codex history so invalid
|
|
3068
|
-
* base64 data becomes readable text instead of poisoning replayed transcripts.
|
|
3069
|
-
*/
|
|
3070
|
-
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
3071
|
-
/** Validates and normalizes an inline image data URL for Codex history payloads. */
|
|
3072
|
-
function sanitizeInlineImageDataUrl$1(imageUrl) {
|
|
3073
|
-
return sanitizeInlineImageDataUrl(imageUrl);
|
|
3074
|
-
}
|
|
3075
|
-
/** Builds the replacement text inserted when an inline image payload is invalid. */
|
|
3076
|
-
function invalidInlineImageText(label) {
|
|
3077
|
-
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
3078
|
-
}
|
|
3079
|
-
function sanitizeImageContentRecord(record, label) {
|
|
3080
|
-
if (record.type === "image" && typeof record.data === "string") {
|
|
3081
|
-
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
|
|
3082
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
|
|
3083
|
-
if (!imageUrl) return {
|
|
3084
|
-
type: "text",
|
|
3085
|
-
text: invalidInlineImageText(label)
|
|
3086
|
-
};
|
|
3087
|
-
const commaIndex = imageUrl.indexOf(",");
|
|
3088
|
-
const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
|
|
3089
|
-
return {
|
|
3090
|
-
...record,
|
|
3091
|
-
mimeType: mime,
|
|
3092
|
-
data: imageUrl.slice(commaIndex + 1)
|
|
3093
|
-
};
|
|
3094
|
-
}
|
|
3095
|
-
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
3096
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
|
|
3097
|
-
return imageUrl ? {
|
|
3098
|
-
...record,
|
|
3099
|
-
imageUrl
|
|
3100
|
-
} : {
|
|
3101
|
-
type: "inputText",
|
|
3102
|
-
text: invalidInlineImageText(label)
|
|
3103
|
-
};
|
|
3104
|
-
}
|
|
3105
|
-
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
3106
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
|
|
3107
|
-
return imageUrl ? {
|
|
3108
|
-
...record,
|
|
3109
|
-
image_url: imageUrl
|
|
3110
|
-
} : {
|
|
3111
|
-
type: "input_text",
|
|
3112
|
-
text: invalidInlineImageText(label)
|
|
3113
|
-
};
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
/** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
|
|
3117
|
-
function sanitizeCodexHistoryImagePayloads(value, label) {
|
|
3118
|
-
if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
|
|
3119
|
-
if (!isRecord(value)) return value;
|
|
3120
|
-
const imageRecord = sanitizeImageContentRecord(value, label);
|
|
3121
|
-
if (imageRecord) return imageRecord;
|
|
3122
|
-
const next = {};
|
|
3123
|
-
for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
|
|
3124
|
-
return next;
|
|
3125
|
-
}
|
|
3126
|
-
//#endregion
|
|
3127
3795
|
//#region extensions/codex/src/app-server/user-input.ts
|
|
3128
3796
|
/** Builds ordered Codex user input for both new turns and same-turn steering. */
|
|
3129
3797
|
function buildCodexUserInput(text, images) {
|
|
3130
3798
|
const imageInputs = (images ?? []).map((image) => {
|
|
3131
|
-
const imageUrl = sanitizeInlineImageDataUrl
|
|
3799
|
+
const imageUrl = sanitizeInlineImageDataUrl(`data:${image.mimeType};base64,${image.data}`);
|
|
3132
3800
|
return imageUrl ? {
|
|
3133
3801
|
type: "image",
|
|
3134
3802
|
url: imageUrl
|
|
@@ -3146,6 +3814,27 @@ function buildCodexUserInput(text, images) {
|
|
|
3146
3814
|
}
|
|
3147
3815
|
//#endregion
|
|
3148
3816
|
//#region extensions/codex/src/app-server/turn-params.ts
|
|
3817
|
+
const CODEX_CURRENT_SENDER_FIELD_MAX_CHARS = 256;
|
|
3818
|
+
function buildCodexCurrentSenderContextValue(params) {
|
|
3819
|
+
const metadata = asOptionalRecord(asOptionalRecord(params.userTurnTranscriptRecorder?.message)?.["__openclaw"]);
|
|
3820
|
+
const recorded = [
|
|
3821
|
+
normalizeOptionalString(metadata?.["senderId"]),
|
|
3822
|
+
normalizeOptionalString(metadata?.["senderName"]),
|
|
3823
|
+
normalizeOptionalString(metadata?.["senderUsername"])
|
|
3824
|
+
];
|
|
3825
|
+
const [id, name, username] = recorded.some(Boolean) ? recorded : [
|
|
3826
|
+
normalizeOptionalString(params.senderId),
|
|
3827
|
+
normalizeOptionalString(params.senderName),
|
|
3828
|
+
normalizeOptionalString(params.senderUsername)
|
|
3829
|
+
];
|
|
3830
|
+
if (!id && !name && !username) return;
|
|
3831
|
+
const bound = (value) => truncateUtf16Safe(value, CODEX_CURRENT_SENDER_FIELD_MAX_CHARS);
|
|
3832
|
+
return JSON.stringify({ sender: {
|
|
3833
|
+
...id ? { id: bound(id) } : {},
|
|
3834
|
+
...name ? { name: bound(name) } : {},
|
|
3835
|
+
...username ? { username: bound(username) } : {}
|
|
3836
|
+
} });
|
|
3837
|
+
}
|
|
3149
3838
|
function buildTurnStartParams(params, options) {
|
|
3150
3839
|
const modelSelection = options.preserveNativeTurnSettings ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
3151
3840
|
model: options.model ?? params.modelId,
|
|
@@ -3156,9 +3845,15 @@ function buildTurnStartParams(params, options) {
|
|
|
3156
3845
|
config: params.config
|
|
3157
3846
|
});
|
|
3158
3847
|
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
3848
|
+
const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
|
|
3849
|
+
const additionalContext = currentSenderContext ? { openclaw_current_sender: {
|
|
3850
|
+
kind: "untrusted",
|
|
3851
|
+
value: currentSenderContext
|
|
3852
|
+
} } : void 0;
|
|
3159
3853
|
return {
|
|
3160
3854
|
threadId: options.threadId,
|
|
3161
|
-
input: buildCodexUserInput(options.promptText ?? params.prompt, params.images),
|
|
3855
|
+
input: [...buildCodexUserInput(options.promptText ?? params.prompt, params.images), ...options.explicitSkillInputs ?? []],
|
|
3856
|
+
...additionalContext ? { additionalContext } : {},
|
|
3162
3857
|
cwd: options.cwd,
|
|
3163
3858
|
approvalPolicy: options.appServer.approvalPolicy,
|
|
3164
3859
|
approvalsReviewer: options.appServer.approvalsReviewer,
|
|
@@ -3167,7 +3862,7 @@ function buildTurnStartParams(params, options) {
|
|
|
3167
3862
|
model: modelSelection.model,
|
|
3168
3863
|
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
3169
3864
|
} : {},
|
|
3170
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
3865
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : options.clearInheritedServiceTier ? { serviceTier: null } : {},
|
|
3171
3866
|
...modelSelection ? { effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)) } : {},
|
|
3172
3867
|
...options.environmentSelection ? { environments: options.environmentSelection } : {},
|
|
3173
3868
|
...modelSelection ? { collaborationMode: buildTurnCollaborationMode(params, {
|
|
@@ -3192,7 +3887,7 @@ function buildTurnCollaborationMode(params, options = {}) {
|
|
|
3192
3887
|
function buildTurnScopedCollaborationInstructions(params, options = {}) {
|
|
3193
3888
|
const contextInstructions = joinPresentSections(options.turnScopedDeveloperInstructions, options.memoryCollaborationInstructions, options.skillsCollaborationInstructions);
|
|
3194
3889
|
if (params.trigger === "cron") return joinPresentSections(buildCronCollaborationInstructions(), contextInstructions);
|
|
3195
|
-
if (params.trigger === "heartbeat"
|
|
3890
|
+
if (params.trigger === "heartbeat") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions);
|
|
3196
3891
|
if (contextInstructions?.trim()) return joinPresentSections(buildDefaultCollaborationInstructions(), contextInstructions);
|
|
3197
3892
|
return null;
|
|
3198
3893
|
}
|
|
@@ -3230,4 +3925,4 @@ function joinPresentSections(...sections) {
|
|
|
3230
3925
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
3231
3926
|
}
|
|
3232
3927
|
//#endregion
|
|
3233
|
-
export {
|
|
3928
|
+
export { resolveCodexDynamicToolsLoadingForRuntime as A, buildCodexPluginThreadConfigInputFingerprint as B, codexLegacyDynamicToolsFingerprint as C, isSystemAgentOnlyCodexDynamicToolAllowlist as D, isForcedPrivateQaCodexRuntime as E, projectContextEngineAssemblyForCodex as F, shouldBuildCodexPluginThreadConfig as G, buildDisabledAppsConfigPatch as H, resolveCodexContextEngineProjectionMaxChars as I, isOpenAiCuratedMarketplace as J, ensureCodexPluginActivation as K, resolveCodexContextEngineProjectionReserveTokens as L, isContextEngineBindingCompatible as M, fitCodexProjectedContextForTurnStart as N, normalizeCodexDynamicToolName as O, neutralizeCodexExplicitMentionSigils as P, buildCodexPluginAppsConfigPatchFromPolicyContext as R, codexDynamicToolsFingerprint as S, filterCodexDynamicToolsForDisabledNativeSurface as T, buildPluginAppPolicyContext as U, buildCodexPluginThreadConfigTimeoutFallback as V, mergeCodexThreadConfigs as W, pluginReadParams as X, isOpenAiCuratedMarketplaceName as Y, resolveRecoverableCodexPluginConfigKeys as Z, resolveCodexAppServerThreadModelSelection as _, isCodexAppServerProfilerEnabled as a, readCodexSupportedReasoningEfforts as b, buildCodexRingZeroThreadConfigPatch as c, buildCodexNativeWebSearchThreadConfig as d, resolveCodexWebSearchPlan as f, resolveCodexAppServerRequestModelSelection as g, resolveCodexAppServerModelProvider as h, startOrResumeThread as i, buildContextEngineBinding as j, resolveCodexDynamicToolsLoading as k, buildCodexRuntimeThreadConfig as l, CODEX_NATIVE_PERSONALITY_NONE as m, buildTurnStartParams as n, assertCodexRestrictedToolSurfaceHasNoManagedHooks as o, buildDeveloperInstructions as p, refreshCodexPluginRuntimeState as q, buildCodexUserInput as r, attestCodexRestrictedToolSurfaceMcpServersDisabled as s, buildTurnCollaborationMode as t, readCodexInheritedMcpServerNames as u, resolveCodexBindingModelProviderFallback as v, filterCodexDynamicTools as w, areCodexDynamicToolFingerprintsCompatible as x, resolveReasoningEffort as y, buildCodexPluginThreadConfig as z };
|