@openclaw/codex 2026.7.2-beta.1 → 2026.7.2-beta.3
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 +1 -1
- package/dist/{app-server-policy-DVcxdse_.js → app-server-policy-DUMF2Xyr.js} +1 -1
- package/dist/{attempt-notifications-DEv9h7iC.js → attempt-notifications-Ecjyzufe.js} +3 -6
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-D6ZlGNmb.js → command-formatters-NCTL7A2q.js} +38 -3
- package/dist/{command-handlers-Cpl9fUWv.js → command-handlers-DtFbZiba.js} +70 -15
- package/dist/{compact-FnJmiPNA.js → compact-Kgy26gIM.js} +4 -5
- package/dist/{computer-use-DVzr8OP1.js → computer-use-C7x4ZaqR.js} +4 -4
- package/dist/{conversation-control-DtQ-E7mr.js → conversation-control-B0s3aIjT.js} +5 -7
- package/dist/doctor-contract-api.js +1 -1
- package/dist/{dynamic-tools-BMLoaTeG.js → dynamic-tools-D_vSVmv9.js} +851 -434
- package/dist/harness.js +11 -6
- package/dist/index.js +52 -31
- package/dist/{media-understanding-provider-D580L0P8.js → media-understanding-provider-IPNNQre6.js} +11 -12
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{models-DuKzZA6G.js → models-DD5zBdX5.js} +5 -9
- package/dist/{notification-correlation-DfaCm0mx.js → notification-correlation-C8DVQ_Bq.js} +3 -3
- package/dist/{plugin-app-cache-key-BKNjHMEs.js → plugin-app-cache-key-DJbhLYSK.js} +2 -2
- package/dist/{rate-limits-Dhp04Rqo.js → rate-limits-CqhWQ2R9.js} +9 -6
- package/dist/{request-DborTWgw.js → request-9yf9pZjf.js} +54 -8
- package/dist/{run-attempt-VPVJoYDP.js → run-attempt-BIoO8ISr.js} +637 -351
- package/dist/{config-c48K5HP9.js → session-binding-Bmt20HfB.js} +788 -3
- package/dist/{session-catalog-7H112Tr_.js → session-catalog-CA7R-hX2.js} +82 -45
- package/dist/{session-cli-B28RhCyp.js → session-cli-Bxk3-Wqz.js} +2 -2
- package/dist/{shared-client-D4mFI9al.js → shared-client-CSXLtCLE.js} +304 -81
- package/dist/{side-question-CgJBz52s.js → side-question-DtzquMOb.js} +15 -13
- package/dist/{thread-lifecycle-BgLXzjvV.js → thread-lifecycle-CQ_9mAa1.js} +1821 -1582
- package/dist/{protocol-validators-dZQ-UTOa.js → transcript-mirror-D9rTxx2P.js} +485 -2
- package/dist/usage-CuPpFgTc.js +25 -0
- package/dist/{web-search-provider.runtime-Bs-eTn5U.js → web-search-provider.runtime-_x5XTeqh.js} +3 -3
- package/npm-shrinkwrap.json +30 -30
- package/openclaw.plugin.json +21 -6
- package/package.json +6 -6
- package/dist/capabilities-42Dfn2TV.js +0 -33
- package/dist/prompt-overlay.js +0 -15
- package/dist/protocol-BMifTfdW.js +0 -15
- package/dist/provider-catalog.js +0 -118
- package/dist/provider-discovery.js +0 -34
- package/dist/provider.js +0 -307
- package/dist/session-binding-BMfX1OX-.js +0 -786
- package/dist/transcript-mirror-DhLwFIL4.js +0 -485
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-
|
|
5
|
-
import { O as CodexAppServerRpcError, P as isCodexAppServerPrewriteRequestCancellationError, d as retireSharedCodexAppServerClientIfCurrent, j as isCodexAppServerConnectionClosedError, k as getCodexAppServerClientInstanceId, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, o as isCodexAppServerStartSelectionChangedError, t as clearSharedCodexAppServerClientIfCurrent } from "./shared-client-D4mFI9al.js";
|
|
6
|
-
import { a as isCodexAppServerNativeAuthProfile, c as sessionBindingIdentity, i as hashCodexAppServerBindingFingerprint, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider, s as reclaimCurrentCodexSessionGeneration } from "./session-binding-BMfX1OX-.js";
|
|
7
|
-
import { CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "./prompt-overlay.js";
|
|
8
|
-
import { isMaxReasoningCodexModel, isModernCodexModel, readCodexSupportedReasoningEfforts, resolveCodexFallbackReasoningEfforts, resolveCodexSupportedReasoningEffort } from "./provider.js";
|
|
9
|
-
import { a as projectBoundedCodexThreadHistory } from "./transcript-mirror-DhLwFIL4.js";
|
|
1
|
+
import { G as isCodexAppServerPrewriteRequestCancellationError, H as isCodexAppServerConnectionClosedError, R as CodexAppServerRpcError, X as flattenCodexDynamicToolFunctions, Z as isJsonObject, d as retireSharedCodexAppServerClientIfCurrent, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, o as isCodexAppServerStartSelectionChangedError, t as clearSharedCodexAppServerClientIfCurrent, z as getCodexAppServerClientInstanceId } from "./shared-client-CSXLtCLE.js";
|
|
2
|
+
import { T as resolveCodexPluginsPolicy, a as isCodexAppServerNativeAuthProfile, c as sessionBindingIdentity, d as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, h as codexSandboxPolicyForTurn, i as hashCodexAppServerBindingFingerprint, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider, s as reclaimCurrentCodexSessionGeneration, u as CODEX_PLUGINS_MARKETPLACE_NAME } from "./session-binding-Bmt20HfB.js";
|
|
3
|
+
import { a as projectBoundedCodexThreadHistory, d as assertCodexThreadStartResponse, l as assertCodexThreadForkResponse, u as assertCodexThreadResumeResponse } from "./transcript-mirror-D9rTxx2P.js";
|
|
4
|
+
import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-DJbhLYSK.js";
|
|
10
5
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
11
6
|
import * as crypto$1 from "node:crypto";
|
|
12
7
|
import crypto from "node:crypto";
|
|
13
8
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
9
|
import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
10
|
+
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
15
11
|
import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
12
|
+
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
16
13
|
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
17
|
-
import {
|
|
14
|
+
import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
|
|
18
15
|
import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
|
|
19
|
-
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
20
16
|
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
21
17
|
/**
|
|
22
18
|
* Reads Codex plugin marketplace state and app inventory to decide which
|
|
@@ -35,13 +31,13 @@ async function readCodexPluginInventory(params) {
|
|
|
35
31
|
}]
|
|
36
32
|
};
|
|
37
33
|
const appInventory = readCachedAppInventory(params);
|
|
38
|
-
const curatedListed = await params
|
|
34
|
+
const curatedListed = await listCodexPluginMetadata(params, "curated-global", {});
|
|
39
35
|
const shouldListWorkspacePlugins = policy.pluginPolicies.some((pluginPolicy) => pluginPolicy.enabled && pluginPolicy.marketplaceName === "workspace-directory");
|
|
40
36
|
let workspaceListResult;
|
|
41
37
|
if (shouldListWorkspacePlugins) try {
|
|
42
38
|
workspaceListResult = {
|
|
43
39
|
kind: "listed",
|
|
44
|
-
response: await params
|
|
40
|
+
response: await listCodexPluginMetadata(params, "workspace-directory", {
|
|
45
41
|
cwds: [],
|
|
46
42
|
marketplaceKinds: [CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME]
|
|
47
43
|
})
|
|
@@ -138,6 +134,31 @@ function pluginReadParams(marketplace, pluginName) {
|
|
|
138
134
|
pluginName
|
|
139
135
|
};
|
|
140
136
|
}
|
|
137
|
+
/** Returns configured plugin keys whose current metadata may still recover. */
|
|
138
|
+
function resolveRecoverableCodexPluginConfigKeys(params) {
|
|
139
|
+
return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
|
|
140
|
+
pluginPolicy,
|
|
141
|
+
metadataCache: params.metadataCache,
|
|
142
|
+
appCacheKey: params.appCacheKey
|
|
143
|
+
})).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
|
|
144
|
+
}
|
|
145
|
+
async function listCodexPluginMetadata(params, queryKind, requestParams) {
|
|
146
|
+
if (!params.metadataCache || !params.appCacheKey || queryKind === "workspace-directory") return await params.request("plugin/list", requestParams);
|
|
147
|
+
return (await params.metadataCache.load({
|
|
148
|
+
appCacheKey: params.appCacheKey,
|
|
149
|
+
queryKind,
|
|
150
|
+
requestParams,
|
|
151
|
+
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
152
|
+
cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
153
|
+
})).response;
|
|
154
|
+
}
|
|
155
|
+
function isSettledMissingPluginPolicy(params) {
|
|
156
|
+
const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "workspace-directory" : "curated-global";
|
|
157
|
+
const listed = params.metadataCache.read(params.appCacheKey, queryKind)?.response;
|
|
158
|
+
if (!listed) return false;
|
|
159
|
+
if (queryKind === "workspace-directory") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
160
|
+
return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
161
|
+
}
|
|
141
162
|
function readCachedAppInventory(params) {
|
|
142
163
|
if (!params.appCache || !params.appCacheKey) return;
|
|
143
164
|
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
@@ -237,6 +258,7 @@ function marketplaceRef(marketplace, name) {
|
|
|
237
258
|
...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
|
|
238
259
|
};
|
|
239
260
|
}
|
|
261
|
+
/** True for either supported OpenAI curated marketplace wire name. */
|
|
240
262
|
function isOpenAiCuratedMarketplace(marketplace) {
|
|
241
263
|
return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME;
|
|
242
264
|
}
|
|
@@ -245,10 +267,10 @@ function isOpenAiCuratedMarketplace(marketplace) {
|
|
|
245
267
|
/** Activates a curated plugin or rejects a workspace plugin that is not already active. */
|
|
246
268
|
async function ensureCodexPluginActivation(params) {
|
|
247
269
|
if (params.identity.marketplaceName === "workspace-directory") return activationFailure(params.identity, "disabled", { message: "workspace-directory plugins must be installed and enabled outside OpenClaw before use." });
|
|
248
|
-
const listed = await params
|
|
270
|
+
const listed = await listCuratedCodexPluginMetadata(params);
|
|
249
271
|
const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName);
|
|
250
272
|
if (!resolved) {
|
|
251
|
-
if (!listed.marketplaces.some(
|
|
273
|
+
if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
|
|
252
274
|
return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
|
|
253
275
|
}
|
|
254
276
|
if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
|
|
@@ -260,6 +282,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
260
282
|
diagnostics: []
|
|
261
283
|
};
|
|
262
284
|
const installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, resolved.marketplace.remoteMarketplaceName && resolved.summary.remotePluginId ? resolved.summary.remotePluginId : params.identity.pluginName));
|
|
285
|
+
if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
|
|
263
286
|
const refreshDiagnostics = [];
|
|
264
287
|
let refreshFailed = false;
|
|
265
288
|
try {
|
|
@@ -267,6 +290,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
267
290
|
request: params.request,
|
|
268
291
|
appCache: params.appCache,
|
|
269
292
|
appCacheKey: params.appCacheKey,
|
|
293
|
+
metadataCache: params.metadataCache,
|
|
270
294
|
targetAppIds: params.targetAppIds
|
|
271
295
|
});
|
|
272
296
|
refreshDiagnostics.push(...refreshResult.diagnostics);
|
|
@@ -288,7 +312,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
288
312
|
/** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
|
|
289
313
|
async function refreshCodexPluginRuntimeState(params) {
|
|
290
314
|
const diagnostics = [];
|
|
291
|
-
await params
|
|
315
|
+
await listCuratedCodexPluginMetadata(params);
|
|
292
316
|
await params.request("skills/list", {
|
|
293
317
|
cwds: [],
|
|
294
318
|
forceReload: true
|
|
@@ -315,6 +339,17 @@ async function refreshCodexPluginRuntimeState(params) {
|
|
|
315
339
|
}
|
|
316
340
|
return { diagnostics };
|
|
317
341
|
}
|
|
342
|
+
async function listCuratedCodexPluginMetadata(params) {
|
|
343
|
+
const requestParams = {};
|
|
344
|
+
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
|
|
345
|
+
return (await params.metadataCache.load({
|
|
346
|
+
appCacheKey: params.appCacheKey,
|
|
347
|
+
queryKind: "curated-global",
|
|
348
|
+
requestParams,
|
|
349
|
+
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
350
|
+
cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
351
|
+
})).response;
|
|
352
|
+
}
|
|
318
353
|
function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
|
|
319
354
|
return {
|
|
320
355
|
identity,
|
|
@@ -345,6 +380,20 @@ function buildCodexPluginThreadConfigInputFingerprint(params) {
|
|
|
345
380
|
appCacheKey: params.appCacheKey ?? null
|
|
346
381
|
});
|
|
347
382
|
}
|
|
383
|
+
/** Builds the deny-all app patch used when plugin discovery exceeds its turn budget. */
|
|
384
|
+
function buildCodexPluginThreadConfigTimeoutFallback(params) {
|
|
385
|
+
return {
|
|
386
|
+
...emptyPluginThreadConfig({
|
|
387
|
+
enabled: true,
|
|
388
|
+
inputFingerprint: buildCodexPluginThreadConfigInputFingerprint(params),
|
|
389
|
+
configPatch: buildDisabledAppsConfigPatch()
|
|
390
|
+
}),
|
|
391
|
+
diagnostics: [{
|
|
392
|
+
code: "plugin_config_timeout",
|
|
393
|
+
message: params.message
|
|
394
|
+
}]
|
|
395
|
+
};
|
|
396
|
+
}
|
|
348
397
|
/** Builds the Codex apps config patch and policy context for a native thread. */
|
|
349
398
|
async function buildCodexPluginThreadConfig(params) {
|
|
350
399
|
const appCache = params.appCache ?? defaultCodexAppInventoryCache;
|
|
@@ -364,6 +413,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
364
413
|
request: params.request,
|
|
365
414
|
appCache,
|
|
366
415
|
appCacheKey: params.appCacheKey,
|
|
416
|
+
metadataCache: params.metadataCache,
|
|
367
417
|
nowMs: params.nowMs,
|
|
368
418
|
suppressAppInventoryRefresh: true
|
|
369
419
|
}) : emptyCodexPluginInventory(policy);
|
|
@@ -380,6 +430,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
380
430
|
request: params.request,
|
|
381
431
|
appCache,
|
|
382
432
|
appCacheKey: params.appCacheKey,
|
|
433
|
+
metadataCache: params.metadataCache,
|
|
383
434
|
nowMs: params.nowMs
|
|
384
435
|
});
|
|
385
436
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -396,6 +447,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
396
447
|
request: params.request,
|
|
397
448
|
appCache,
|
|
398
449
|
appCacheKey: params.appCacheKey,
|
|
450
|
+
metadataCache: params.metadataCache,
|
|
399
451
|
targetAppIds: record.ownedAppIds
|
|
400
452
|
});
|
|
401
453
|
activationResults.push(activation);
|
|
@@ -419,6 +471,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
419
471
|
request: params.request,
|
|
420
472
|
appCache,
|
|
421
473
|
appCacheKey: params.appCacheKey,
|
|
474
|
+
metadataCache: params.metadataCache,
|
|
422
475
|
nowMs: params.nowMs
|
|
423
476
|
});
|
|
424
477
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -438,6 +491,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
438
491
|
request: params.request,
|
|
439
492
|
appCache,
|
|
440
493
|
appCacheKey: params.appCacheKey,
|
|
494
|
+
metadataCache: params.metadataCache,
|
|
441
495
|
nowMs: params.nowMs
|
|
442
496
|
});
|
|
443
497
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -755,129 +809,6 @@ function stableStringify(value) {
|
|
|
755
809
|
return JSON.stringify(value);
|
|
756
810
|
}
|
|
757
811
|
//#endregion
|
|
758
|
-
//#region extensions/codex/src/app-server/attempt-client-cleanup.ts
|
|
759
|
-
/**
|
|
760
|
-
* Best-effort cleanup helpers for Codex app-server startup attempts and turns.
|
|
761
|
-
*/
|
|
762
|
-
/** Timeout for best-effort app-server turn interruption during cleanup. */
|
|
763
|
-
const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
764
|
-
/** Timeout for best-effort thread unsubscribe during cleanup. */
|
|
765
|
-
const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
|
|
766
|
-
/** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
|
|
767
|
-
var CodexAppServerUnsafeSubscriptionError = class extends Error {
|
|
768
|
-
constructor(message, options) {
|
|
769
|
-
super(message, options);
|
|
770
|
-
this.name = "CodexAppServerUnsafeSubscriptionError";
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
function isCodexAppServerUnsafeSubscriptionError(error) {
|
|
774
|
-
return error instanceof CodexAppServerUnsafeSubscriptionError;
|
|
775
|
-
}
|
|
776
|
-
/** Asserts Codex resumed the exact thread this attempt subscribed to. */
|
|
777
|
-
function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
|
|
778
|
-
if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
|
|
779
|
-
}
|
|
780
|
-
async function closeClientAndWaitIfAvailable(client) {
|
|
781
|
-
const closeable = client;
|
|
782
|
-
if (typeof closeable.closeAndWait === "function") {
|
|
783
|
-
await closeable.closeAndWait();
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
closeable.close?.();
|
|
787
|
-
}
|
|
788
|
-
async function closeCodexStartupClientBestEffort(client) {
|
|
789
|
-
if (!client) return;
|
|
790
|
-
const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
|
|
791
|
-
if (unclaimedSharedClient.closed) {
|
|
792
|
-
await closeClientAndWaitIfAvailable(client);
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
if (unclaimedSharedClient.found) {
|
|
796
|
-
if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
|
|
800
|
-
if (retiredSharedClient) {
|
|
801
|
-
if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
if (clearSharedCodexAppServerClientIfCurrent(client)) {
|
|
805
|
-
await closeClientAndWaitIfAvailable(client);
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
await closeClientAndWaitIfAvailable(client);
|
|
809
|
-
}
|
|
810
|
-
/** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
|
|
811
|
-
function interruptCodexTurnBestEffort(client, params) {
|
|
812
|
-
const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
|
|
813
|
-
const requestParams = {
|
|
814
|
-
threadId: params.threadId,
|
|
815
|
-
turnId: params.turnId
|
|
816
|
-
};
|
|
817
|
-
try {
|
|
818
|
-
const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
|
|
819
|
-
Promise.resolve(interrupt).catch((error) => {
|
|
820
|
-
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
821
|
-
});
|
|
822
|
-
} catch (error) {
|
|
823
|
-
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
/** Unsubscribes from a thread while swallowing cleanup-only failures. */
|
|
827
|
-
async function unsubscribeCodexThreadBestEffort(client, params) {
|
|
828
|
-
try {
|
|
829
|
-
await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
|
|
830
|
-
return true;
|
|
831
|
-
} catch (error) {
|
|
832
|
-
embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
|
|
833
|
-
threadId: params.threadId,
|
|
834
|
-
error
|
|
835
|
-
});
|
|
836
|
-
return false;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* Retires the shared client after a timed-out turn so later runs do not reuse a
|
|
841
|
-
* potentially wedged app-server connection.
|
|
842
|
-
*/
|
|
843
|
-
async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
|
|
844
|
-
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
|
|
845
|
-
const detachedSharedClient = Boolean(retiredSharedClient);
|
|
846
|
-
if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
|
|
847
|
-
interruptCodexTurnBestEffort(client, {
|
|
848
|
-
threadId: params.threadId,
|
|
849
|
-
turnId: params.turnId,
|
|
850
|
-
timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
|
|
851
|
-
});
|
|
852
|
-
await unsubscribeCodexThreadBestEffort(client, {
|
|
853
|
-
threadId: params.threadId,
|
|
854
|
-
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
let closedClient = retiredSharedClient?.closed ?? false;
|
|
858
|
-
if (!detachedSharedClient) {
|
|
859
|
-
const close = client.close;
|
|
860
|
-
if (typeof close === "function") try {
|
|
861
|
-
close.call(client);
|
|
862
|
-
closedClient = true;
|
|
863
|
-
} catch (error) {
|
|
864
|
-
embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
|
|
865
|
-
threadId: params.threadId,
|
|
866
|
-
turnId: params.turnId,
|
|
867
|
-
error
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
|
|
872
|
-
threadId: params.threadId,
|
|
873
|
-
turnId: params.turnId,
|
|
874
|
-
reason: params.reason,
|
|
875
|
-
detachedSharedClient,
|
|
876
|
-
closedClient,
|
|
877
|
-
activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
//#endregion
|
|
881
812
|
//#region extensions/codex/src/app-server/context-engine-projection.ts
|
|
882
813
|
const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
|
|
883
814
|
const CONTEXT_OPEN = "<conversation_context>";
|
|
@@ -1137,961 +1068,1357 @@ function truncateOlderContext(text, maxChars) {
|
|
|
1137
1068
|
return `${marker}${sliceUtf16Safe(text, -tailChars).trimStart()}`;
|
|
1138
1069
|
}
|
|
1139
1070
|
//#endregion
|
|
1140
|
-
//#region extensions/codex/src/app-server/
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1071
|
+
//#region extensions/codex/src/app-server/thread-context-engine.ts
|
|
1072
|
+
function buildContextEngineBinding(params, projection) {
|
|
1073
|
+
const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
|
|
1074
|
+
const engineId = contextEngine?.info?.id?.trim();
|
|
1075
|
+
if (!contextEngine || !engineId) return;
|
|
1076
|
+
return {
|
|
1077
|
+
schemaVersion: 1,
|
|
1078
|
+
engineId,
|
|
1079
|
+
policyFingerprint: JSON.stringify({
|
|
1080
|
+
schemaVersion: 1,
|
|
1081
|
+
engineId,
|
|
1082
|
+
engineVersion: contextEngine.info.version,
|
|
1083
|
+
ownsCompaction: contextEngine.info.ownsCompaction === true,
|
|
1084
|
+
turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
|
|
1085
|
+
citationsMode: resolveContextEngineCitationsMode(params.config),
|
|
1086
|
+
contextTokenBudget: params.contextTokenBudget,
|
|
1087
|
+
projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
|
|
1088
|
+
contextTokenBudget: params.contextTokenBudget,
|
|
1089
|
+
reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
|
|
1090
|
+
})
|
|
1091
|
+
}),
|
|
1092
|
+
projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
|
|
1093
|
+
};
|
|
1163
1094
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1095
|
+
function buildContextEngineProjectionBinding(projection) {
|
|
1096
|
+
return {
|
|
1097
|
+
schemaVersion: 1,
|
|
1098
|
+
mode: "thread_bootstrap",
|
|
1099
|
+
epoch: projection.epoch,
|
|
1100
|
+
fingerprint: projection.fingerprint
|
|
1101
|
+
};
|
|
1167
1102
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
1103
|
+
function isContextEngineBindingCompatible(previous, next) {
|
|
1104
|
+
return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
|
|
1171
1105
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
return
|
|
1106
|
+
function areContextEngineProjectionBindingsCompatible(previous, next) {
|
|
1107
|
+
if (!next) return previous === void 0;
|
|
1108
|
+
return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
|
|
1175
1109
|
}
|
|
1176
|
-
function
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
return
|
|
1110
|
+
function resolveContextEngineCitationsMode(config) {
|
|
1111
|
+
const rootConfig = isUnknownRecord(config) ? config : void 0;
|
|
1112
|
+
const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
|
|
1113
|
+
return isJsonConfigValue(citations) ? citations : void 0;
|
|
1180
1114
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
return shouldDisableCodexToolSearchForModel(modelId);
|
|
1115
|
+
function isUnknownRecord(value) {
|
|
1116
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1184
1117
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1118
|
+
function isJsonConfigValue(value) {
|
|
1119
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1120
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1121
|
+
if (Array.isArray(value)) return value.every(isJsonConfigValue);
|
|
1122
|
+
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
|
|
1188
1123
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
return
|
|
1124
|
+
//#endregion
|
|
1125
|
+
//#region extensions/codex/src/app-server/thread-fingerprints.ts
|
|
1126
|
+
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
1127
|
+
return fingerprintDynamicTools(dynamicTools);
|
|
1193
1128
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
const loading = resolveCodexDynamicToolsLoadingForModel(config, modelId, env);
|
|
1197
|
-
return loading === "searchable" && options.connectionClass === "remote" ? "direct" : loading;
|
|
1129
|
+
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
1130
|
+
return legacyFingerprintDynamicTools(dynamicTools);
|
|
1198
1131
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const excludes = /* @__PURE__ */ new Set();
|
|
1202
|
-
if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) excludes.add(name);
|
|
1203
|
-
for (const name of config.codexDynamicToolsExclude ?? []) {
|
|
1204
|
-
const trimmed = normalizeCodexDynamicToolName(name);
|
|
1205
|
-
if (trimmed) excludes.add(trimmed);
|
|
1206
|
-
}
|
|
1207
|
-
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
1132
|
+
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
1133
|
+
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
1208
1134
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
/**
|
|
1212
|
-
* Sanitizes inline image payloads mirrored through Codex history so invalid
|
|
1213
|
-
* base64 data becomes readable text instead of poisoning replayed transcripts.
|
|
1214
|
-
*/
|
|
1215
|
-
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
1216
|
-
/** Validates and normalizes an inline image data URL for Codex history payloads. */
|
|
1217
|
-
function sanitizeInlineImageDataUrl$1(imageUrl) {
|
|
1218
|
-
return sanitizeInlineImageDataUrl(imageUrl);
|
|
1135
|
+
function fingerprintDynamicTools(dynamicTools) {
|
|
1136
|
+
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
1219
1137
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
1138
|
+
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
1139
|
+
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
1223
1140
|
}
|
|
1224
|
-
function
|
|
1225
|
-
|
|
1226
|
-
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
|
|
1227
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
|
|
1228
|
-
if (!imageUrl) return {
|
|
1229
|
-
type: "text",
|
|
1230
|
-
text: invalidInlineImageText(label)
|
|
1231
|
-
};
|
|
1232
|
-
const commaIndex = imageUrl.indexOf(",");
|
|
1233
|
-
const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
|
|
1234
|
-
return {
|
|
1235
|
-
...record,
|
|
1236
|
-
mimeType: mime,
|
|
1237
|
-
data: imageUrl.slice(commaIndex + 1)
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
1241
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
|
|
1242
|
-
return imageUrl ? {
|
|
1243
|
-
...record,
|
|
1244
|
-
imageUrl
|
|
1245
|
-
} : {
|
|
1246
|
-
type: "inputText",
|
|
1247
|
-
text: invalidInlineImageText(label)
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
1251
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
|
|
1252
|
-
return imageUrl ? {
|
|
1253
|
-
...record,
|
|
1254
|
-
image_url: imageUrl
|
|
1255
|
-
} : {
|
|
1256
|
-
type: "input_text",
|
|
1257
|
-
text: invalidInlineImageText(label)
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1141
|
+
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1142
|
+
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
1260
1143
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
if (
|
|
1144
|
+
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1145
|
+
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
|
|
1146
|
+
}
|
|
1147
|
+
function redactUserMcpServersFingerprintSecrets(value) {
|
|
1148
|
+
if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
|
|
1149
|
+
if (!value || typeof value !== "object") return value;
|
|
1267
1150
|
const next = {};
|
|
1268
|
-
for (const [key,
|
|
1151
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1152
|
+
if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1153
|
+
next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
next[key] = redactUserMcpServersFingerprintSecrets(entry);
|
|
1157
|
+
}
|
|
1269
1158
|
return next;
|
|
1270
1159
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
1274
|
-
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
1275
|
-
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
1276
|
-
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
1160
|
+
function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
1161
|
+
return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
|
|
1277
1162
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
if (
|
|
1293
|
-
|
|
1294
|
-
try {
|
|
1295
|
-
await params.abandonClient();
|
|
1296
|
-
} catch (abandonError) {
|
|
1297
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
|
|
1298
|
-
}
|
|
1299
|
-
if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
|
|
1300
|
-
throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
|
|
1163
|
+
function fingerprintJsonObject(value) {
|
|
1164
|
+
return JSON.stringify(stabilizeJsonValue(value));
|
|
1165
|
+
}
|
|
1166
|
+
function fingerprintEnvironmentSelection(environments) {
|
|
1167
|
+
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
1168
|
+
}
|
|
1169
|
+
function fingerprintDynamicToolSpec(tool) {
|
|
1170
|
+
return stabilizeDynamicToolFingerprintValue(tool);
|
|
1171
|
+
}
|
|
1172
|
+
function stabilizeDynamicToolFingerprintValue(value) {
|
|
1173
|
+
if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
|
|
1174
|
+
if (!isJsonObject(value)) return value;
|
|
1175
|
+
const stable = {};
|
|
1176
|
+
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
1177
|
+
if (key === "description") continue;
|
|
1178
|
+
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
|
1301
1179
|
}
|
|
1302
|
-
return
|
|
1180
|
+
return stable;
|
|
1303
1181
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
function normalizeOptionalString$1(value) {
|
|
1311
|
-
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
1182
|
+
function stabilizeJsonValue(value) {
|
|
1183
|
+
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
1184
|
+
if (!isJsonObject(value)) return value;
|
|
1185
|
+
const stable = {};
|
|
1186
|
+
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
1187
|
+
return stable;
|
|
1312
1188
|
}
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
const normalized = [...new Set(value.map(normalizeOptionalString$1).filter((entry) => Boolean(entry)))];
|
|
1316
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
1189
|
+
function readActiveCodexTurnIds(thread) {
|
|
1190
|
+
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
1317
1191
|
}
|
|
1318
|
-
function
|
|
1319
|
-
|
|
1192
|
+
function readActiveCodexTurnIdsFromResume(response) {
|
|
1193
|
+
const pagedTurns = response.initialTurnsPage?.data;
|
|
1194
|
+
return readActiveCodexTurnIds(Array.isArray(pagedTurns) ? { turns: pagedTurns } : response.thread);
|
|
1320
1195
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1196
|
+
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
1197
|
+
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
1198
|
+
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
1199
|
+
return !previous || previous === next || previous === nextLegacy;
|
|
1323
1200
|
}
|
|
1324
|
-
function
|
|
1325
|
-
|
|
1326
|
-
const threadConfig = {
|
|
1327
|
-
"features.standalone_web_search": false,
|
|
1328
|
-
web_search: nativeConfig?.mode === "live" ? "live" : "cached"
|
|
1329
|
-
};
|
|
1330
|
-
const allowedDomains = normalizeUniqueStrings(nativeConfig?.allowedDomains);
|
|
1331
|
-
if (allowedDomains) threadConfig["tools.web_search.allowed_domains"] = allowedDomains;
|
|
1332
|
-
if (nativeConfig?.contextSize) threadConfig["tools.web_search.context_size"] = nativeConfig.contextSize;
|
|
1333
|
-
const location = nativeConfig?.userLocation;
|
|
1334
|
-
const country = normalizeOptionalString$1(location?.country);
|
|
1335
|
-
const region = normalizeOptionalString$1(location?.region);
|
|
1336
|
-
const city = normalizeOptionalString$1(location?.city);
|
|
1337
|
-
const timezone = normalizeOptionalString$1(location?.timezone);
|
|
1338
|
-
if (country) threadConfig["tools.web_search.location.country"] = country;
|
|
1339
|
-
if (region) threadConfig["tools.web_search.location.region"] = region;
|
|
1340
|
-
if (city) threadConfig["tools.web_search.location.city"] = city;
|
|
1341
|
-
if (timezone) threadConfig["tools.web_search.location.timezone"] = timezone;
|
|
1342
|
-
return threadConfig;
|
|
1201
|
+
function areUserMcpServersFingerprintsCompatible(params) {
|
|
1202
|
+
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
1343
1203
|
}
|
|
1344
|
-
function
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const nativeProviderSupportsSearch = params.nativeProviderWebSearchSupport === void 0 || params.nativeProviderWebSearchSupport === "supported";
|
|
1353
|
-
if (!(params.nativeToolSurfaceEnabled !== false && nativeProviderSupportsSearch && nativeConfig?.enabled !== false && !hasManagedSearchProvider(params.config))) {
|
|
1354
|
-
if (!managedSearchExplicit && hasNativeDomainRestrictions(params.config)) return {
|
|
1355
|
-
kind: "disabled",
|
|
1356
|
-
suppressManagedWebSearch: true,
|
|
1357
|
-
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1358
|
-
};
|
|
1359
|
-
return {
|
|
1360
|
-
kind: "managed",
|
|
1361
|
-
suppressManagedWebSearch: false,
|
|
1362
|
-
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
return {
|
|
1366
|
-
kind: "native-hosted",
|
|
1367
|
-
suppressManagedWebSearch: true,
|
|
1368
|
-
threadConfig: buildCodexNativeWebSearchThreadConfig(params.config)
|
|
1369
|
-
};
|
|
1204
|
+
function shouldStartTransientNoToolThread(params) {
|
|
1205
|
+
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
1206
|
+
}
|
|
1207
|
+
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
1208
|
+
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
1209
|
+
}
|
|
1210
|
+
function compareJsonFingerprint(left, right) {
|
|
1211
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1370
1212
|
}
|
|
1371
1213
|
//#endregion
|
|
1372
|
-
//#region extensions/codex/src/app-server/
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1214
|
+
//#region extensions/codex/src/app-server/attempt-client-cleanup.ts
|
|
1215
|
+
/**
|
|
1216
|
+
* Best-effort cleanup helpers for Codex app-server startup attempts and turns.
|
|
1217
|
+
*/
|
|
1218
|
+
/** Timeout for best-effort app-server turn interruption during cleanup. */
|
|
1219
|
+
const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
1220
|
+
/** Timeout for best-effort thread unsubscribe during cleanup. */
|
|
1221
|
+
const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
|
|
1222
|
+
/** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
|
|
1223
|
+
var CodexAppServerUnsafeSubscriptionError = class extends Error {
|
|
1224
|
+
constructor(message, options) {
|
|
1225
|
+
super(message, options);
|
|
1226
|
+
this.name = "CodexAppServerUnsafeSubscriptionError";
|
|
1377
1227
|
}
|
|
1378
1228
|
};
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1229
|
+
function isCodexAppServerUnsafeSubscriptionError(error) {
|
|
1230
|
+
return error instanceof CodexAppServerUnsafeSubscriptionError;
|
|
1231
|
+
}
|
|
1232
|
+
/** Asserts Codex resumed the exact thread this attempt subscribed to. */
|
|
1233
|
+
function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
|
|
1234
|
+
if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
|
|
1235
|
+
}
|
|
1236
|
+
async function closeClientAndWaitIfAvailable(client) {
|
|
1237
|
+
const closeable = client;
|
|
1238
|
+
if (typeof closeable.closeAndWait === "function") {
|
|
1239
|
+
await closeable.closeAndWait();
|
|
1240
|
+
return;
|
|
1383
1241
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1242
|
+
closeable.close?.();
|
|
1243
|
+
}
|
|
1244
|
+
async function closeCodexStartupClientBestEffort(client) {
|
|
1245
|
+
if (!client) return;
|
|
1246
|
+
const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
|
|
1247
|
+
if (unclaimedSharedClient.closed) {
|
|
1248
|
+
await closeClientAndWaitIfAvailable(client);
|
|
1249
|
+
return;
|
|
1389
1250
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
constructor() {
|
|
1394
|
-
super("Codex session became active in another runner; wait for it to finish before continuing");
|
|
1395
|
-
this.name = "CodexAdoptedThreadActiveError";
|
|
1251
|
+
if (unclaimedSharedClient.found) {
|
|
1252
|
+
if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
|
|
1253
|
+
return;
|
|
1396
1254
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
"features.code_mode_only": false
|
|
1408
|
-
};
|
|
1409
|
-
const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
|
|
1410
|
-
const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
|
|
1411
|
-
const CODEX_RING_ZERO_THREAD_CONFIG = {
|
|
1412
|
-
"features.apps": false,
|
|
1413
|
-
"features.current_time_reminder": false,
|
|
1414
|
-
"features.deferred_executor": false,
|
|
1415
|
-
"features.enable_fanout": false,
|
|
1416
|
-
"features.goals": false,
|
|
1417
|
-
"features.hooks": false,
|
|
1418
|
-
"features.image_generation": false,
|
|
1419
|
-
"features.memories": false,
|
|
1420
|
-
"features.multi_agent": false,
|
|
1421
|
-
"features.multi_agent_v2": false,
|
|
1422
|
-
"features.plugins": false,
|
|
1423
|
-
"features.standalone_web_search": false,
|
|
1424
|
-
"features.token_budget": false,
|
|
1425
|
-
"orchestrator.mcp.enabled": false,
|
|
1426
|
-
"orchestrator.skills.enabled": false,
|
|
1427
|
-
"tools.experimental_request_user_input.enabled": false,
|
|
1428
|
-
hooks: {
|
|
1429
|
-
PreToolUse: [],
|
|
1430
|
-
PermissionRequest: [],
|
|
1431
|
-
PostToolUse: [],
|
|
1432
|
-
PreCompact: [],
|
|
1433
|
-
PostCompact: [],
|
|
1434
|
-
SessionStart: [],
|
|
1435
|
-
UserPromptSubmit: [],
|
|
1436
|
-
SubagentStart: [],
|
|
1437
|
-
SubagentStop: [],
|
|
1438
|
-
Stop: []
|
|
1439
|
-
},
|
|
1440
|
-
project_doc_max_bytes: 0,
|
|
1441
|
-
notify: [],
|
|
1442
|
-
web_search: "disabled"
|
|
1443
|
-
};
|
|
1444
|
-
const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
|
|
1445
|
-
"apps",
|
|
1446
|
-
"code_mode",
|
|
1447
|
-
"code_mode_only",
|
|
1448
|
-
"current_time_reminder",
|
|
1449
|
-
"deferred_executor",
|
|
1450
|
-
"enable_fanout",
|
|
1451
|
-
"goals",
|
|
1452
|
-
"hooks",
|
|
1453
|
-
"image_generation",
|
|
1454
|
-
"memories",
|
|
1455
|
-
"multi_agent",
|
|
1456
|
-
"multi_agent_v2",
|
|
1457
|
-
"plugins",
|
|
1458
|
-
"standalone_web_search",
|
|
1459
|
-
"token_budget"
|
|
1460
|
-
]);
|
|
1461
|
-
const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
1462
|
-
"mdm",
|
|
1463
|
-
"system",
|
|
1464
|
-
"enterpriseManaged",
|
|
1465
|
-
"user",
|
|
1466
|
-
"project",
|
|
1467
|
-
"sessionFlags"
|
|
1468
|
-
]);
|
|
1469
|
-
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
|
|
1470
|
-
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
|
|
1471
|
-
function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
|
|
1472
|
-
const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
|
|
1473
|
-
const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
|
|
1474
|
-
return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
|
|
1475
|
-
}
|
|
1476
|
-
function formatCodexThreadLifecycleTimingSummary(params) {
|
|
1477
|
-
const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
|
|
1478
|
-
return `[trace:codex-app-server] thread lifecycle: runId=${params.runId} sessionId=${params.sessionId} sessionKey=${params.sessionKey ?? "unknown"} action=${params.action} totalMs=${params.summary.totalMs} stages=${spans}`;
|
|
1255
|
+
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
|
|
1256
|
+
if (retiredSharedClient) {
|
|
1257
|
+
if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (clearSharedCodexAppServerClientIfCurrent(client)) {
|
|
1261
|
+
await closeClientAndWaitIfAvailable(client);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
await closeClientAndWaitIfAvailable(client);
|
|
1479
1265
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
measureSync(_name, run) {
|
|
1487
|
-
return run();
|
|
1488
|
-
},
|
|
1489
|
-
mark() {},
|
|
1490
|
-
logSummary() {}
|
|
1266
|
+
/** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
|
|
1267
|
+
function interruptCodexTurnBestEffort(client, params) {
|
|
1268
|
+
const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
|
|
1269
|
+
const requestParams = {
|
|
1270
|
+
threadId: params.threadId,
|
|
1271
|
+
turnId: params.turnId
|
|
1491
1272
|
};
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
const toMs = (value) => Math.max(0, Math.round(value));
|
|
1497
|
-
const record = (name, spanStartedAt) => {
|
|
1498
|
-
const currentAt = now();
|
|
1499
|
-
spans.push({
|
|
1500
|
-
name,
|
|
1501
|
-
durationMs: toMs(currentAt - spanStartedAt),
|
|
1502
|
-
elapsedMs: toMs(currentAt - startedAt)
|
|
1273
|
+
try {
|
|
1274
|
+
const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
|
|
1275
|
+
Promise.resolve(interrupt).catch((error) => {
|
|
1276
|
+
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1503
1277
|
});
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
spans: spans.slice()
|
|
1508
|
-
});
|
|
1509
|
-
return {
|
|
1510
|
-
async measure(name, run) {
|
|
1511
|
-
const spanStartedAt = now();
|
|
1512
|
-
try {
|
|
1513
|
-
return await run();
|
|
1514
|
-
} finally {
|
|
1515
|
-
record(name, spanStartedAt);
|
|
1516
|
-
}
|
|
1517
|
-
},
|
|
1518
|
-
measureSync(name, run) {
|
|
1519
|
-
const spanStartedAt = now();
|
|
1520
|
-
try {
|
|
1521
|
-
return run();
|
|
1522
|
-
} finally {
|
|
1523
|
-
record(name, spanStartedAt);
|
|
1524
|
-
}
|
|
1525
|
-
},
|
|
1526
|
-
mark(name) {
|
|
1527
|
-
record(name, now());
|
|
1528
|
-
},
|
|
1529
|
-
logSummary(params) {
|
|
1530
|
-
if (didLog) return;
|
|
1531
|
-
const summary = snapshot();
|
|
1532
|
-
const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
|
|
1533
|
-
if (!shouldWarn && !log.isEnabled?.("trace")) return;
|
|
1534
|
-
didLog = true;
|
|
1535
|
-
const message = formatCodexThreadLifecycleTimingSummary({
|
|
1536
|
-
runId: params.runId,
|
|
1537
|
-
sessionId: params.sessionId,
|
|
1538
|
-
sessionKey: params.sessionKey,
|
|
1539
|
-
action: params.action,
|
|
1540
|
-
summary
|
|
1541
|
-
});
|
|
1542
|
-
const meta = {
|
|
1543
|
-
runId: params.runId,
|
|
1544
|
-
sessionId: params.sessionId,
|
|
1545
|
-
sessionKey: params.sessionKey,
|
|
1546
|
-
action: params.action,
|
|
1547
|
-
threadId: params.threadId,
|
|
1548
|
-
totalMs: summary.totalMs,
|
|
1549
|
-
spans: summary.spans
|
|
1550
|
-
};
|
|
1551
|
-
if (shouldWarn) log.warn(message, meta);
|
|
1552
|
-
else log.trace(message, meta);
|
|
1553
|
-
}
|
|
1554
|
-
};
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1280
|
+
}
|
|
1555
1281
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
...params.timing,
|
|
1566
|
-
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
1282
|
+
/** Unsubscribes from a thread while swallowing cleanup-only failures. */
|
|
1283
|
+
async function unsubscribeCodexThreadBestEffort(client, params) {
|
|
1284
|
+
try {
|
|
1285
|
+
await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
|
|
1286
|
+
return true;
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
|
|
1289
|
+
threadId: params.threadId,
|
|
1290
|
+
error
|
|
1567
1291
|
});
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
1584
|
-
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
1585
|
-
serverName,
|
|
1586
|
-
error: formatErrorMessage(error)
|
|
1587
|
-
})
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Retires the shared client after a timed-out turn so later runs do not reuse a
|
|
1297
|
+
* potentially wedged app-server connection.
|
|
1298
|
+
*/
|
|
1299
|
+
async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
|
|
1300
|
+
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
|
|
1301
|
+
const detachedSharedClient = Boolean(retiredSharedClient);
|
|
1302
|
+
if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
|
|
1303
|
+
interruptCodexTurnBestEffort(client, {
|
|
1304
|
+
threadId: params.threadId,
|
|
1305
|
+
turnId: params.turnId,
|
|
1306
|
+
timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
|
|
1588
1307
|
});
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
1593
|
-
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
1594
|
-
if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
|
|
1595
|
-
const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
1596
|
-
if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
|
|
1597
|
-
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
1598
|
-
version: 1,
|
|
1599
|
-
baseInstructions: CODEX_RING_ZERO_BASE_INSTRUCTIONS,
|
|
1600
|
-
config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
|
|
1601
|
-
}) : void 0;
|
|
1602
|
-
const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
|
|
1603
|
-
let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
|
|
1604
|
-
const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
|
|
1605
|
-
authProfileId,
|
|
1606
|
-
modelProvider,
|
|
1607
|
-
authProfileStore: params.params.authProfileStore,
|
|
1608
|
-
agentDir: params.params.agentDir,
|
|
1609
|
-
config: params.params.config
|
|
1308
|
+
await unsubscribeCodexThreadBestEffort(client, {
|
|
1309
|
+
threadId: params.threadId,
|
|
1310
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
1610
1311
|
});
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
config: params.params.config
|
|
1624
|
-
}))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
|
|
1625
|
-
}
|
|
1626
|
-
if (binding?.pendingSupervisionBranch) {
|
|
1627
|
-
const pendingBinding = binding;
|
|
1628
|
-
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
1629
|
-
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
1630
|
-
configPatch: params.finalConfigPatch,
|
|
1631
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
1632
|
-
};
|
|
1633
|
-
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
1634
|
-
return await materializePendingSupervisionBranch({
|
|
1635
|
-
client: params.client,
|
|
1636
|
-
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
1637
|
-
bindingStore: params.bindingStore,
|
|
1638
|
-
bindingIdentity,
|
|
1639
|
-
binding: pendingBinding,
|
|
1640
|
-
attempt: params.params,
|
|
1641
|
-
cwd: params.cwd,
|
|
1642
|
-
dynamicTools: params.dynamicTools,
|
|
1643
|
-
appServer: params.appServer,
|
|
1644
|
-
developerInstructions: params.developerInstructions,
|
|
1645
|
-
config,
|
|
1646
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
1647
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
1648
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
1649
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
1650
|
-
environmentSelection: params.environmentSelection,
|
|
1651
|
-
signal: params.signal,
|
|
1652
|
-
throwIfAborted,
|
|
1653
|
-
lifecycleTiming,
|
|
1654
|
-
normalizeBindingModelProvider,
|
|
1655
|
-
bindingPatch: {
|
|
1656
|
-
cwd: params.cwd,
|
|
1657
|
-
authProfileId: void 0,
|
|
1658
|
-
preserveNativeModel: true,
|
|
1659
|
-
dynamicToolsFingerprint,
|
|
1660
|
-
dynamicToolsContainDeferred,
|
|
1661
|
-
webSearchThreadConfigFingerprint,
|
|
1662
|
-
userMcpServersFingerprint,
|
|
1663
|
-
mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
|
|
1664
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
1665
|
-
networkProxyConfigFingerprint,
|
|
1666
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
1667
|
-
appServerRuntimeFingerprint: buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir),
|
|
1668
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
1669
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
1670
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
1671
|
-
contextEngine: contextEngineBinding,
|
|
1672
|
-
environmentSelectionFingerprint,
|
|
1673
|
-
conversationSourceTransferComplete: true
|
|
1674
|
-
}
|
|
1675
|
-
});
|
|
1676
|
-
}
|
|
1677
|
-
const clearCurrentBinding = async (operation) => {
|
|
1678
|
-
const current = binding;
|
|
1679
|
-
if (!current?.threadId) return;
|
|
1680
|
-
assertCodexBindingMayBeReplaced(current, operation);
|
|
1681
|
-
if (!await params.bindingStore.mutate(bindingIdentity, {
|
|
1682
|
-
kind: "clear",
|
|
1683
|
-
threadId: current.threadId
|
|
1684
|
-
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
1685
|
-
binding = void 0;
|
|
1686
|
-
};
|
|
1687
|
-
if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
|
|
1688
|
-
embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
|
|
1689
|
-
await clearCurrentBinding("rotating a ring-zero thread binding");
|
|
1690
|
-
}
|
|
1691
|
-
if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
|
|
1692
|
-
connectionClass: params.appServer.connectionClass,
|
|
1693
|
-
current: binding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
1694
|
-
binding: binding.appServerRuntimeFingerprint
|
|
1695
|
-
})) {
|
|
1696
|
-
embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
|
|
1697
|
-
threadId: binding.threadId,
|
|
1698
|
-
connectionClass: params.appServer.connectionClass
|
|
1699
|
-
});
|
|
1700
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1701
|
-
binding = void 0;
|
|
1702
|
-
}
|
|
1703
|
-
if (binding?.threadId && shouldRotateCodexGpt56MultiAgentBinding({
|
|
1704
|
-
bindingModel: binding.model,
|
|
1705
|
-
requestedModel: params.params.modelId
|
|
1706
|
-
})) {
|
|
1707
|
-
embeddedAgentLog.debug("codex app-server GPT-5.6 multi-agent version changed; starting a new thread", {
|
|
1708
|
-
threadId: binding.threadId,
|
|
1709
|
-
bindingModel: binding.model,
|
|
1710
|
-
requestedModel: params.params.modelId
|
|
1711
|
-
});
|
|
1712
|
-
await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
|
|
1713
|
-
binding = void 0;
|
|
1714
|
-
}
|
|
1715
|
-
const startModelSelection = resolveCodexAppServerThreadModelSelection({
|
|
1716
|
-
provider: params.params.provider,
|
|
1717
|
-
model: params.params.modelId,
|
|
1718
|
-
binding,
|
|
1719
|
-
authProfileId: params.params.authProfileId,
|
|
1720
|
-
authProfileStore: params.params.authProfileStore,
|
|
1721
|
-
agentDir: params.params.agentDir,
|
|
1722
|
-
config: params.params.config
|
|
1723
|
-
});
|
|
1724
|
-
const startModelProvider = startModelSelection.modelProvider;
|
|
1725
|
-
let preserveExistingBinding = !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
1726
|
-
let rotatedContextEngineBinding = false;
|
|
1727
|
-
let prebuiltPluginThreadConfig;
|
|
1728
|
-
const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
|
|
1729
|
-
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
1730
|
-
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
1731
|
-
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
1732
|
-
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
1733
|
-
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
1734
|
-
if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
|
|
1735
|
-
assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
|
|
1736
|
-
if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
|
|
1737
|
-
embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
|
|
1738
|
-
preserveExistingBinding = true;
|
|
1739
|
-
} else {
|
|
1740
|
-
embeddedAgentLog.debug("codex app-server MCP config changed; starting a new thread", { threadId: binding.threadId });
|
|
1741
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1742
|
-
}
|
|
1743
|
-
binding = void 0;
|
|
1744
|
-
}
|
|
1745
|
-
const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
|
|
1746
|
-
if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
|
|
1747
|
-
assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
|
|
1748
|
-
if (!ringZeroActive && transientWebSearchRestriction) {
|
|
1749
|
-
embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
|
|
1750
|
-
preserveExistingBinding = true;
|
|
1751
|
-
} else {
|
|
1752
|
-
embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
|
|
1753
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1754
|
-
}
|
|
1755
|
-
binding = void 0;
|
|
1756
|
-
}
|
|
1757
|
-
if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
|
|
1758
|
-
assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
|
|
1759
|
-
embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
|
|
1760
|
-
preserveExistingBinding = true;
|
|
1761
|
-
binding = void 0;
|
|
1762
|
-
}
|
|
1763
|
-
if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
|
|
1764
|
-
if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
|
|
1765
|
-
embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
|
|
1766
|
-
threadId: binding.threadId,
|
|
1767
|
-
engineId: contextEngineBinding?.engineId,
|
|
1768
|
-
previousEngineId: binding.contextEngine?.engineId,
|
|
1769
|
-
epoch: contextEngineBinding?.projection?.epoch,
|
|
1770
|
-
previousEpoch: binding.contextEngine?.projection?.epoch,
|
|
1771
|
-
fingerprint: contextEngineBinding?.projection?.fingerprint,
|
|
1772
|
-
previousFingerprint: binding.contextEngine?.projection?.fingerprint,
|
|
1773
|
-
policyFingerprint: contextEngineBinding?.policyFingerprint,
|
|
1774
|
-
previousPolicyFingerprint: binding.contextEngine?.policyFingerprint
|
|
1775
|
-
});
|
|
1776
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1777
|
-
binding = void 0;
|
|
1778
|
-
rotatedContextEngineBinding = true;
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
if (binding?.threadId && !areUserMcpServersFingerprintsCompatible({
|
|
1782
|
-
previous: binding.userMcpServersFingerprint,
|
|
1783
|
-
next: userMcpServersFingerprint,
|
|
1784
|
-
nextLegacy: legacyUserMcpServersFingerprint
|
|
1785
|
-
})) {
|
|
1786
|
-
embeddedAgentLog.debug("codex app-server user MCP config changed; starting a new thread", { threadId: binding.threadId });
|
|
1787
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1788
|
-
binding = void 0;
|
|
1789
|
-
}
|
|
1790
|
-
if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
|
|
1791
|
-
embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
|
|
1792
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1793
|
-
binding = void 0;
|
|
1794
|
-
}
|
|
1795
|
-
if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
|
|
1796
|
-
embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
|
|
1797
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1798
|
-
binding = void 0;
|
|
1799
|
-
}
|
|
1800
|
-
if (binding?.threadId) {
|
|
1801
|
-
let pluginBindingStale = isCodexPluginThreadBindingStale({
|
|
1802
|
-
codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
|
|
1803
|
-
bindingFingerprint: binding.pluginAppsFingerprint,
|
|
1804
|
-
bindingInputFingerprint: binding.pluginAppsInputFingerprint,
|
|
1805
|
-
currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
|
|
1806
|
-
hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
|
|
1312
|
+
}
|
|
1313
|
+
let closedClient = retiredSharedClient?.closed ?? false;
|
|
1314
|
+
if (!detachedSharedClient) {
|
|
1315
|
+
const close = client.close;
|
|
1316
|
+
if (typeof close === "function") try {
|
|
1317
|
+
close.call(client);
|
|
1318
|
+
closedClient = true;
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
|
|
1321
|
+
threadId: params.threadId,
|
|
1322
|
+
turnId: params.turnId,
|
|
1323
|
+
error
|
|
1807
1324
|
});
|
|
1808
|
-
if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
|
|
1809
|
-
binding,
|
|
1810
|
-
pluginThreadConfig: params.pluginThreadConfig
|
|
1811
|
-
})) try {
|
|
1812
|
-
prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build());
|
|
1813
|
-
pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
|
|
1814
|
-
} catch (error) {
|
|
1815
|
-
embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
|
|
1816
|
-
error,
|
|
1817
|
-
threadId: binding.threadId
|
|
1818
|
-
});
|
|
1819
|
-
}
|
|
1820
|
-
if (pluginBindingStale) {
|
|
1821
|
-
embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
|
|
1822
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1823
|
-
binding = void 0;
|
|
1824
|
-
}
|
|
1825
1325
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1326
|
+
}
|
|
1327
|
+
embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
|
|
1328
|
+
threadId: params.threadId,
|
|
1329
|
+
turnId: params.turnId,
|
|
1330
|
+
reason: params.reason,
|
|
1331
|
+
detachedSharedClient,
|
|
1332
|
+
closedClient,
|
|
1333
|
+
activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
//#endregion
|
|
1337
|
+
//#region extensions/codex/src/app-server/dynamic-tool-profile.ts
|
|
1338
|
+
/** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
|
|
1339
|
+
const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
|
|
1340
|
+
"read",
|
|
1341
|
+
"write",
|
|
1342
|
+
"edit",
|
|
1343
|
+
"apply_patch",
|
|
1344
|
+
"exec",
|
|
1345
|
+
"process",
|
|
1346
|
+
"update_plan",
|
|
1347
|
+
"tool_call",
|
|
1348
|
+
"tool_describe",
|
|
1349
|
+
"tool_search",
|
|
1350
|
+
"tool_search_code"
|
|
1351
|
+
];
|
|
1352
|
+
const CODEX_NATIVE_GOAL_TOOL_EXCLUDES = [
|
|
1353
|
+
"get_goal",
|
|
1354
|
+
"create_goal",
|
|
1355
|
+
"update_goal"
|
|
1356
|
+
];
|
|
1357
|
+
const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
|
|
1358
|
+
const DYNAMIC_TOOL_NAME_ALIASES = {
|
|
1359
|
+
bash: "exec",
|
|
1360
|
+
"apply-patch": "apply_patch"
|
|
1361
|
+
};
|
|
1362
|
+
/** Normalizes OpenClaw/Codex tool names before filtering and allowlist checks. */
|
|
1363
|
+
function normalizeCodexDynamicToolName(name) {
|
|
1364
|
+
const normalized = name.trim().toLowerCase();
|
|
1365
|
+
return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
|
|
1366
|
+
}
|
|
1367
|
+
/** True only for the host-scoped OpenClaw run's exact tool contract. */
|
|
1368
|
+
function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
|
|
1369
|
+
return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
|
|
1370
|
+
}
|
|
1371
|
+
/** Returns true for private QA runs that force the Codex runtime profile. */
|
|
1372
|
+
function isForcedPrivateQaCodexRuntime(env = process.env) {
|
|
1373
|
+
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
1374
|
+
}
|
|
1375
|
+
/** Resolves whether dynamic tools load directly or through Codex tool search. */
|
|
1376
|
+
function resolveCodexDynamicToolsLoading(config, env = process.env) {
|
|
1377
|
+
return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
|
|
1378
|
+
}
|
|
1379
|
+
function normalizeCodexModelId(modelId) {
|
|
1380
|
+
const normalized = modelId?.trim().toLowerCase();
|
|
1381
|
+
if (!normalized) return "";
|
|
1382
|
+
return normalized.includes("/") ? normalized.split("/").at(-1) : normalized;
|
|
1383
|
+
}
|
|
1384
|
+
/** Returns true when model behavior requires direct dynamic-tool registration. */
|
|
1385
|
+
function shouldUseDirectCodexDynamicToolsForModel(modelId) {
|
|
1386
|
+
return shouldDisableCodexToolSearchForModel(modelId);
|
|
1387
|
+
}
|
|
1388
|
+
/** Returns true for models whose tool-search path is unsupported or inefficient. */
|
|
1389
|
+
function shouldDisableCodexToolSearchForModel(modelId) {
|
|
1390
|
+
return normalizeCodexModelId(modelId) === "gpt-5.4-nano";
|
|
1391
|
+
}
|
|
1392
|
+
/** Resolves dynamic-tool loading after applying model-specific restrictions. */
|
|
1393
|
+
function resolveCodexDynamicToolsLoadingForModel(config, modelId, env = process.env) {
|
|
1394
|
+
const loading = resolveCodexDynamicToolsLoading(config, env);
|
|
1395
|
+
return loading === "searchable" && shouldUseDirectCodexDynamicToolsForModel(modelId) ? "direct" : loading;
|
|
1396
|
+
}
|
|
1397
|
+
/** Resolves dynamic-tool loading for the app-server connection that will execute the turn. */
|
|
1398
|
+
function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}, env = process.env) {
|
|
1399
|
+
const loading = resolveCodexDynamicToolsLoadingForModel(config, modelId, env);
|
|
1400
|
+
return loading === "searchable" && options.connectionClass === "remote" ? "direct" : loading;
|
|
1401
|
+
}
|
|
1402
|
+
/** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
|
|
1403
|
+
function filterCodexDynamicTools(tools, config, env = process.env) {
|
|
1404
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: false });
|
|
1405
|
+
}
|
|
1406
|
+
/** Keeps exec/process only when Codex cannot advertise an environment-backed native shell. */
|
|
1407
|
+
function filterCodexDynamicToolsWithOpenClawShell(tools, config, env = process.env) {
|
|
1408
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: true });
|
|
1409
|
+
}
|
|
1410
|
+
function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
1411
|
+
const excludes = /* @__PURE__ */ new Set();
|
|
1412
|
+
for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
|
|
1413
|
+
if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
|
|
1414
|
+
if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
|
|
1415
|
+
excludes.add(name);
|
|
1416
|
+
}
|
|
1417
|
+
for (const name of config.codexDynamicToolsExclude ?? []) {
|
|
1418
|
+
const trimmed = normalizeCodexDynamicToolName(name);
|
|
1419
|
+
if (trimmed) excludes.add(trimmed);
|
|
1420
|
+
}
|
|
1421
|
+
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
1422
|
+
}
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
1425
|
+
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
1426
|
+
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
1427
|
+
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
1428
|
+
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
1429
|
+
}
|
|
1430
|
+
//#endregion
|
|
1431
|
+
//#region extensions/codex/src/app-server/thread-binding-policy.ts
|
|
1432
|
+
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
1433
|
+
if (!params.current) return false;
|
|
1434
|
+
if (params.binding === params.current) return false;
|
|
1435
|
+
return params.connectionClass === "remote" || Boolean(params.binding);
|
|
1436
|
+
}
|
|
1437
|
+
function resolveCodexGpt56MultiAgentVersion(modelRef) {
|
|
1438
|
+
let modelId = modelRef?.trim().toLowerCase();
|
|
1439
|
+
if (!modelId) return;
|
|
1440
|
+
const slashIndex = modelId.indexOf("/");
|
|
1441
|
+
if (slashIndex > 0) {
|
|
1442
|
+
const provider = modelId.slice(0, slashIndex);
|
|
1443
|
+
if (provider !== "openai" && provider !== "codex") return;
|
|
1444
|
+
modelId = modelId.slice(slashIndex + 1);
|
|
1445
|
+
}
|
|
1446
|
+
if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
|
|
1447
|
+
return modelId === "gpt-5.6-luna" ? "v1" : void 0;
|
|
1448
|
+
}
|
|
1449
|
+
function shouldRotateCodexGpt56MultiAgentBinding(params) {
|
|
1450
|
+
const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
|
|
1451
|
+
const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
|
|
1452
|
+
return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
|
|
1453
|
+
}
|
|
1454
|
+
function isTransientWebSearchRestriction(params) {
|
|
1455
|
+
if (params.nativeProviderWebSearchSupport === "unknown") return true;
|
|
1456
|
+
if (params.params.config?.tools?.web?.search?.enabled === false) return false;
|
|
1457
|
+
if (params.params.disableTools === true) return true;
|
|
1458
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
1459
|
+
if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
|
|
1460
|
+
if (params.webSearchAllowed !== false) return false;
|
|
1461
|
+
if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
|
|
1462
|
+
if (params.params.toolsAllow === void 0) return false;
|
|
1463
|
+
return !params.params.toolsAllow.some((name) => {
|
|
1464
|
+
const normalized = normalizeCodexDynamicToolName(name);
|
|
1465
|
+
return normalized === "*" || normalized === "web_search";
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
function shouldRecheckRecoverablePluginBinding(params) {
|
|
1469
|
+
if (!params.pluginThreadConfig?.enabled) return false;
|
|
1470
|
+
if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
|
|
1471
|
+
const policyContext = params.binding.pluginAppPolicyContext;
|
|
1472
|
+
if (!policyContext) return false;
|
|
1473
|
+
const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
|
|
1474
|
+
const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
|
|
1475
|
+
const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
|
|
1476
|
+
const bindingContainsSettledPlugin = enabledPluginConfigKeys.filter((configKey) => !recoverablePluginConfigKeySet.has(configKey)).some((configKey) => (policyContext.pluginAppIds[configKey]?.length ?? 0) > 0 || Object.values(policyContext.apps).some((app) => app.source !== "account" && app.configKey === configKey));
|
|
1477
|
+
const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
|
|
1478
|
+
return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
|
|
1479
|
+
}
|
|
1480
|
+
//#endregion
|
|
1481
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
|
|
1482
|
+
var CodexThreadStartRequestError = class extends Error {
|
|
1483
|
+
constructor(cause) {
|
|
1484
|
+
super(formatErrorMessage(cause), { cause });
|
|
1485
|
+
this.name = "CodexThreadStartRequestError";
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
var CodexThreadBindingConflictError = class extends Error {
|
|
1489
|
+
constructor(threadId, operation) {
|
|
1490
|
+
super(`Codex thread binding changed while ${operation}: ${threadId}`);
|
|
1491
|
+
this.name = "CodexThreadBindingConflictError";
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
var CodexRingZeroAttestationError = class extends Error {
|
|
1495
|
+
constructor(cause) {
|
|
1496
|
+
super("Codex ring-zero MCP attestation failed", { cause });
|
|
1497
|
+
this.name = "CodexRingZeroAttestationError";
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
|
|
1501
|
+
var CodexAdoptedThreadActiveError = class extends Error {
|
|
1502
|
+
constructor() {
|
|
1503
|
+
super("Codex session became active in another runner; wait for it to finish before continuing");
|
|
1504
|
+
this.name = "CodexAdoptedThreadActiveError";
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
//#endregion
|
|
1508
|
+
//#region extensions/codex/src/app-server/reasoning-effort.ts
|
|
1509
|
+
const CODEX_REASONING_EFFORTS = [
|
|
1510
|
+
"minimal",
|
|
1511
|
+
"low",
|
|
1512
|
+
"medium",
|
|
1513
|
+
"high",
|
|
1514
|
+
"xhigh",
|
|
1515
|
+
"max",
|
|
1516
|
+
"ultra"
|
|
1517
|
+
];
|
|
1518
|
+
const GPT_56_MAX_REASONING_EFFORTS = [
|
|
1519
|
+
"low",
|
|
1520
|
+
"medium",
|
|
1521
|
+
"high",
|
|
1522
|
+
"xhigh",
|
|
1523
|
+
"max"
|
|
1524
|
+
];
|
|
1525
|
+
const GPT_56_ULTRA_REASONING_EFFORTS = [...GPT_56_MAX_REASONING_EFFORTS, "ultra"];
|
|
1526
|
+
const GPT_5_PRO_REASONING_EFFORTS = [
|
|
1527
|
+
"medium",
|
|
1528
|
+
"high",
|
|
1529
|
+
"xhigh"
|
|
1530
|
+
];
|
|
1531
|
+
const GPT_56_ULTRA_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.6-sol", "gpt-5.6-terra"]);
|
|
1532
|
+
const GPT_56_MAX_MODEL_IDS = /* @__PURE__ */ new Set([...GPT_56_ULTRA_MODEL_IDS, "gpt-5.6-luna"]);
|
|
1533
|
+
const MODERN_CODEX_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
1534
|
+
...GPT_56_MAX_MODEL_IDS,
|
|
1535
|
+
"gpt-5.5",
|
|
1536
|
+
"gpt-5.5-pro",
|
|
1537
|
+
"gpt-5.4",
|
|
1538
|
+
"gpt-5.4-pro",
|
|
1539
|
+
"gpt-5.4-mini",
|
|
1540
|
+
"gpt-5.3-codex-spark"
|
|
1541
|
+
]);
|
|
1542
|
+
function normalizeCodexReasoningEfforts(efforts) {
|
|
1543
|
+
if (!efforts) return [];
|
|
1544
|
+
const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
|
|
1545
|
+
return CODEX_REASONING_EFFORTS.filter((effort) => supported.has(effort));
|
|
1546
|
+
}
|
|
1547
|
+
/** Read reasoning metadata after the Codex app-server route has been selected. */
|
|
1548
|
+
function readCodexSupportedReasoningEfforts(compat) {
|
|
1549
|
+
if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
|
|
1550
|
+
const efforts = compat.supportedReasoningEfforts;
|
|
1551
|
+
if (!Array.isArray(efforts)) return;
|
|
1552
|
+
return efforts.filter((effort) => typeof effort === "string");
|
|
1553
|
+
}
|
|
1554
|
+
function resolveSupportedReasoningEffort(params) {
|
|
1555
|
+
const supported = normalizeCodexReasoningEfforts(params.supportedReasoningEfforts);
|
|
1556
|
+
if (supported.includes(params.requested)) return params.requested;
|
|
1557
|
+
const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
|
|
1558
|
+
const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
|
|
1559
|
+
return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
|
|
1560
|
+
}
|
|
1561
|
+
function resolveFallbackReasoningEfforts(modelId) {
|
|
1562
|
+
const normalized = modelId.trim().toLowerCase();
|
|
1563
|
+
if (GPT_56_ULTRA_MODEL_IDS.has(normalized)) return GPT_56_ULTRA_REASONING_EFFORTS;
|
|
1564
|
+
if (normalized === "gpt-5.6-luna") return GPT_56_MAX_REASONING_EFFORTS;
|
|
1565
|
+
if (normalized === "gpt-5.5-pro" || normalized === "gpt-5.4-pro") return GPT_5_PRO_REASONING_EFFORTS;
|
|
1566
|
+
}
|
|
1567
|
+
/** Resolve a turn effort from app-server metadata, with exact-name offline fallbacks. */
|
|
1568
|
+
function resolveCodexAppServerReasoningEffort(params) {
|
|
1569
|
+
if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
|
|
1570
|
+
const supportedReasoningEfforts = params.supportedReasoningEfforts ?? resolveFallbackReasoningEfforts(params.modelId);
|
|
1571
|
+
if (supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
1572
|
+
requested: params.thinkLevel,
|
|
1573
|
+
supportedReasoningEfforts
|
|
1574
|
+
}) ?? null;
|
|
1575
|
+
const normalizedModelId = params.modelId.trim().toLowerCase();
|
|
1576
|
+
if (params.thinkLevel === "minimal") return MODERN_CODEX_MODEL_IDS.has(normalizedModelId) ? "low" : "minimal";
|
|
1577
|
+
if (params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
|
|
1578
|
+
return params.thinkLevel === "max" && GPT_56_MAX_MODEL_IDS.has(normalizedModelId) ? "max" : null;
|
|
1579
|
+
}
|
|
1580
|
+
//#endregion
|
|
1581
|
+
//#region extensions/codex/src/app-server/thread-model-selection.ts
|
|
1582
|
+
const CODEX_NATIVE_PERSONALITY_NONE = "none";
|
|
1583
|
+
function resolveCodexBindingModelProviderFallback(params) {
|
|
1584
|
+
const provider = params.provider?.trim().toLowerCase();
|
|
1585
|
+
if (provider && provider !== "codex") return;
|
|
1586
|
+
const currentModel = params.currentModel?.trim();
|
|
1587
|
+
const bindingModel = params.bindingModel?.trim();
|
|
1588
|
+
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
1589
|
+
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
1590
|
+
}
|
|
1591
|
+
function resolveCodexAppServerThreadModelSelection(params) {
|
|
1592
|
+
const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
|
|
1593
|
+
const explicitModelProvider = resolveCodexAppServerModelProvider({
|
|
1594
|
+
provider: params.provider,
|
|
1595
|
+
authProfileId,
|
|
1596
|
+
authProfileStore: params.authProfileStore,
|
|
1597
|
+
agentDir: params.agentDir,
|
|
1598
|
+
config: params.config
|
|
1599
|
+
});
|
|
1600
|
+
const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
|
|
1601
|
+
provider: params.provider,
|
|
1602
|
+
currentModel: params.model,
|
|
1603
|
+
bindingModel: params.binding.model,
|
|
1604
|
+
bindingModelProvider: params.binding.modelProvider
|
|
1605
|
+
}) : void 0;
|
|
1606
|
+
return resolveCodexAppServerRequestModelSelection({
|
|
1607
|
+
model: params.model,
|
|
1608
|
+
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
1609
|
+
authProfileId,
|
|
1610
|
+
authProfileStore: params.authProfileStore,
|
|
1611
|
+
agentDir: params.agentDir,
|
|
1612
|
+
config: params.config
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
function resolveCodexAppServerRequestModelSelection(params) {
|
|
1616
|
+
const model = params.model.trim();
|
|
1617
|
+
const modelProvider = params.modelProvider?.trim();
|
|
1618
|
+
if (modelProvider) return {
|
|
1619
|
+
model,
|
|
1620
|
+
modelProvider
|
|
1621
|
+
};
|
|
1622
|
+
const slashIndex = model.indexOf("/");
|
|
1623
|
+
if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
|
|
1624
|
+
const inferredModelProvider = resolveCodexAppServerModelProvider({
|
|
1625
|
+
provider: model.slice(0, slashIndex),
|
|
1626
|
+
authProfileId: params.authProfileId,
|
|
1627
|
+
authProfileStore: params.authProfileStore,
|
|
1628
|
+
agentDir: params.agentDir,
|
|
1629
|
+
config: params.config
|
|
1630
|
+
});
|
|
1631
|
+
return {
|
|
1632
|
+
model: model.slice(slashIndex + 1).trim(),
|
|
1633
|
+
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function hasProviderQualifiedModelRef(model) {
|
|
1637
|
+
const trimmed = model?.trim();
|
|
1638
|
+
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
|
1639
|
+
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
1640
|
+
}
|
|
1641
|
+
function resolveCodexAppServerModelProvider(params) {
|
|
1642
|
+
const normalized = params.provider.trim();
|
|
1643
|
+
const normalizedLower = normalized.toLowerCase();
|
|
1644
|
+
if (!normalized || normalizedLower === "codex") return;
|
|
1645
|
+
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
1646
|
+
return normalizedLower === "openai" ? "openai" : normalized;
|
|
1647
|
+
}
|
|
1648
|
+
function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
|
|
1649
|
+
return resolveCodexAppServerReasoningEffort({
|
|
1650
|
+
thinkLevel,
|
|
1651
|
+
modelId,
|
|
1652
|
+
supportedReasoningEfforts
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
//#endregion
|
|
1656
|
+
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
1657
|
+
function buildDeveloperInstructions(params, options = {}) {
|
|
1658
|
+
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
1659
|
+
surface: "codex_app_server",
|
|
1660
|
+
includeLegacyGlobalGuidance: false
|
|
1661
|
+
}).join("\n");
|
|
1662
|
+
return [
|
|
1663
|
+
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
1664
|
+
buildDeferredDynamicToolManifest(options.dynamicTools),
|
|
1665
|
+
buildSkillWorkshopInstruction(options.dynamicTools),
|
|
1666
|
+
"Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.",
|
|
1667
|
+
buildVisibleReplyInstruction(params, options.dynamicTools),
|
|
1668
|
+
nativeCommandGuidance,
|
|
1669
|
+
params.extraSystemPrompt
|
|
1670
|
+
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
1671
|
+
}
|
|
1672
|
+
function buildDeferredDynamicToolManifest(dynamicTools) {
|
|
1673
|
+
const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
|
|
1674
|
+
if (deferredToolNames.length === 0) return;
|
|
1675
|
+
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
1676
|
+
}
|
|
1677
|
+
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
1678
|
+
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
1679
|
+
return buildSkillWorkshopPromptSection().join("\n");
|
|
1680
|
+
}
|
|
1681
|
+
function buildVisibleReplyInstruction(params, dynamicTools) {
|
|
1682
|
+
const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
|
|
1683
|
+
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.";
|
|
1684
|
+
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.";
|
|
1685
|
+
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
|
|
1686
|
+
}
|
|
1687
|
+
//#endregion
|
|
1688
|
+
//#region extensions/codex/src/app-server/web-search.ts
|
|
1689
|
+
const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG = {
|
|
1690
|
+
"features.standalone_web_search": false,
|
|
1691
|
+
web_search: "disabled"
|
|
1692
|
+
};
|
|
1693
|
+
function normalizeOptionalString$1(value) {
|
|
1694
|
+
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
1695
|
+
}
|
|
1696
|
+
function normalizeUniqueStrings(value) {
|
|
1697
|
+
if (!Array.isArray(value)) return;
|
|
1698
|
+
const normalized = [...new Set(value.map(normalizeOptionalString$1).filter((entry) => Boolean(entry)))];
|
|
1699
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
1700
|
+
}
|
|
1701
|
+
function hasManagedSearchProvider(config) {
|
|
1702
|
+
return normalizeOptionalString$1(config?.tools?.web?.search?.provider) !== void 0;
|
|
1703
|
+
}
|
|
1704
|
+
function hasNativeDomainRestrictions(config) {
|
|
1705
|
+
return normalizeUniqueStrings(config?.tools?.web?.search?.openaiCodex?.allowedDomains) !== void 0;
|
|
1706
|
+
}
|
|
1707
|
+
function buildCodexNativeWebSearchThreadConfig(config) {
|
|
1708
|
+
const nativeConfig = config?.tools?.web?.search?.openaiCodex;
|
|
1709
|
+
const threadConfig = {
|
|
1710
|
+
"features.standalone_web_search": false,
|
|
1711
|
+
web_search: nativeConfig?.mode === "live" ? "live" : "cached"
|
|
1712
|
+
};
|
|
1713
|
+
const allowedDomains = normalizeUniqueStrings(nativeConfig?.allowedDomains);
|
|
1714
|
+
if (allowedDomains) threadConfig["tools.web_search.allowed_domains"] = allowedDomains;
|
|
1715
|
+
if (nativeConfig?.contextSize) threadConfig["tools.web_search.context_size"] = nativeConfig.contextSize;
|
|
1716
|
+
const location = nativeConfig?.userLocation;
|
|
1717
|
+
const country = normalizeOptionalString$1(location?.country);
|
|
1718
|
+
const region = normalizeOptionalString$1(location?.region);
|
|
1719
|
+
const city = normalizeOptionalString$1(location?.city);
|
|
1720
|
+
const timezone = normalizeOptionalString$1(location?.timezone);
|
|
1721
|
+
if (country) threadConfig["tools.web_search.location.country"] = country;
|
|
1722
|
+
if (region) threadConfig["tools.web_search.location.region"] = region;
|
|
1723
|
+
if (city) threadConfig["tools.web_search.location.city"] = city;
|
|
1724
|
+
if (timezone) threadConfig["tools.web_search.location.timezone"] = timezone;
|
|
1725
|
+
return threadConfig;
|
|
1726
|
+
}
|
|
1727
|
+
function resolveCodexWebSearchPlan(params) {
|
|
1728
|
+
if (params.disableTools === true || params.webSearchAllowed === false || params.config?.tools?.web?.search?.enabled === false) return {
|
|
1729
|
+
kind: "disabled",
|
|
1730
|
+
suppressManagedWebSearch: true,
|
|
1731
|
+
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1732
|
+
};
|
|
1733
|
+
const nativeConfig = params.config?.tools?.web?.search?.openaiCodex;
|
|
1734
|
+
const managedSearchExplicit = hasManagedSearchProvider(params.config) || nativeConfig?.enabled === false;
|
|
1735
|
+
const nativeProviderSupportsSearch = params.nativeProviderWebSearchSupport === void 0 || params.nativeProviderWebSearchSupport === "supported";
|
|
1736
|
+
if (!(params.nativeToolSurfaceEnabled !== false && nativeProviderSupportsSearch && nativeConfig?.enabled !== false && !hasManagedSearchProvider(params.config))) {
|
|
1737
|
+
if (!managedSearchExplicit && hasNativeDomainRestrictions(params.config)) return {
|
|
1738
|
+
kind: "disabled",
|
|
1739
|
+
suppressManagedWebSearch: true,
|
|
1740
|
+
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1741
|
+
};
|
|
1742
|
+
return {
|
|
1743
|
+
kind: "managed",
|
|
1744
|
+
suppressManagedWebSearch: false,
|
|
1745
|
+
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
kind: "native-hosted",
|
|
1750
|
+
suppressManagedWebSearch: true,
|
|
1751
|
+
threadConfig: buildCodexNativeWebSearchThreadConfig(params.config)
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
const CODEX_CODE_MODE_THREAD_CONFIG = {
|
|
1755
|
+
"features.code_mode": true,
|
|
1756
|
+
"features.code_mode_only": false,
|
|
1757
|
+
"features.apply_patch_streaming_events": true
|
|
1758
|
+
};
|
|
1759
|
+
const CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG = { "features.goals": false };
|
|
1760
|
+
const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
|
|
1761
|
+
"features.code_mode": false,
|
|
1762
|
+
"features.code_mode_only": false
|
|
1763
|
+
};
|
|
1764
|
+
const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
|
|
1765
|
+
const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
|
|
1766
|
+
const CODEX_DELEGATION_DISABLED_THREAD_CONFIG = {
|
|
1767
|
+
"features.multi_agent": false,
|
|
1768
|
+
"features.multi_agent_v2": false
|
|
1769
|
+
};
|
|
1770
|
+
const CODEX_RING_ZERO_THREAD_CONFIG = {
|
|
1771
|
+
"features.apps": false,
|
|
1772
|
+
"features.current_time_reminder": false,
|
|
1773
|
+
"features.deferred_executor": false,
|
|
1774
|
+
"features.enable_fanout": false,
|
|
1775
|
+
"features.goals": false,
|
|
1776
|
+
"features.hooks": false,
|
|
1777
|
+
"features.image_generation": false,
|
|
1778
|
+
"features.memories": false,
|
|
1779
|
+
"features.multi_agent": false,
|
|
1780
|
+
"features.multi_agent_v2": false,
|
|
1781
|
+
"features.plugins": false,
|
|
1782
|
+
"features.standalone_web_search": false,
|
|
1783
|
+
"features.token_budget": false,
|
|
1784
|
+
"orchestrator.mcp.enabled": false,
|
|
1785
|
+
"orchestrator.skills.enabled": false,
|
|
1786
|
+
"tools.experimental_request_user_input.enabled": false,
|
|
1787
|
+
hooks: {
|
|
1788
|
+
PreToolUse: [],
|
|
1789
|
+
PermissionRequest: [],
|
|
1790
|
+
PostToolUse: [],
|
|
1791
|
+
PreCompact: [],
|
|
1792
|
+
PostCompact: [],
|
|
1793
|
+
SessionStart: [],
|
|
1794
|
+
UserPromptSubmit: [],
|
|
1795
|
+
SubagentStart: [],
|
|
1796
|
+
SubagentStop: [],
|
|
1797
|
+
Stop: []
|
|
1798
|
+
},
|
|
1799
|
+
project_doc_max_bytes: 0,
|
|
1800
|
+
notify: [],
|
|
1801
|
+
web_search: "disabled"
|
|
1802
|
+
};
|
|
1803
|
+
const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
|
|
1804
|
+
"apps",
|
|
1805
|
+
"code_mode",
|
|
1806
|
+
"code_mode_only",
|
|
1807
|
+
"current_time_reminder",
|
|
1808
|
+
"deferred_executor",
|
|
1809
|
+
"enable_fanout",
|
|
1810
|
+
"goals",
|
|
1811
|
+
"hooks",
|
|
1812
|
+
"image_generation",
|
|
1813
|
+
"memories",
|
|
1814
|
+
"multi_agent",
|
|
1815
|
+
"multi_agent_v2",
|
|
1816
|
+
"plugins",
|
|
1817
|
+
"standalone_web_search",
|
|
1818
|
+
"token_budget"
|
|
1819
|
+
]);
|
|
1820
|
+
const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
1821
|
+
"mdm",
|
|
1822
|
+
"system",
|
|
1823
|
+
"enterpriseManaged",
|
|
1824
|
+
"user",
|
|
1825
|
+
"project",
|
|
1826
|
+
"sessionFlags"
|
|
1827
|
+
]);
|
|
1828
|
+
function buildThreadStartParams(params, options) {
|
|
1829
|
+
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
1830
|
+
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
1831
|
+
provider: params.provider,
|
|
1832
|
+
authProfileId: params.authProfileId,
|
|
1833
|
+
authProfileStore: params.authProfileStore,
|
|
1834
|
+
agentDir: params.agentDir,
|
|
1835
|
+
config: params.config
|
|
1836
|
+
});
|
|
1837
|
+
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
1838
|
+
model: options.model ?? params.modelId,
|
|
1839
|
+
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
1840
|
+
authProfileId: params.authProfileId,
|
|
1841
|
+
authProfileStore: params.authProfileStore,
|
|
1842
|
+
agentDir: params.agentDir,
|
|
1843
|
+
config: params.config
|
|
1844
|
+
});
|
|
1845
|
+
return {
|
|
1846
|
+
model: modelSelection.model,
|
|
1847
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
1848
|
+
cwd: options.cwd,
|
|
1849
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
1850
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
1851
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
1852
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1853
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
1854
|
+
serviceName: "OpenClaw",
|
|
1855
|
+
...ringZeroActive ? { baseInstructions: "" } : {},
|
|
1856
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
1857
|
+
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
1858
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1859
|
+
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
1860
|
+
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
1861
|
+
webSearchAllowed: options.webSearchAllowed,
|
|
1862
|
+
appServer: options.appServer,
|
|
1863
|
+
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1864
|
+
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
1865
|
+
}),
|
|
1866
|
+
...resolveCodexThreadEnvironmentSelection(options),
|
|
1867
|
+
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
1868
|
+
dynamicTools: [...options.dynamicTools],
|
|
1869
|
+
experimentalRawEvents: true
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function buildThreadResumeParams(params, options) {
|
|
1873
|
+
const modelSelection = options.preserveNativeModel ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
1874
|
+
model: options.model ?? params.modelId,
|
|
1875
|
+
modelProvider: options.modelProvider ?? resolveCodexAppServerModelProvider({
|
|
1876
|
+
provider: params.provider,
|
|
1877
|
+
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
1878
|
+
authProfileStore: params.authProfileStore,
|
|
1879
|
+
agentDir: params.agentDir,
|
|
1880
|
+
config: params.config
|
|
1881
|
+
}),
|
|
1882
|
+
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
1883
|
+
authProfileStore: params.authProfileStore,
|
|
1884
|
+
agentDir: params.agentDir,
|
|
1885
|
+
config: params.config
|
|
1886
|
+
});
|
|
1887
|
+
return {
|
|
1888
|
+
threadId: options.threadId,
|
|
1889
|
+
excludeTurns: true,
|
|
1890
|
+
initialTurnsPage: {
|
|
1891
|
+
limit: 1,
|
|
1892
|
+
sortDirection: "desc",
|
|
1893
|
+
itemsView: "notLoaded"
|
|
1894
|
+
},
|
|
1895
|
+
...modelSelection ? {
|
|
1896
|
+
model: modelSelection.model,
|
|
1897
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
|
|
1898
|
+
} : {},
|
|
1899
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
1900
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
1901
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
1902
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1903
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
1904
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
1905
|
+
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
1906
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1907
|
+
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
1908
|
+
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
1909
|
+
webSearchAllowed: options.webSearchAllowed,
|
|
1910
|
+
appServer: options.appServer,
|
|
1911
|
+
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1912
|
+
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
1913
|
+
}),
|
|
1914
|
+
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
1918
|
+
const codeModeConfig = {
|
|
1919
|
+
...CODEX_CODE_MODE_THREAD_CONFIG,
|
|
1920
|
+
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
1921
|
+
};
|
|
1922
|
+
if (options.nativeCodeModeEnabled === false) {
|
|
1923
|
+
const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
|
|
1924
|
+
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
1925
|
+
return disabledConfig;
|
|
1926
|
+
}
|
|
1927
|
+
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true }) ?? {
|
|
1928
|
+
...codeModeConfig,
|
|
1929
|
+
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
1930
|
+
"features.code_mode_only": true
|
|
1931
|
+
}, options.directOnlyToolNamespaces);
|
|
1932
|
+
return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG) ?? {
|
|
1933
|
+
...codeModeConfig,
|
|
1934
|
+
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG
|
|
1935
|
+
}, options.directOnlyToolNamespaces);
|
|
1936
|
+
}
|
|
1937
|
+
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
1938
|
+
if (!requiredNamespaces?.length) return config;
|
|
1939
|
+
const configured = config["code_mode.direct_only_tool_namespaces"];
|
|
1940
|
+
const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
1941
|
+
return {
|
|
1942
|
+
...config,
|
|
1943
|
+
"code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
1947
|
+
return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
|
|
1948
|
+
}
|
|
1949
|
+
function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
1950
|
+
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
1951
|
+
const configMcpServers = config?.mcp_servers;
|
|
1952
|
+
if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
|
|
1953
|
+
const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
1954
|
+
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
1955
|
+
config: params.config,
|
|
1956
|
+
disableTools: params.disableTools,
|
|
1957
|
+
nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
|
|
1958
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1959
|
+
webSearchAllowed: options.webSearchAllowed
|
|
1960
|
+
}).threadConfig;
|
|
1961
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
1962
|
+
const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
|
|
1963
|
+
if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
|
|
1964
|
+
return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
|
|
1965
|
+
...runtimeConfig,
|
|
1966
|
+
...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
|
|
1970
|
+
if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
|
|
1971
|
+
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
1972
|
+
return {
|
|
1973
|
+
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
1974
|
+
...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
async function readCodexInheritedMcpServerNames(client, cwd, signal) {
|
|
1978
|
+
const response = await client.request("config/read", {
|
|
1979
|
+
cwd,
|
|
1980
|
+
includeLayers: true
|
|
1981
|
+
}, { signal });
|
|
1982
|
+
if (!isJsonObject(response) || !isJsonObject(response.config)) throw new Error("Codex config/read returned an invalid effective config");
|
|
1983
|
+
if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
|
|
1984
|
+
for (const layer of response.layers) {
|
|
1985
|
+
if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
|
|
1986
|
+
if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex ring-zero cannot override config layer ${layer.name.type}`);
|
|
1987
|
+
if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex ring-zero does not recognize config layer ${layer.name.type}`);
|
|
1988
|
+
}
|
|
1989
|
+
const configuredServers = response.config.mcp_servers;
|
|
1990
|
+
if (configuredServers === void 0) return [];
|
|
1991
|
+
if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
|
|
1992
|
+
return Object.keys(configuredServers).toSorted();
|
|
1993
|
+
}
|
|
1994
|
+
async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
|
|
1995
|
+
const response = await client.request("configRequirements/read", void 0, { signal });
|
|
1996
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
|
|
1997
|
+
if (response.requirements === null) return;
|
|
1998
|
+
if (!isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
|
|
1999
|
+
for (const key of [
|
|
2000
|
+
"hooks",
|
|
2001
|
+
"managedHooks",
|
|
2002
|
+
"managed_hooks"
|
|
2003
|
+
]) {
|
|
2004
|
+
const hooks = response.requirements[key];
|
|
2005
|
+
if (hooks === void 0 || hooks === null) continue;
|
|
2006
|
+
if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
|
|
2007
|
+
if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex ring-zero cannot override managed hooks");
|
|
2008
|
+
}
|
|
2009
|
+
for (const key of ["featureRequirements", "feature_requirements"]) {
|
|
2010
|
+
const requirements = response.requirements[key];
|
|
2011
|
+
if (requirements === void 0 || requirements === null) continue;
|
|
2012
|
+
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
2013
|
+
for (const [feature, enabled] of Object.entries(requirements)) {
|
|
2014
|
+
if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
2015
|
+
if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(feature)) throw new Error(`Codex ring-zero cannot override required feature ${feature}`);
|
|
1832
2016
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
client: params.client,
|
|
1888
|
-
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
1889
|
-
request: resumeParams,
|
|
1890
|
-
signal: params.signal
|
|
1891
|
-
}));
|
|
1892
|
-
if (ringZeroActive) try {
|
|
1893
|
-
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
1894
|
-
} catch (error) {
|
|
1895
|
-
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
1896
|
-
throw new CodexRingZeroAttestationError(error);
|
|
1897
|
-
}
|
|
1898
|
-
throwIfAborted();
|
|
1899
|
-
const boundAuthProfileId = authProfileId;
|
|
1900
|
-
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
1901
|
-
const resumePatch = {
|
|
1902
|
-
cwd: params.cwd,
|
|
1903
|
-
authProfileId: boundAuthProfileId,
|
|
1904
|
-
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
1905
|
-
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
1906
|
-
modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
1907
|
-
dynamicToolsFingerprint,
|
|
1908
|
-
dynamicToolsContainDeferred,
|
|
1909
|
-
webSearchThreadConfigFingerprint,
|
|
1910
|
-
userMcpServersFingerprint,
|
|
1911
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
1912
|
-
ringZeroConfigFingerprint,
|
|
1913
|
-
ringZeroClientInstanceId,
|
|
1914
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
1915
|
-
networkProxyConfigFingerprint,
|
|
1916
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
1917
|
-
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
1918
|
-
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
|
1919
|
-
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
|
1920
|
-
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
|
1921
|
-
contextEngine: contextEngineBinding,
|
|
1922
|
-
environmentSelectionFingerprint
|
|
1923
|
-
};
|
|
1924
|
-
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
1925
|
-
kind: "patch",
|
|
1926
|
-
threadId: resumeBinding.threadId,
|
|
1927
|
-
patch: resumePatch
|
|
1928
|
-
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
1929
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
1930
|
-
sessionId: params.params.sessionId,
|
|
1931
|
-
sessionKey: params.params.sessionKey,
|
|
1932
|
-
threadId: response.thread.id,
|
|
1933
|
-
engineId: contextEngineBinding.engineId,
|
|
1934
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
1935
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
1936
|
-
action: "resumed"
|
|
1937
|
-
});
|
|
1938
|
-
lifecycleTiming.mark("thread-ready");
|
|
1939
|
-
lifecycleTiming.logSummary({
|
|
1940
|
-
runId: params.params.runId,
|
|
1941
|
-
sessionId: params.params.sessionId,
|
|
1942
|
-
sessionKey: params.params.sessionKey,
|
|
1943
|
-
threadId: response.thread.id,
|
|
1944
|
-
action: "resumed"
|
|
1945
|
-
});
|
|
1946
|
-
const activeTurnIds = readActiveCodexTurnIds(response.thread);
|
|
1947
|
-
return {
|
|
1948
|
-
...resumeBinding,
|
|
1949
|
-
threadId: response.thread.id,
|
|
1950
|
-
...resumePatch,
|
|
1951
|
-
lifecycle: {
|
|
1952
|
-
action: "resumed",
|
|
1953
|
-
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
1954
|
-
}
|
|
1955
|
-
};
|
|
1956
|
-
} catch (error) {
|
|
1957
|
-
resumeReservation?.release();
|
|
1958
|
-
if (isCodexAppServerStartSelectionChangedError(error)) throw error;
|
|
1959
|
-
if (error instanceof CodexRingZeroAttestationError) {
|
|
1960
|
-
await clearCurrentBinding("retiring a failed ring-zero thread attestation");
|
|
1961
|
-
throw error;
|
|
1962
|
-
}
|
|
1963
|
-
if (error instanceof CodexAdoptedThreadActiveError) throw error;
|
|
1964
|
-
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
1965
|
-
const resumeRejected = error instanceof CodexAppServerRpcError;
|
|
1966
|
-
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
1967
|
-
threadId: resumeBinding.threadId,
|
|
1968
|
-
timeoutMs: 5e3
|
|
1969
|
-
}) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
1970
|
-
if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
|
|
1971
|
-
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
1972
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1973
|
-
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
async function attestCodexRingZeroThreadHasNoMcpServers(client, threadId, signal) {
|
|
2020
|
+
const response = await client.request("mcpServerStatus/list", {
|
|
2021
|
+
threadId,
|
|
2022
|
+
limit: 1,
|
|
2023
|
+
detail: "toolsAndAuthOnly"
|
|
2024
|
+
}, { signal });
|
|
2025
|
+
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid ring-zero attestation");
|
|
2026
|
+
if (response.data.length > 0) {
|
|
2027
|
+
const first = response.data[0];
|
|
2028
|
+
const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
|
|
2029
|
+
throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
|
|
2030
|
+
}
|
|
2031
|
+
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
2032
|
+
}
|
|
2033
|
+
function hasNonEmptyJsonValue(value) {
|
|
2034
|
+
if (value === null || value === false || value === "") return false;
|
|
2035
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
2036
|
+
if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
|
|
2037
|
+
return true;
|
|
2038
|
+
}
|
|
2039
|
+
function resolveCodexThreadApprovalsReviewer(appServer, config) {
|
|
2040
|
+
return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
|
|
2041
|
+
}
|
|
2042
|
+
function codexThreadSandboxOrPermissions(appServer) {
|
|
2043
|
+
if (appServer.networkProxy) return {};
|
|
2044
|
+
return { sandbox: appServer.sandbox };
|
|
2045
|
+
}
|
|
2046
|
+
function resolveCodexThreadEnvironmentSelection(options) {
|
|
2047
|
+
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
2048
|
+
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
2049
|
+
return {};
|
|
2050
|
+
}
|
|
2051
|
+
//#endregion
|
|
2052
|
+
//#region extensions/codex/src/app-server/thread-resume.ts
|
|
2053
|
+
/** Owns Codex thread/resume subscription safety. */
|
|
2054
|
+
/** Resumes one thread and retires the physical client when acceptance is indeterminate. */
|
|
2055
|
+
async function resumeCodexAppServerThread(params) {
|
|
2056
|
+
const threadId = params.request.threadId;
|
|
2057
|
+
let response;
|
|
2058
|
+
try {
|
|
2059
|
+
response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
|
|
2060
|
+
...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
|
|
2061
|
+
...params.signal ? { signal: params.signal } : {}
|
|
2062
|
+
}));
|
|
2063
|
+
assertCodexThreadResumeSubscription(threadId, response.thread.id);
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
|
|
2066
|
+
if (error instanceof CodexAppServerRpcError) throw error;
|
|
2067
|
+
try {
|
|
2068
|
+
await params.abandonClient();
|
|
2069
|
+
} catch (abandonError) {
|
|
2070
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
|
|
1974
2071
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
2072
|
+
if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
|
|
2073
|
+
throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
|
|
2074
|
+
}
|
|
2075
|
+
return response;
|
|
2076
|
+
}
|
|
2077
|
+
//#endregion
|
|
2078
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-io.ts
|
|
2079
|
+
async function resumeExistingCodexThread(params, context) {
|
|
2080
|
+
const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
|
|
2081
|
+
let resumeReservation;
|
|
2082
|
+
try {
|
|
2083
|
+
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
2084
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({
|
|
2085
|
+
action: "resume",
|
|
2086
|
+
binding: resumeBinding
|
|
2087
|
+
}) ?? {
|
|
1977
2088
|
configPatch: params.finalConfigPatch,
|
|
1978
2089
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
1979
2090
|
};
|
|
1980
|
-
const
|
|
1981
|
-
const
|
|
1982
|
-
|
|
1983
|
-
|
|
2091
|
+
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
|
|
2092
|
+
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
|
|
2093
|
+
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2094
|
+
threadId: resumeBinding.threadId,
|
|
2095
|
+
authProfileId,
|
|
2096
|
+
model: startModelSelection.model,
|
|
2097
|
+
modelProvider: startModelProvider,
|
|
2098
|
+
preserveNativeModel: resumeBinding.preserveNativeModel === true,
|
|
1984
2099
|
appServer: params.appServer,
|
|
2100
|
+
dynamicTools: params.dynamicTools,
|
|
1985
2101
|
developerInstructions: params.developerInstructions,
|
|
1986
|
-
config,
|
|
2102
|
+
config: resumeConfig,
|
|
1987
2103
|
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
1988
2104
|
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
1989
2105
|
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
1990
2106
|
webSearchAllowed: params.webSearchAllowed,
|
|
1991
|
-
environmentSelection: params.environmentSelection,
|
|
1992
|
-
model: startModelSelection.model,
|
|
1993
|
-
modelProvider: startModelProvider,
|
|
1994
2107
|
hostSystemAgentActive,
|
|
1995
2108
|
ringZeroInheritedMcpServerNames
|
|
1996
2109
|
}));
|
|
1997
|
-
const requestModelProvider = typeof
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2110
|
+
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2111
|
+
throwIfAborted();
|
|
2112
|
+
if (resumeBinding.preserveNativeModel === true) {
|
|
2113
|
+
const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
2114
|
+
threadId: resumeBinding.threadId,
|
|
2115
|
+
includeTurns: false
|
|
2116
|
+
}, { signal: params.signal }));
|
|
2117
|
+
throwIfAborted();
|
|
2118
|
+
if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
2119
|
+
}
|
|
2120
|
+
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
2121
|
+
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
2122
|
+
client: params.client,
|
|
2123
|
+
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
2124
|
+
request: resumeParams,
|
|
2125
|
+
signal: params.signal
|
|
2005
2126
|
}));
|
|
2006
2127
|
if (ringZeroActive) try {
|
|
2007
2128
|
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
2008
2129
|
} catch (error) {
|
|
2009
2130
|
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2010
|
-
throw error;
|
|
2131
|
+
throw new CodexRingZeroAttestationError(error);
|
|
2011
2132
|
}
|
|
2012
2133
|
throwIfAborted();
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2134
|
+
const boundAuthProfileId = authProfileId;
|
|
2135
|
+
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2136
|
+
const resumePatch = {
|
|
2137
|
+
cwd: params.cwd,
|
|
2138
|
+
authProfileId: boundAuthProfileId,
|
|
2139
|
+
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
2140
|
+
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
2141
|
+
modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
2142
|
+
dynamicToolsFingerprint,
|
|
2143
|
+
dynamicToolsContainDeferred,
|
|
2144
|
+
webSearchThreadConfigFingerprint,
|
|
2145
|
+
userMcpServersFingerprint,
|
|
2146
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2147
|
+
ringZeroConfigFingerprint,
|
|
2148
|
+
ringZeroClientInstanceId,
|
|
2149
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2150
|
+
networkProxyConfigFingerprint,
|
|
2151
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
2152
|
+
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2153
|
+
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
|
2154
|
+
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
|
2155
|
+
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
|
2156
|
+
contextEngine: contextEngineBinding,
|
|
2157
|
+
environmentSelectionFingerprint
|
|
2158
|
+
};
|
|
2159
|
+
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2160
|
+
kind: "patch",
|
|
2161
|
+
threadId: resumeBinding.threadId,
|
|
2162
|
+
patch: resumePatch
|
|
2163
|
+
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2164
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2165
|
+
sessionId: params.params.sessionId,
|
|
2166
|
+
sessionKey: params.params.sessionKey,
|
|
2167
|
+
threadId: response.thread.id,
|
|
2168
|
+
engineId: contextEngineBinding.engineId,
|
|
2169
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
2170
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2171
|
+
action: "resumed"
|
|
2019
2172
|
});
|
|
2020
|
-
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2021
|
-
if (!preserveExistingBinding) {
|
|
2022
|
-
if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2023
|
-
kind: "set",
|
|
2024
|
-
if: { kind: "absent" },
|
|
2025
|
-
binding: {
|
|
2026
|
-
threadId: response.thread.id,
|
|
2027
|
-
cwd: params.cwd,
|
|
2028
|
-
authProfileId: params.params.authProfileId,
|
|
2029
|
-
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2030
|
-
modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
|
|
2031
|
-
dynamicToolsFingerprint,
|
|
2032
|
-
dynamicToolsContainDeferred,
|
|
2033
|
-
webSearchThreadConfigFingerprint,
|
|
2034
|
-
userMcpServersFingerprint,
|
|
2035
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2036
|
-
ringZeroConfigFingerprint,
|
|
2037
|
-
ringZeroClientInstanceId,
|
|
2038
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2039
|
-
networkProxyConfigFingerprint,
|
|
2040
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2041
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2042
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2043
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2044
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2045
|
-
contextEngine: contextEngineBinding,
|
|
2046
|
-
environmentSelectionFingerprint
|
|
2047
|
-
}
|
|
2048
|
-
}))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
|
|
2049
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2050
|
-
sessionId: params.params.sessionId,
|
|
2051
|
-
sessionKey: params.params.sessionKey,
|
|
2052
|
-
threadId: response.thread.id,
|
|
2053
|
-
engineId: contextEngineBinding.engineId,
|
|
2054
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2055
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2056
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
2173
|
lifecycleTiming.mark("thread-ready");
|
|
2060
2174
|
lifecycleTiming.logSummary({
|
|
2061
2175
|
runId: params.params.runId,
|
|
2062
2176
|
sessionId: params.params.sessionId,
|
|
2063
2177
|
sessionKey: params.params.sessionKey,
|
|
2064
2178
|
threadId: response.thread.id,
|
|
2179
|
+
action: "resumed"
|
|
2180
|
+
});
|
|
2181
|
+
const activeTurnIds = readActiveCodexTurnIdsFromResume(response);
|
|
2182
|
+
return {
|
|
2183
|
+
...resumeBinding,
|
|
2184
|
+
threadId: response.thread.id,
|
|
2185
|
+
...resumePatch,
|
|
2186
|
+
lifecycle: {
|
|
2187
|
+
action: "resumed",
|
|
2188
|
+
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
resumeReservation?.release();
|
|
2193
|
+
if (isCodexAppServerStartSelectionChangedError(error)) throw error;
|
|
2194
|
+
if (error instanceof CodexRingZeroAttestationError) {
|
|
2195
|
+
await clearCurrentBinding("retiring a failed ring-zero thread attestation");
|
|
2196
|
+
throw error;
|
|
2197
|
+
}
|
|
2198
|
+
if (error instanceof CodexAdoptedThreadActiveError) throw error;
|
|
2199
|
+
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
2200
|
+
const resumeRejected = error instanceof CodexAppServerRpcError;
|
|
2201
|
+
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
2202
|
+
threadId: resumeBinding.threadId,
|
|
2203
|
+
timeoutMs: 5e3
|
|
2204
|
+
}) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
2205
|
+
if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
|
|
2206
|
+
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
2207
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
async function startFreshCodexThread(params, context) {
|
|
2211
|
+
const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding } = context;
|
|
2212
|
+
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2213
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2214
|
+
configPatch: params.finalConfigPatch,
|
|
2215
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2216
|
+
};
|
|
2217
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
2218
|
+
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
2219
|
+
cwd: params.cwd,
|
|
2220
|
+
dynamicTools: params.dynamicTools,
|
|
2221
|
+
appServer: params.appServer,
|
|
2222
|
+
developerInstructions: params.developerInstructions,
|
|
2223
|
+
config,
|
|
2224
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2225
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2226
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2227
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2228
|
+
environmentSelection: params.environmentSelection,
|
|
2229
|
+
model: startModelSelection.model,
|
|
2230
|
+
modelProvider: startModelProvider,
|
|
2231
|
+
hostSystemAgentActive,
|
|
2232
|
+
ringZeroInheritedMcpServerNames
|
|
2233
|
+
}));
|
|
2234
|
+
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2235
|
+
const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2236
|
+
try {
|
|
2237
|
+
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2240
|
+
throw error;
|
|
2241
|
+
}
|
|
2242
|
+
}));
|
|
2243
|
+
if (ringZeroActive) try {
|
|
2244
|
+
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2247
|
+
throw error;
|
|
2248
|
+
}
|
|
2249
|
+
throwIfAborted();
|
|
2250
|
+
const modelProvider = resolveCodexAppServerModelProvider({
|
|
2251
|
+
provider: params.params.provider,
|
|
2252
|
+
authProfileId: params.params.authProfileId,
|
|
2253
|
+
authProfileStore: params.params.authProfileStore,
|
|
2254
|
+
agentDir: params.params.agentDir,
|
|
2255
|
+
config: params.params.config
|
|
2256
|
+
});
|
|
2257
|
+
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2258
|
+
if (!preserveExistingBinding) {
|
|
2259
|
+
if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2260
|
+
kind: "set",
|
|
2261
|
+
if: { kind: "absent" },
|
|
2262
|
+
binding: {
|
|
2263
|
+
threadId: response.thread.id,
|
|
2264
|
+
cwd: params.cwd,
|
|
2265
|
+
authProfileId: params.params.authProfileId,
|
|
2266
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2267
|
+
modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
|
|
2268
|
+
dynamicToolsFingerprint,
|
|
2269
|
+
dynamicToolsContainDeferred,
|
|
2270
|
+
webSearchThreadConfigFingerprint,
|
|
2271
|
+
userMcpServersFingerprint,
|
|
2272
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2273
|
+
ringZeroConfigFingerprint,
|
|
2274
|
+
ringZeroClientInstanceId,
|
|
2275
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2276
|
+
networkProxyConfigFingerprint,
|
|
2277
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2278
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2279
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2280
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2281
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2282
|
+
contextEngine: contextEngineBinding,
|
|
2283
|
+
environmentSelectionFingerprint
|
|
2284
|
+
}
|
|
2285
|
+
}))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
|
|
2286
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2287
|
+
sessionId: params.params.sessionId,
|
|
2288
|
+
sessionKey: params.params.sessionKey,
|
|
2289
|
+
threadId: response.thread.id,
|
|
2290
|
+
engineId: contextEngineBinding.engineId,
|
|
2291
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
2292
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2065
2293
|
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2066
2294
|
});
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2295
|
+
}
|
|
2296
|
+
lifecycleTiming.mark("thread-ready");
|
|
2297
|
+
lifecycleTiming.logSummary({
|
|
2298
|
+
runId: params.params.runId,
|
|
2299
|
+
sessionId: params.params.sessionId,
|
|
2300
|
+
sessionKey: params.params.sessionKey,
|
|
2301
|
+
threadId: response.thread.id,
|
|
2302
|
+
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2303
|
+
});
|
|
2304
|
+
return {
|
|
2305
|
+
threadId: response.thread.id,
|
|
2306
|
+
cwd: params.cwd,
|
|
2307
|
+
authProfileId: params.params.authProfileId,
|
|
2308
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2309
|
+
modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
|
|
2310
|
+
dynamicToolsFingerprint,
|
|
2311
|
+
dynamicToolsContainDeferred,
|
|
2312
|
+
userMcpServersFingerprint,
|
|
2313
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2314
|
+
ringZeroConfigFingerprint,
|
|
2315
|
+
ringZeroClientInstanceId,
|
|
2316
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2317
|
+
networkProxyConfigFingerprint,
|
|
2318
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2319
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2320
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2321
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2322
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2323
|
+
contextEngine: contextEngineBinding,
|
|
2324
|
+
environmentSelectionFingerprint,
|
|
2325
|
+
lifecycle: {
|
|
2326
|
+
action: "started",
|
|
2327
|
+
...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
//#endregion
|
|
2332
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
|
|
2333
|
+
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
|
|
2334
|
+
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
|
|
2335
|
+
function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
|
|
2336
|
+
const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
|
|
2337
|
+
const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
|
|
2338
|
+
return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
|
|
2339
|
+
}
|
|
2340
|
+
function formatCodexThreadLifecycleTimingSummary(params) {
|
|
2341
|
+
const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
|
|
2342
|
+
return `[trace:codex-app-server] thread lifecycle: runId=${params.runId} sessionId=${params.sessionId} sessionKey=${params.sessionKey ?? "unknown"} action=${params.action} totalMs=${params.summary.totalMs} stages=${spans}`;
|
|
2343
|
+
}
|
|
2344
|
+
function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
2345
|
+
const log = options.log ?? embeddedAgentLog;
|
|
2346
|
+
if (!options.enabled && log.isEnabled?.("trace") !== true) return {
|
|
2347
|
+
async measure(_name, run) {
|
|
2348
|
+
return await run();
|
|
2349
|
+
},
|
|
2350
|
+
measureSync(_name, run) {
|
|
2351
|
+
return run();
|
|
2352
|
+
},
|
|
2353
|
+
mark() {},
|
|
2354
|
+
logSummary() {}
|
|
2355
|
+
};
|
|
2356
|
+
const now = options.now ?? Date.now;
|
|
2357
|
+
const startedAt = now();
|
|
2358
|
+
let didLog = false;
|
|
2359
|
+
const spans = [];
|
|
2360
|
+
const toMs = (value) => Math.max(0, Math.round(value));
|
|
2361
|
+
const record = (name, spanStartedAt) => {
|
|
2362
|
+
const currentAt = now();
|
|
2363
|
+
spans.push({
|
|
2364
|
+
name,
|
|
2365
|
+
durationMs: toMs(currentAt - spanStartedAt),
|
|
2366
|
+
elapsedMs: toMs(currentAt - startedAt)
|
|
2367
|
+
});
|
|
2368
|
+
};
|
|
2369
|
+
const snapshot = () => ({
|
|
2370
|
+
totalMs: toMs(now() - startedAt),
|
|
2371
|
+
spans: spans.slice()
|
|
2093
2372
|
});
|
|
2373
|
+
return {
|
|
2374
|
+
async measure(name, run) {
|
|
2375
|
+
const spanStartedAt = now();
|
|
2376
|
+
try {
|
|
2377
|
+
return await run();
|
|
2378
|
+
} finally {
|
|
2379
|
+
record(name, spanStartedAt);
|
|
2380
|
+
}
|
|
2381
|
+
},
|
|
2382
|
+
measureSync(name, run) {
|
|
2383
|
+
const spanStartedAt = now();
|
|
2384
|
+
try {
|
|
2385
|
+
return run();
|
|
2386
|
+
} finally {
|
|
2387
|
+
record(name, spanStartedAt);
|
|
2388
|
+
}
|
|
2389
|
+
},
|
|
2390
|
+
mark(name) {
|
|
2391
|
+
record(name, now());
|
|
2392
|
+
},
|
|
2393
|
+
logSummary(params) {
|
|
2394
|
+
if (didLog) return;
|
|
2395
|
+
const summary = snapshot();
|
|
2396
|
+
const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
|
|
2397
|
+
if (!shouldWarn && !log.isEnabled?.("trace")) return;
|
|
2398
|
+
didLog = true;
|
|
2399
|
+
const message = formatCodexThreadLifecycleTimingSummary({
|
|
2400
|
+
runId: params.runId,
|
|
2401
|
+
sessionId: params.sessionId,
|
|
2402
|
+
sessionKey: params.sessionKey,
|
|
2403
|
+
action: params.action,
|
|
2404
|
+
summary
|
|
2405
|
+
});
|
|
2406
|
+
const meta = {
|
|
2407
|
+
runId: params.runId,
|
|
2408
|
+
sessionId: params.sessionId,
|
|
2409
|
+
sessionKey: params.sessionKey,
|
|
2410
|
+
action: params.action,
|
|
2411
|
+
threadId: params.threadId,
|
|
2412
|
+
totalMs: summary.totalMs,
|
|
2413
|
+
spans: summary.spans
|
|
2414
|
+
};
|
|
2415
|
+
if (shouldWarn) log.warn(message, meta);
|
|
2416
|
+
else log.trace(message, meta);
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2094
2419
|
}
|
|
2420
|
+
//#endregion
|
|
2421
|
+
//#region extensions/codex/src/app-server/thread-supervision.ts
|
|
2095
2422
|
async function materializePendingSupervisionBranch(params) {
|
|
2096
2423
|
let pending = params.binding.pendingSupervisionBranch;
|
|
2097
2424
|
const connectionFingerprint = buildCodexAppServerConnectionFingerprint(params.appServer, params.attempt.agentDir);
|
|
@@ -2361,411 +2688,489 @@ async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
|
2361
2688
|
}
|
|
2362
2689
|
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2363
2690
|
kind: "patch-pending-supervision-branch",
|
|
2364
|
-
expected: pending,
|
|
2365
|
-
pending: next
|
|
2366
|
-
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
|
|
2367
|
-
return next;
|
|
2368
|
-
}
|
|
2369
|
-
async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
|
|
2370
|
-
const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
2371
|
-
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2372
|
-
kind: "patch-pending-supervision-branch",
|
|
2373
|
-
expected: pending,
|
|
2374
|
-
pending: next
|
|
2375
|
-
})) {
|
|
2376
|
-
const cleanupFailed = [];
|
|
2377
|
-
for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
|
|
2378
|
-
if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
|
|
2379
|
-
throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
2380
|
-
}
|
|
2381
|
-
return next;
|
|
2382
|
-
}
|
|
2383
|
-
function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
|
|
2384
|
-
return {
|
|
2385
|
-
sourceThreadId: pending.sourceThreadId,
|
|
2386
|
-
...pending.connectionFingerprint ? { connectionFingerprint: pending.connectionFingerprint } : {},
|
|
2387
|
-
...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
|
|
2388
|
-
...cleanupThreadIds.length > 0 ? { cleanupThreadIds } : {}
|
|
2389
|
-
};
|
|
2390
|
-
}
|
|
2391
|
-
async function cleanPendingSupervisionArtifacts(client, pending) {
|
|
2392
|
-
const remaining = [];
|
|
2393
|
-
for (const threadId of pending.cleanupThreadIds ?? []) if (!await archiveSupervisionArtifact(client, threadId)) remaining.push(threadId);
|
|
2394
|
-
return { remaining };
|
|
2395
|
-
}
|
|
2396
|
-
async function archiveSupervisionArtifact(client, threadId) {
|
|
2397
|
-
try {
|
|
2398
|
-
await client.request("thread/archive", { threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
|
|
2399
|
-
return true;
|
|
2400
|
-
} catch (error) {
|
|
2401
|
-
const message = formatErrorMessage(error).toLowerCase();
|
|
2402
|
-
if (message.includes("no rollout found for thread id") || message.includes("thread not found") || message.includes("already archived")) return true;
|
|
2403
|
-
await unsubscribeCodexThreadBestEffort(client, {
|
|
2404
|
-
threadId,
|
|
2405
|
-
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
2406
|
-
});
|
|
2407
|
-
embeddedAgentLog.warn("failed to archive temporary Codex supervision thread", {
|
|
2408
|
-
threadId,
|
|
2409
|
-
error
|
|
2410
|
-
});
|
|
2411
|
-
return false;
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
2415
|
-
if (!params.current) return false;
|
|
2416
|
-
if (params.binding === params.current) return false;
|
|
2417
|
-
return params.connectionClass === "remote" || Boolean(params.binding);
|
|
2418
|
-
}
|
|
2419
|
-
function resolveCodexGpt56MultiAgentVersion(modelRef) {
|
|
2420
|
-
let modelId = modelRef?.trim().toLowerCase();
|
|
2421
|
-
if (!modelId) return;
|
|
2422
|
-
const slashIndex = modelId.indexOf("/");
|
|
2423
|
-
if (slashIndex > 0) {
|
|
2424
|
-
const provider = modelId.slice(0, slashIndex);
|
|
2425
|
-
if (provider !== "openai" && provider !== "codex") return;
|
|
2426
|
-
modelId = modelId.slice(slashIndex + 1);
|
|
2427
|
-
}
|
|
2428
|
-
if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
|
|
2429
|
-
return modelId === "gpt-5.6-luna" ? "v1" : void 0;
|
|
2430
|
-
}
|
|
2431
|
-
function shouldRotateCodexGpt56MultiAgentBinding(params) {
|
|
2432
|
-
const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
|
|
2433
|
-
const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
|
|
2434
|
-
return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
|
|
2435
|
-
}
|
|
2436
|
-
function isTransientWebSearchRestriction(params) {
|
|
2437
|
-
if (params.nativeProviderWebSearchSupport === "unknown") return true;
|
|
2438
|
-
if (params.params.config?.tools?.web?.search?.enabled === false) return false;
|
|
2439
|
-
if (params.params.disableTools === true) return true;
|
|
2440
|
-
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
2441
|
-
if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
|
|
2442
|
-
if (params.webSearchAllowed !== false) return false;
|
|
2443
|
-
if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
|
|
2444
|
-
if (params.params.toolsAllow === void 0) return false;
|
|
2445
|
-
return !params.params.toolsAllow.some((name) => {
|
|
2446
|
-
const normalized = normalizeCodexDynamicToolName(name);
|
|
2447
|
-
return normalized === "*" || normalized === "web_search";
|
|
2448
|
-
});
|
|
2449
|
-
}
|
|
2450
|
-
function buildContextEngineBinding(params, projection) {
|
|
2451
|
-
const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
|
|
2452
|
-
const engineId = contextEngine?.info?.id?.trim();
|
|
2453
|
-
if (!contextEngine || !engineId) return;
|
|
2454
|
-
return {
|
|
2455
|
-
schemaVersion: 1,
|
|
2456
|
-
engineId,
|
|
2457
|
-
policyFingerprint: JSON.stringify({
|
|
2458
|
-
schemaVersion: 1,
|
|
2459
|
-
engineId,
|
|
2460
|
-
engineVersion: contextEngine.info.version,
|
|
2461
|
-
ownsCompaction: contextEngine.info.ownsCompaction === true,
|
|
2462
|
-
turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
|
|
2463
|
-
citationsMode: resolveContextEngineCitationsMode(params.config),
|
|
2464
|
-
contextTokenBudget: params.contextTokenBudget,
|
|
2465
|
-
projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
|
|
2466
|
-
contextTokenBudget: params.contextTokenBudget,
|
|
2467
|
-
reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
|
|
2468
|
-
})
|
|
2469
|
-
}),
|
|
2470
|
-
projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
|
|
2471
|
-
};
|
|
2472
|
-
}
|
|
2473
|
-
function buildContextEngineProjectionBinding(projection) {
|
|
2474
|
-
return {
|
|
2475
|
-
schemaVersion: 1,
|
|
2476
|
-
mode: "thread_bootstrap",
|
|
2477
|
-
epoch: projection.epoch,
|
|
2478
|
-
fingerprint: projection.fingerprint
|
|
2479
|
-
};
|
|
2480
|
-
}
|
|
2481
|
-
function isContextEngineBindingCompatible(previous, next) {
|
|
2482
|
-
return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
|
|
2483
|
-
}
|
|
2484
|
-
function areContextEngineProjectionBindingsCompatible(previous, next) {
|
|
2485
|
-
if (!next) return previous === void 0;
|
|
2486
|
-
return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
|
|
2487
|
-
}
|
|
2488
|
-
function resolveContextEngineCitationsMode(config) {
|
|
2489
|
-
const rootConfig = isUnknownRecord(config) ? config : void 0;
|
|
2490
|
-
const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
|
|
2491
|
-
return isJsonConfigValue(citations) ? citations : void 0;
|
|
2492
|
-
}
|
|
2493
|
-
function isUnknownRecord(value) {
|
|
2494
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2495
|
-
}
|
|
2496
|
-
function isJsonConfigValue(value) {
|
|
2497
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
2498
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
2499
|
-
if (Array.isArray(value)) return value.every(isJsonConfigValue);
|
|
2500
|
-
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
|
|
2501
|
-
}
|
|
2502
|
-
function shouldRecheckRecoverablePluginBinding(params) {
|
|
2503
|
-
if (!params.pluginThreadConfig?.enabled) return false;
|
|
2504
|
-
if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
|
|
2505
|
-
const policyContext = params.binding.pluginAppPolicyContext;
|
|
2506
|
-
if (!policyContext) return false;
|
|
2507
|
-
const expectedPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
|
|
2508
|
-
return Object.keys(policyContext.apps).length === 0 || expectedPluginConfigKeys.length > 0;
|
|
2509
|
-
}
|
|
2510
|
-
function buildThreadStartParams(params, options) {
|
|
2511
|
-
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
2512
|
-
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
2513
|
-
provider: params.provider,
|
|
2514
|
-
authProfileId: params.authProfileId,
|
|
2515
|
-
authProfileStore: params.authProfileStore,
|
|
2516
|
-
agentDir: params.agentDir,
|
|
2517
|
-
config: params.config
|
|
2518
|
-
});
|
|
2519
|
-
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
2520
|
-
model: options.model ?? params.modelId,
|
|
2521
|
-
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
2522
|
-
authProfileId: params.authProfileId,
|
|
2523
|
-
authProfileStore: params.authProfileStore,
|
|
2524
|
-
agentDir: params.agentDir,
|
|
2525
|
-
config: params.config
|
|
2526
|
-
});
|
|
2527
|
-
return {
|
|
2528
|
-
model: modelSelection.model,
|
|
2529
|
-
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
2530
|
-
cwd: options.cwd,
|
|
2531
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
2532
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2533
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2534
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2535
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2536
|
-
serviceName: "OpenClaw",
|
|
2537
|
-
...ringZeroActive ? { baseInstructions: CODEX_RING_ZERO_BASE_INSTRUCTIONS } : {},
|
|
2538
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2539
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2540
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2541
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2542
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
2543
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2544
|
-
appServer: options.appServer,
|
|
2545
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
2546
|
-
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
2547
|
-
}),
|
|
2548
|
-
...resolveCodexThreadEnvironmentSelection(options),
|
|
2549
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
2550
|
-
dynamicTools: [...options.dynamicTools],
|
|
2551
|
-
experimentalRawEvents: true
|
|
2552
|
-
};
|
|
2691
|
+
expected: pending,
|
|
2692
|
+
pending: next
|
|
2693
|
+
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
|
|
2694
|
+
return next;
|
|
2553
2695
|
}
|
|
2554
|
-
function
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
})
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2696
|
+
async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
|
|
2697
|
+
const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
2698
|
+
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2699
|
+
kind: "patch-pending-supervision-branch",
|
|
2700
|
+
expected: pending,
|
|
2701
|
+
pending: next
|
|
2702
|
+
})) {
|
|
2703
|
+
const cleanupFailed = [];
|
|
2704
|
+
for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
|
|
2705
|
+
if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
|
|
2706
|
+
throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
2707
|
+
}
|
|
2708
|
+
return next;
|
|
2709
|
+
}
|
|
2710
|
+
function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
|
|
2569
2711
|
return {
|
|
2570
|
-
|
|
2571
|
-
...
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
} : {},
|
|
2575
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
2576
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2577
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2578
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2579
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2580
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2581
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2582
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2583
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2584
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
2585
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2586
|
-
appServer: options.appServer,
|
|
2587
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
2588
|
-
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
2589
|
-
}),
|
|
2590
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
2712
|
+
sourceThreadId: pending.sourceThreadId,
|
|
2713
|
+
...pending.connectionFingerprint ? { connectionFingerprint: pending.connectionFingerprint } : {},
|
|
2714
|
+
...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
|
|
2715
|
+
...cleanupThreadIds.length > 0 ? { cleanupThreadIds } : {}
|
|
2591
2716
|
};
|
|
2592
2717
|
}
|
|
2593
|
-
function
|
|
2594
|
-
const
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
const bindingModel = params.bindingModel?.trim();
|
|
2598
|
-
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
2599
|
-
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
2718
|
+
async function cleanPendingSupervisionArtifacts(client, pending) {
|
|
2719
|
+
const remaining = [];
|
|
2720
|
+
for (const threadId of pending.cleanupThreadIds ?? []) if (!await archiveSupervisionArtifact(client, threadId)) remaining.push(threadId);
|
|
2721
|
+
return { remaining };
|
|
2600
2722
|
}
|
|
2601
|
-
function
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
2619
|
-
authProfileId,
|
|
2620
|
-
authProfileStore: params.authProfileStore,
|
|
2621
|
-
agentDir: params.agentDir,
|
|
2622
|
-
config: params.config
|
|
2623
|
-
});
|
|
2723
|
+
async function archiveSupervisionArtifact(client, threadId) {
|
|
2724
|
+
try {
|
|
2725
|
+
await client.request("thread/archive", { threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
|
|
2726
|
+
return true;
|
|
2727
|
+
} catch (error) {
|
|
2728
|
+
const message = formatErrorMessage(error).toLowerCase();
|
|
2729
|
+
if (message.includes("no rollout found for thread id") || message.includes("thread not found") || message.includes("already archived")) return true;
|
|
2730
|
+
await unsubscribeCodexThreadBestEffort(client, {
|
|
2731
|
+
threadId,
|
|
2732
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
2733
|
+
});
|
|
2734
|
+
embeddedAgentLog.warn("failed to archive temporary Codex supervision thread", {
|
|
2735
|
+
threadId,
|
|
2736
|
+
error
|
|
2737
|
+
});
|
|
2738
|
+
return false;
|
|
2739
|
+
}
|
|
2624
2740
|
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2741
|
+
//#endregion
|
|
2742
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-run.ts
|
|
2743
|
+
async function startOrResumeThread(params) {
|
|
2744
|
+
const bindingIdentity = sessionBindingIdentity({
|
|
2745
|
+
sessionId: params.params.sessionId,
|
|
2746
|
+
sessionKey: params.params.sessionKey,
|
|
2747
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
2748
|
+
config: params.params.config
|
|
2749
|
+
});
|
|
2750
|
+
return await params.bindingStore.withLease(bindingIdentity, async () => {
|
|
2751
|
+
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
2752
|
+
...params.timing,
|
|
2753
|
+
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
2754
|
+
});
|
|
2755
|
+
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
2756
|
+
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
2757
|
+
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
2758
|
+
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
2759
|
+
config: params.params.config,
|
|
2760
|
+
disableTools: params.params.disableTools,
|
|
2761
|
+
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
2762
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2763
|
+
webSearchAllowed: params.webSearchAllowed
|
|
2764
|
+
})).threadConfig);
|
|
2765
|
+
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
2766
|
+
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
2767
|
+
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
|
|
2768
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
2769
|
+
agentDir: params.params.agentDir,
|
|
2770
|
+
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
2771
|
+
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
2772
|
+
serverName,
|
|
2773
|
+
error: formatErrorMessage(error)
|
|
2774
|
+
})
|
|
2775
|
+
});
|
|
2776
|
+
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2777
|
+
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2778
|
+
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
2779
|
+
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
2780
|
+
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
2781
|
+
if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
|
|
2782
|
+
const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
2783
|
+
if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
|
|
2784
|
+
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
2785
|
+
version: 1,
|
|
2786
|
+
baseInstructions: "",
|
|
2787
|
+
config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
|
|
2788
|
+
}) : void 0;
|
|
2789
|
+
const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
|
|
2790
|
+
let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
|
|
2791
|
+
const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
|
|
2792
|
+
authProfileId,
|
|
2793
|
+
modelProvider,
|
|
2794
|
+
authProfileStore: params.params.authProfileStore,
|
|
2795
|
+
agentDir: params.params.agentDir,
|
|
2796
|
+
config: params.params.config
|
|
2797
|
+
});
|
|
2798
|
+
const throwIfAborted = () => {
|
|
2799
|
+
if (!params.signal?.aborted) return;
|
|
2800
|
+
const reason = params.signal.reason;
|
|
2801
|
+
if (reason instanceof Error) throw reason;
|
|
2802
|
+
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
2803
|
+
error.name = "AbortError";
|
|
2804
|
+
throw error;
|
|
2805
|
+
};
|
|
2806
|
+
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
2807
|
+
if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
|
|
2808
|
+
bindingStore: params.bindingStore,
|
|
2809
|
+
identity: bindingIdentity,
|
|
2810
|
+
config: params.params.config
|
|
2811
|
+
}))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
|
|
2812
|
+
}
|
|
2813
|
+
if (binding?.pendingSupervisionBranch) {
|
|
2814
|
+
const pendingBinding = binding;
|
|
2815
|
+
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2816
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2817
|
+
configPatch: params.finalConfigPatch,
|
|
2818
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2819
|
+
};
|
|
2820
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
2821
|
+
return await materializePendingSupervisionBranch({
|
|
2822
|
+
client: params.client,
|
|
2823
|
+
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
2824
|
+
bindingStore: params.bindingStore,
|
|
2825
|
+
bindingIdentity,
|
|
2826
|
+
binding: pendingBinding,
|
|
2827
|
+
attempt: params.params,
|
|
2828
|
+
cwd: params.cwd,
|
|
2829
|
+
dynamicTools: params.dynamicTools,
|
|
2830
|
+
appServer: params.appServer,
|
|
2831
|
+
developerInstructions: params.developerInstructions,
|
|
2832
|
+
config,
|
|
2833
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2834
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2835
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2836
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2837
|
+
environmentSelection: params.environmentSelection,
|
|
2838
|
+
signal: params.signal,
|
|
2839
|
+
throwIfAborted,
|
|
2840
|
+
lifecycleTiming,
|
|
2841
|
+
normalizeBindingModelProvider,
|
|
2842
|
+
bindingPatch: {
|
|
2843
|
+
cwd: params.cwd,
|
|
2844
|
+
authProfileId: void 0,
|
|
2845
|
+
preserveNativeModel: true,
|
|
2846
|
+
dynamicToolsFingerprint,
|
|
2847
|
+
dynamicToolsContainDeferred,
|
|
2848
|
+
webSearchThreadConfigFingerprint,
|
|
2849
|
+
userMcpServersFingerprint,
|
|
2850
|
+
mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
|
|
2851
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2852
|
+
networkProxyConfigFingerprint,
|
|
2853
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2854
|
+
appServerRuntimeFingerprint: buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir),
|
|
2855
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2856
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2857
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2858
|
+
contextEngine: contextEngineBinding,
|
|
2859
|
+
environmentSelectionFingerprint,
|
|
2860
|
+
conversationSourceTransferComplete: true
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
const clearCurrentBinding = async (operation) => {
|
|
2865
|
+
const current = binding;
|
|
2866
|
+
if (!current?.threadId) return;
|
|
2867
|
+
assertCodexBindingMayBeReplaced(current, operation);
|
|
2868
|
+
if (!await params.bindingStore.mutate(bindingIdentity, {
|
|
2869
|
+
kind: "clear",
|
|
2870
|
+
threadId: current.threadId
|
|
2871
|
+
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
2872
|
+
binding = void 0;
|
|
2873
|
+
};
|
|
2874
|
+
if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
|
|
2875
|
+
embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
|
|
2876
|
+
await clearCurrentBinding("rotating a ring-zero thread binding");
|
|
2877
|
+
}
|
|
2878
|
+
if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
|
|
2879
|
+
connectionClass: params.appServer.connectionClass,
|
|
2880
|
+
current: binding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2881
|
+
binding: binding.appServerRuntimeFingerprint
|
|
2882
|
+
})) {
|
|
2883
|
+
embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
|
|
2884
|
+
threadId: binding.threadId,
|
|
2885
|
+
connectionClass: params.appServer.connectionClass
|
|
2886
|
+
});
|
|
2887
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2888
|
+
binding = void 0;
|
|
2889
|
+
}
|
|
2890
|
+
if (binding?.threadId && shouldRotateCodexGpt56MultiAgentBinding({
|
|
2891
|
+
bindingModel: binding.model,
|
|
2892
|
+
requestedModel: params.params.modelId
|
|
2893
|
+
})) {
|
|
2894
|
+
embeddedAgentLog.debug("codex app-server GPT-5.6 multi-agent version changed; starting a new thread", {
|
|
2895
|
+
threadId: binding.threadId,
|
|
2896
|
+
bindingModel: binding.model,
|
|
2897
|
+
requestedModel: params.params.modelId
|
|
2898
|
+
});
|
|
2899
|
+
await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
|
|
2900
|
+
binding = void 0;
|
|
2901
|
+
}
|
|
2902
|
+
const startModelSelection = resolveCodexAppServerThreadModelSelection({
|
|
2903
|
+
provider: params.params.provider,
|
|
2904
|
+
model: params.params.modelId,
|
|
2905
|
+
binding,
|
|
2906
|
+
authProfileId: params.params.authProfileId,
|
|
2907
|
+
authProfileStore: params.params.authProfileStore,
|
|
2908
|
+
agentDir: params.params.agentDir,
|
|
2909
|
+
config: params.params.config
|
|
2910
|
+
});
|
|
2911
|
+
const startModelProvider = startModelSelection.modelProvider;
|
|
2912
|
+
const transientDelegationRestriction = params.params.delegationCapability === "report_only";
|
|
2913
|
+
let preserveExistingBinding = transientDelegationRestriction || !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
2914
|
+
let rotatedContextEngineBinding = false;
|
|
2915
|
+
let prebuiltPluginThreadConfig;
|
|
2916
|
+
const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
|
|
2917
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
2918
|
+
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
2919
|
+
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
2920
|
+
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
2921
|
+
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
2922
|
+
if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
|
|
2923
|
+
assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
|
|
2924
|
+
if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
|
|
2925
|
+
embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
|
|
2926
|
+
preserveExistingBinding = true;
|
|
2927
|
+
} else {
|
|
2928
|
+
embeddedAgentLog.debug("codex app-server MCP config changed; starting a new thread", { threadId: binding.threadId });
|
|
2929
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2930
|
+
}
|
|
2931
|
+
binding = void 0;
|
|
2932
|
+
}
|
|
2933
|
+
const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
|
|
2934
|
+
if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
|
|
2935
|
+
assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
|
|
2936
|
+
if (!ringZeroActive && transientWebSearchRestriction) {
|
|
2937
|
+
embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
|
|
2938
|
+
preserveExistingBinding = true;
|
|
2939
|
+
} else {
|
|
2940
|
+
embeddedAgentLog.debug("codex app-server web search config changed; starting a new thread", { threadId: binding.threadId });
|
|
2941
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2942
|
+
}
|
|
2943
|
+
binding = void 0;
|
|
2944
|
+
}
|
|
2945
|
+
if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
|
|
2946
|
+
assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
|
|
2947
|
+
embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
|
|
2948
|
+
preserveExistingBinding = true;
|
|
2949
|
+
binding = void 0;
|
|
2950
|
+
}
|
|
2951
|
+
if (binding?.threadId && transientDelegationRestriction) {
|
|
2952
|
+
assertCodexBindingMayBeReplaced(binding, "starting a delegation-restricted turn");
|
|
2953
|
+
embeddedAgentLog.debug("codex app-server delegation restricted for turn; starting transient thread", { threadId: binding.threadId });
|
|
2954
|
+
binding = void 0;
|
|
2955
|
+
}
|
|
2956
|
+
if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
|
|
2957
|
+
if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
|
|
2958
|
+
embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
|
|
2959
|
+
threadId: binding.threadId,
|
|
2960
|
+
engineId: contextEngineBinding?.engineId,
|
|
2961
|
+
previousEngineId: binding.contextEngine?.engineId,
|
|
2962
|
+
epoch: contextEngineBinding?.projection?.epoch,
|
|
2963
|
+
previousEpoch: binding.contextEngine?.projection?.epoch,
|
|
2964
|
+
fingerprint: contextEngineBinding?.projection?.fingerprint,
|
|
2965
|
+
previousFingerprint: binding.contextEngine?.projection?.fingerprint,
|
|
2966
|
+
policyFingerprint: contextEngineBinding?.policyFingerprint,
|
|
2967
|
+
previousPolicyFingerprint: binding.contextEngine?.policyFingerprint
|
|
2968
|
+
});
|
|
2969
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2970
|
+
binding = void 0;
|
|
2971
|
+
rotatedContextEngineBinding = true;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
if (binding?.threadId && !areUserMcpServersFingerprintsCompatible({
|
|
2975
|
+
previous: binding.userMcpServersFingerprint,
|
|
2976
|
+
next: userMcpServersFingerprint,
|
|
2977
|
+
nextLegacy: legacyUserMcpServersFingerprint
|
|
2978
|
+
})) {
|
|
2979
|
+
embeddedAgentLog.debug("codex app-server user MCP config changed; starting a new thread", { threadId: binding.threadId });
|
|
2980
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2981
|
+
binding = void 0;
|
|
2982
|
+
}
|
|
2983
|
+
if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
|
|
2984
|
+
embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
|
|
2985
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2986
|
+
binding = void 0;
|
|
2987
|
+
}
|
|
2988
|
+
if (binding?.threadId && (binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint || binding.networkProxyProfileName !== params.appServer.networkProxy?.profileName)) {
|
|
2989
|
+
embeddedAgentLog.debug("codex app-server network proxy config changed; starting a new thread", { threadId: binding.threadId });
|
|
2990
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2991
|
+
binding = void 0;
|
|
2992
|
+
}
|
|
2993
|
+
if (binding?.threadId) {
|
|
2994
|
+
let pluginBindingStale = isCodexPluginThreadBindingStale({
|
|
2995
|
+
codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
|
|
2996
|
+
bindingFingerprint: binding.pluginAppsFingerprint,
|
|
2997
|
+
bindingInputFingerprint: binding.pluginAppsInputFingerprint,
|
|
2998
|
+
currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
|
|
2999
|
+
hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
|
|
3000
|
+
});
|
|
3001
|
+
if (!pluginBindingStale && shouldRecheckRecoverablePluginBinding({
|
|
3002
|
+
binding,
|
|
3003
|
+
pluginThreadConfig: params.pluginThreadConfig
|
|
3004
|
+
})) try {
|
|
3005
|
+
prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build());
|
|
3006
|
+
pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
|
|
3007
|
+
} catch (error) {
|
|
3008
|
+
embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
|
|
3009
|
+
error,
|
|
3010
|
+
threadId: binding.threadId
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
if (pluginBindingStale) {
|
|
3014
|
+
embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
|
|
3015
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
3016
|
+
binding = void 0;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
if (binding?.threadId) {
|
|
3020
|
+
if (binding.dynamicToolsFingerprint && params.dynamicTools.length > 0 && binding.dynamicToolsContainDeferred !== dynamicToolsContainDeferred && (binding.dynamicToolsContainDeferred !== void 0 || !dynamicToolsContainDeferred)) {
|
|
3021
|
+
embeddedAgentLog.debug("codex app-server dynamic tool loading changed; starting a new thread", { threadId: binding.threadId });
|
|
3022
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
3023
|
+
binding = void 0;
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
|
|
3027
|
+
assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
|
|
3028
|
+
preserveExistingBinding = shouldStartTransientNoToolThread({
|
|
3029
|
+
previous: binding.dynamicToolsFingerprint,
|
|
3030
|
+
nextHasDynamicTools: params.dynamicTools.length > 0
|
|
3031
|
+
});
|
|
3032
|
+
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
3033
|
+
else {
|
|
3034
|
+
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
3035
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
3036
|
+
}
|
|
3037
|
+
} else {
|
|
3038
|
+
const resumed = await resumeExistingCodexThread(params, {
|
|
3039
|
+
binding,
|
|
3040
|
+
bindingIdentity,
|
|
3041
|
+
startModelSelection,
|
|
3042
|
+
startModelProvider,
|
|
3043
|
+
userMcpServersConfigPatch,
|
|
3044
|
+
dynamicToolsFingerprint,
|
|
3045
|
+
dynamicToolsContainDeferred,
|
|
3046
|
+
webSearchThreadConfigFingerprint,
|
|
3047
|
+
userMcpServersFingerprint,
|
|
3048
|
+
ringZeroConfigFingerprint,
|
|
3049
|
+
ringZeroClientInstanceId,
|
|
3050
|
+
networkProxyConfigFingerprint,
|
|
3051
|
+
contextEngineBinding,
|
|
3052
|
+
environmentSelectionFingerprint,
|
|
3053
|
+
hostSystemAgentActive,
|
|
3054
|
+
ringZeroActive,
|
|
3055
|
+
ringZeroInheritedMcpServerNames,
|
|
3056
|
+
lifecycleTiming,
|
|
3057
|
+
normalizeBindingModelProvider,
|
|
3058
|
+
throwIfAborted,
|
|
3059
|
+
clearCurrentBinding
|
|
3060
|
+
});
|
|
3061
|
+
if (resumed) return resumed;
|
|
3062
|
+
}
|
|
3063
|
+
return await startFreshCodexThread(params, {
|
|
3064
|
+
bindingIdentity,
|
|
3065
|
+
startModelSelection,
|
|
3066
|
+
startModelProvider,
|
|
3067
|
+
userMcpServersConfigPatch,
|
|
3068
|
+
dynamicToolsFingerprint,
|
|
3069
|
+
dynamicToolsContainDeferred,
|
|
3070
|
+
webSearchThreadConfigFingerprint,
|
|
3071
|
+
userMcpServersFingerprint,
|
|
3072
|
+
ringZeroConfigFingerprint,
|
|
3073
|
+
ringZeroClientInstanceId,
|
|
3074
|
+
networkProxyConfigFingerprint,
|
|
3075
|
+
contextEngineBinding,
|
|
3076
|
+
environmentSelectionFingerprint,
|
|
3077
|
+
hostSystemAgentActive,
|
|
3078
|
+
ringZeroActive,
|
|
3079
|
+
ringZeroInheritedMcpServerNames,
|
|
3080
|
+
lifecycleTiming,
|
|
3081
|
+
normalizeBindingModelProvider,
|
|
3082
|
+
throwIfAborted,
|
|
3083
|
+
prebuiltPluginThreadConfig,
|
|
3084
|
+
preserveExistingBinding,
|
|
3085
|
+
rotatedContextEngineBinding
|
|
3086
|
+
});
|
|
2640
3087
|
});
|
|
2641
|
-
return {
|
|
2642
|
-
model: model.slice(slashIndex + 1).trim(),
|
|
2643
|
-
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
2644
|
-
};
|
|
2645
|
-
}
|
|
2646
|
-
function hasProviderQualifiedModelRef(model) {
|
|
2647
|
-
const trimmed = model?.trim();
|
|
2648
|
-
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
|
2649
|
-
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
2650
|
-
}
|
|
2651
|
-
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
2652
|
-
const codeModeConfig = {
|
|
2653
|
-
...CODEX_CODE_MODE_THREAD_CONFIG,
|
|
2654
|
-
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
2655
|
-
};
|
|
2656
|
-
if (options.nativeCodeModeEnabled === false) {
|
|
2657
|
-
const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
|
|
2658
|
-
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
2659
|
-
return disabledConfig;
|
|
2660
|
-
}
|
|
2661
|
-
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, { "features.code_mode_only": true }) ?? {
|
|
2662
|
-
...codeModeConfig,
|
|
2663
|
-
"features.code_mode_only": true
|
|
2664
|
-
}, options.directOnlyToolNamespaces);
|
|
2665
|
-
return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config) ?? { ...codeModeConfig }, options.directOnlyToolNamespaces);
|
|
2666
|
-
}
|
|
2667
|
-
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
2668
|
-
if (!requiredNamespaces?.length) return config;
|
|
2669
|
-
const configured = config["code_mode.direct_only_tool_namespaces"];
|
|
2670
|
-
const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2671
|
-
return {
|
|
2672
|
-
...config,
|
|
2673
|
-
"code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
2674
|
-
};
|
|
2675
|
-
}
|
|
2676
|
-
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
2677
|
-
return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
|
|
2678
|
-
}
|
|
2679
|
-
function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
2680
|
-
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
2681
|
-
const configMcpServers = config?.mcp_servers;
|
|
2682
|
-
if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
|
|
2683
|
-
const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
2684
|
-
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
2685
|
-
config: params.config,
|
|
2686
|
-
disableTools: params.disableTools,
|
|
2687
|
-
nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
|
|
2688
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2689
|
-
webSearchAllowed: options.webSearchAllowed
|
|
2690
|
-
}).threadConfig;
|
|
2691
|
-
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
2692
|
-
const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
|
|
2693
|
-
if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
|
|
2694
|
-
return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
|
|
2695
|
-
...runtimeConfig,
|
|
2696
|
-
...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
|
|
2697
|
-
};
|
|
2698
|
-
}
|
|
2699
|
-
function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
|
|
2700
|
-
if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
|
|
2701
|
-
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
2702
|
-
return {
|
|
2703
|
-
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
2704
|
-
...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
|
|
2705
|
-
};
|
|
2706
3088
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex ring-zero does not recognize config layer ${layer.name.type}`);
|
|
2718
|
-
}
|
|
2719
|
-
const configuredServers = response.config.mcp_servers;
|
|
2720
|
-
if (configuredServers === void 0) return [];
|
|
2721
|
-
if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
|
|
2722
|
-
return Object.keys(configuredServers).toSorted();
|
|
3089
|
+
//#endregion
|
|
3090
|
+
//#region extensions/codex/src/app-server/image-payload-sanitizer.ts
|
|
3091
|
+
/**
|
|
3092
|
+
* Sanitizes inline image payloads mirrored through Codex history so invalid
|
|
3093
|
+
* base64 data becomes readable text instead of poisoning replayed transcripts.
|
|
3094
|
+
*/
|
|
3095
|
+
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
3096
|
+
/** Validates and normalizes an inline image data URL for Codex history payloads. */
|
|
3097
|
+
function sanitizeInlineImageDataUrl$1(imageUrl) {
|
|
3098
|
+
return sanitizeInlineImageDataUrl(imageUrl);
|
|
2723
3099
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
"
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
3100
|
+
/** Builds the replacement text inserted when an inline image payload is invalid. */
|
|
3101
|
+
function invalidInlineImageText(label) {
|
|
3102
|
+
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
3103
|
+
}
|
|
3104
|
+
function sanitizeImageContentRecord(record, label) {
|
|
3105
|
+
if (record.type === "image" && typeof record.data === "string") {
|
|
3106
|
+
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
|
|
3107
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
|
|
3108
|
+
if (!imageUrl) return {
|
|
3109
|
+
type: "text",
|
|
3110
|
+
text: invalidInlineImageText(label)
|
|
3111
|
+
};
|
|
3112
|
+
const commaIndex = imageUrl.indexOf(",");
|
|
3113
|
+
const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
|
|
3114
|
+
return {
|
|
3115
|
+
...record,
|
|
3116
|
+
mimeType: mime,
|
|
3117
|
+
data: imageUrl.slice(commaIndex + 1)
|
|
3118
|
+
};
|
|
2738
3119
|
}
|
|
2739
|
-
|
|
2740
|
-
const
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
3120
|
+
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
3121
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
|
|
3122
|
+
return imageUrl ? {
|
|
3123
|
+
...record,
|
|
3124
|
+
imageUrl
|
|
3125
|
+
} : {
|
|
3126
|
+
type: "inputText",
|
|
3127
|
+
text: invalidInlineImageText(label)
|
|
3128
|
+
};
|
|
2747
3129
|
}
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
const first = response.data[0];
|
|
2758
|
-
const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
|
|
2759
|
-
throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
|
|
3130
|
+
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
3131
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
|
|
3132
|
+
return imageUrl ? {
|
|
3133
|
+
...record,
|
|
3134
|
+
image_url: imageUrl
|
|
3135
|
+
} : {
|
|
3136
|
+
type: "input_text",
|
|
3137
|
+
text: invalidInlineImageText(label)
|
|
3138
|
+
};
|
|
2760
3139
|
}
|
|
2761
|
-
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
2762
3140
|
}
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
if (Array.isArray(value)) return value.
|
|
2766
|
-
if (
|
|
2767
|
-
|
|
3141
|
+
/** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
|
|
3142
|
+
function sanitizeCodexHistoryImagePayloads(value, label) {
|
|
3143
|
+
if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
|
|
3144
|
+
if (!isRecord(value)) return value;
|
|
3145
|
+
const imageRecord = sanitizeImageContentRecord(value, label);
|
|
3146
|
+
if (imageRecord) return imageRecord;
|
|
3147
|
+
const next = {};
|
|
3148
|
+
for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
|
|
3149
|
+
return next;
|
|
3150
|
+
}
|
|
3151
|
+
//#endregion
|
|
3152
|
+
//#region extensions/codex/src/app-server/user-input.ts
|
|
3153
|
+
/** Builds ordered Codex user input for both new turns and same-turn steering. */
|
|
3154
|
+
function buildCodexUserInput(text, images) {
|
|
3155
|
+
const imageInputs = (images ?? []).map((image) => {
|
|
3156
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
|
|
3157
|
+
return imageUrl ? {
|
|
3158
|
+
type: "image",
|
|
3159
|
+
url: imageUrl
|
|
3160
|
+
} : {
|
|
3161
|
+
type: "text",
|
|
3162
|
+
text: invalidInlineImageText("codex user input"),
|
|
3163
|
+
text_elements: []
|
|
3164
|
+
};
|
|
3165
|
+
});
|
|
3166
|
+
return [...text === void 0 ? [] : [{
|
|
3167
|
+
type: "text",
|
|
3168
|
+
text,
|
|
3169
|
+
text_elements: []
|
|
3170
|
+
}], ...imageInputs];
|
|
2768
3171
|
}
|
|
3172
|
+
//#endregion
|
|
3173
|
+
//#region extensions/codex/src/app-server/turn-params.ts
|
|
2769
3174
|
function buildTurnStartParams(params, options) {
|
|
2770
3175
|
const modelSelection = options.preserveNativeTurnSettings ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
2771
3176
|
model: options.model ?? params.modelId,
|
|
@@ -2778,7 +3183,7 @@ function buildTurnStartParams(params, options) {
|
|
|
2778
3183
|
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
2779
3184
|
return {
|
|
2780
3185
|
threadId: options.threadId,
|
|
2781
|
-
input:
|
|
3186
|
+
input: buildCodexUserInput(options.promptText ?? params.prompt, params.images),
|
|
2782
3187
|
cwd: options.cwd,
|
|
2783
3188
|
approvalPolicy: options.appServer.approvalPolicy,
|
|
2784
3189
|
approvalsReviewer: options.appServer.approvalsReviewer,
|
|
@@ -2799,18 +3204,6 @@ function buildTurnStartParams(params, options) {
|
|
|
2799
3204
|
}) } : {}
|
|
2800
3205
|
};
|
|
2801
3206
|
}
|
|
2802
|
-
function resolveCodexThreadApprovalsReviewer(appServer, config) {
|
|
2803
|
-
return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
|
|
2804
|
-
}
|
|
2805
|
-
function codexThreadSandboxOrPermissions(appServer) {
|
|
2806
|
-
if (appServer.networkProxy) return {};
|
|
2807
|
-
return { sandbox: appServer.sandbox };
|
|
2808
|
-
}
|
|
2809
|
-
function resolveCodexThreadEnvironmentSelection(options) {
|
|
2810
|
-
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
2811
|
-
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
2812
|
-
return {};
|
|
2813
|
-
}
|
|
2814
3207
|
function buildTurnCollaborationMode(params, options = {}) {
|
|
2815
3208
|
const model = options.model ?? params.modelId;
|
|
2816
3209
|
return {
|
|
@@ -2856,165 +3249,11 @@ function buildHeartbeatCollaborationInstructions() {
|
|
|
2856
3249
|
return [
|
|
2857
3250
|
"This is an OpenClaw heartbeat turn. Apply these instructions only to this heartbeat wake; ordinary chat turns should stay in Codex Default mode.",
|
|
2858
3251
|
"When you are ready to end the heartbeat, prefer the structured `heartbeat_respond` tool so OpenClaw can record the wake outcome and notification decision. If `heartbeat_respond` is not already available and `tool_search` is available, search for `heartbeat_respond`, load it, then call it. Use `notify=false` when nothing should visibly interrupt the user.",
|
|
2859
|
-
|
|
3252
|
+
GPT5_HEARTBEAT_PROMPT_OVERLAY
|
|
2860
3253
|
].join("\n\n");
|
|
2861
3254
|
}
|
|
2862
3255
|
function joinPresentSections(...sections) {
|
|
2863
3256
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
2864
3257
|
}
|
|
2865
|
-
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
2866
|
-
return fingerprintDynamicTools(dynamicTools);
|
|
2867
|
-
}
|
|
2868
|
-
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
2869
|
-
return legacyFingerprintDynamicTools(dynamicTools);
|
|
2870
|
-
}
|
|
2871
|
-
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
2872
|
-
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
2873
|
-
}
|
|
2874
|
-
function fingerprintDynamicTools(dynamicTools) {
|
|
2875
|
-
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
2876
|
-
}
|
|
2877
|
-
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
2878
|
-
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
2879
|
-
}
|
|
2880
|
-
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
2881
|
-
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
2882
|
-
}
|
|
2883
|
-
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
2884
|
-
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
|
|
2885
|
-
}
|
|
2886
|
-
function redactUserMcpServersFingerprintSecrets(value) {
|
|
2887
|
-
if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
|
|
2888
|
-
if (!value || typeof value !== "object") return value;
|
|
2889
|
-
const next = {};
|
|
2890
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
2891
|
-
if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
2892
|
-
next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
|
|
2893
|
-
continue;
|
|
2894
|
-
}
|
|
2895
|
-
next[key] = redactUserMcpServersFingerprintSecrets(entry);
|
|
2896
|
-
}
|
|
2897
|
-
return next;
|
|
2898
|
-
}
|
|
2899
|
-
function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
2900
|
-
return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
|
|
2901
|
-
}
|
|
2902
|
-
function fingerprintJsonObject(value) {
|
|
2903
|
-
return JSON.stringify(stabilizeJsonValue(value));
|
|
2904
|
-
}
|
|
2905
|
-
function fingerprintEnvironmentSelection(environments) {
|
|
2906
|
-
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
2907
|
-
}
|
|
2908
|
-
function fingerprintDynamicToolSpec(tool) {
|
|
2909
|
-
return stabilizeDynamicToolFingerprintValue(tool);
|
|
2910
|
-
}
|
|
2911
|
-
function stabilizeDynamicToolFingerprintValue(value) {
|
|
2912
|
-
if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
|
|
2913
|
-
if (!isJsonObject(value)) return value;
|
|
2914
|
-
const stable = {};
|
|
2915
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
2916
|
-
if (key === "description") continue;
|
|
2917
|
-
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
|
2918
|
-
}
|
|
2919
|
-
return stable;
|
|
2920
|
-
}
|
|
2921
|
-
function stabilizeJsonValue(value) {
|
|
2922
|
-
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
2923
|
-
if (!isJsonObject(value)) return value;
|
|
2924
|
-
const stable = {};
|
|
2925
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
2926
|
-
return stable;
|
|
2927
|
-
}
|
|
2928
|
-
function readActiveCodexTurnIds(thread) {
|
|
2929
|
-
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
2930
|
-
}
|
|
2931
|
-
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
2932
|
-
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
2933
|
-
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
2934
|
-
return !previous || previous === next || previous === nextLegacy;
|
|
2935
|
-
}
|
|
2936
|
-
function areUserMcpServersFingerprintsCompatible(params) {
|
|
2937
|
-
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
2938
|
-
}
|
|
2939
|
-
function shouldStartTransientNoToolThread(params) {
|
|
2940
|
-
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
2941
|
-
}
|
|
2942
|
-
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
2943
|
-
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
2944
|
-
}
|
|
2945
|
-
function compareJsonFingerprint(left, right) {
|
|
2946
|
-
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
2947
|
-
}
|
|
2948
|
-
function buildDeveloperInstructions(params, options = {}) {
|
|
2949
|
-
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
2950
|
-
surface: "codex_app_server",
|
|
2951
|
-
includeLegacyGlobalGuidance: false
|
|
2952
|
-
}).join("\n");
|
|
2953
|
-
return [
|
|
2954
|
-
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
2955
|
-
buildDeferredDynamicToolManifest(options.dynamicTools),
|
|
2956
|
-
buildSkillWorkshopInstruction(options.dynamicTools),
|
|
2957
|
-
"Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.",
|
|
2958
|
-
buildVisibleReplyInstruction(params, options.dynamicTools),
|
|
2959
|
-
nativeCommandGuidance,
|
|
2960
|
-
params.extraSystemPrompt
|
|
2961
|
-
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
2962
|
-
}
|
|
2963
|
-
function buildDeferredDynamicToolManifest(dynamicTools) {
|
|
2964
|
-
const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
|
|
2965
|
-
if (deferredToolNames.length === 0) return;
|
|
2966
|
-
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
2967
|
-
}
|
|
2968
|
-
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
2969
|
-
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
2970
|
-
return buildSkillWorkshopPromptSection().join("\n");
|
|
2971
|
-
}
|
|
2972
|
-
function buildVisibleReplyInstruction(params, dynamicTools) {
|
|
2973
|
-
const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
|
|
2974
|
-
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. Do not repeat that visible content in your final answer.";
|
|
2975
|
-
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.";
|
|
2976
|
-
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
|
|
2977
|
-
}
|
|
2978
|
-
function buildUserInput(params, promptText = params.prompt) {
|
|
2979
|
-
const imageInputs = (params.images ?? []).map((image) => {
|
|
2980
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
|
|
2981
|
-
return imageUrl ? {
|
|
2982
|
-
type: "image",
|
|
2983
|
-
url: imageUrl
|
|
2984
|
-
} : {
|
|
2985
|
-
type: "text",
|
|
2986
|
-
text: invalidInlineImageText("codex user input"),
|
|
2987
|
-
text_elements: []
|
|
2988
|
-
};
|
|
2989
|
-
});
|
|
2990
|
-
return [{
|
|
2991
|
-
type: "text",
|
|
2992
|
-
text: promptText,
|
|
2993
|
-
text_elements: []
|
|
2994
|
-
}, ...imageInputs];
|
|
2995
|
-
}
|
|
2996
|
-
function resolveCodexAppServerModelProvider(params) {
|
|
2997
|
-
const normalized = params.provider.trim();
|
|
2998
|
-
const normalizedLower = normalized.toLowerCase();
|
|
2999
|
-
if (!normalized || normalizedLower === "codex") return;
|
|
3000
|
-
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
3001
|
-
return normalizedLower === "openai" ? "openai" : normalized;
|
|
3002
|
-
}
|
|
3003
|
-
function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
|
|
3004
|
-
if (thinkLevel === "off" || thinkLevel === "adaptive") return null;
|
|
3005
|
-
if (supportedReasoningEfforts) return resolveCodexSupportedReasoningEffort({
|
|
3006
|
-
requested: thinkLevel,
|
|
3007
|
-
supportedReasoningEfforts
|
|
3008
|
-
}) ?? null;
|
|
3009
|
-
const fallbackReasoningEfforts = resolveCodexFallbackReasoningEfforts(modelId);
|
|
3010
|
-
if (fallbackReasoningEfforts) return resolveCodexSupportedReasoningEffort({
|
|
3011
|
-
requested: thinkLevel,
|
|
3012
|
-
supportedReasoningEfforts: fallbackReasoningEfforts
|
|
3013
|
-
}) ?? null;
|
|
3014
|
-
if (thinkLevel === "minimal") return isModernCodexModel(modelId) ? "low" : "minimal";
|
|
3015
|
-
if (thinkLevel === "low" || thinkLevel === "medium" || thinkLevel === "high" || thinkLevel === "xhigh") return thinkLevel;
|
|
3016
|
-
if (thinkLevel === "max" && isMaxReasoningCodexModel(modelId)) return "max";
|
|
3017
|
-
return null;
|
|
3018
|
-
}
|
|
3019
3258
|
//#endregion
|
|
3020
|
-
export {
|
|
3259
|
+
export { CodexAppServerUnsafeSubscriptionError as A, fitCodexProjectedContextForTurnStart as B, isForcedPrivateQaCodexRuntime as C, resolveCodexDynamicToolsLoadingForRuntime as D, resolveCodexDynamicToolsLoading as E, areCodexDynamicToolFingerprintsCompatible as F, buildCodexPluginThreadConfig as G, resolveCodexContextEngineProjectionMaxChars as H, codexDynamicToolsFingerprint as I, mergeCodexThreadConfigs as J, buildCodexPluginThreadConfigInputFingerprint as K, codexLegacyDynamicToolsFingerprint as L, interruptCodexTurnBestEffort as M, retireCodexAppServerClientAfterTimedOutTurn as N, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as O, unsubscribeCodexThreadBestEffort as P, resolveRecoverableCodexPluginConfigKeys as Q, buildContextEngineBinding as R, filterCodexDynamicToolsWithOpenClawShell as S, normalizeCodexDynamicToolName as T, resolveCodexContextEngineProjectionReserveTokens as U, projectContextEngineAssemblyForCodex as V, buildCodexPluginAppsConfigPatchFromPolicyContext as W, ensureCodexPluginActivation as X, shouldBuildCodexPluginThreadConfig as Y, pluginReadParams as Z, resolveCodexBindingModelProviderFallback as _, sanitizeCodexHistoryImagePayloads as a, isCodexAppServerProfilerEnabled as b, resumeCodexAppServerThread as c, resolveCodexWebSearchPlan as d, buildDeveloperInstructions as f, resolveCodexAppServerThreadModelSelection as g, resolveCodexAppServerRequestModelSelection as h, invalidInlineImageText as i, closeCodexStartupClientBestEffort as j, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as k, buildCodexRuntimeThreadConfig as l, resolveCodexAppServerModelProvider as m, buildTurnStartParams as n, sanitizeInlineImageDataUrl$1 as o, CODEX_NATIVE_PERSONALITY_NONE as p, buildCodexPluginThreadConfigTimeoutFallback as q, buildCodexUserInput as r, startOrResumeThread as s, buildTurnCollaborationMode as t, buildCodexNativeWebSearchThreadConfig as u, resolveReasoningEffort as v, isSystemAgentOnlyCodexDynamicToolAllowlist as w, filterCodexDynamicTools as x, readCodexSupportedReasoningEfforts as y, isContextEngineBindingCompatible as z };
|