@openclaw/codex 2026.9.1-beta.1 → 2026.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +5 -6
- package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-DYHcRQAa.js} +31 -45
- package/dist/attempt-context-DmQUNWwT.js +634 -0
- package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
- package/dist/{binding-connection-BMvPX73o.js → binding-connection-E6uDsPlx.js} +2 -2
- package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-wzJrwKKe.js} +15 -20
- package/dist/{capabilities-BRMh0aIq.js → capabilities-dIjNVCYH.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-DEsIufkt.js → command-formatters-CLHhEB1Q.js} +5 -4
- package/dist/{command-handlers-D_-1_qRp.js → command-handlers-CTGb2csu.js} +124 -101
- package/dist/{compact-BglNFZrQ.js → compact-_t0HQrUS.js} +38 -29
- package/dist/{config-giW8X0QA.js → config-BhmfC1s6.js} +294 -282
- package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
- package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
- package/dist/doctor-contract-api.js +143 -12
- package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BU9tmlYm.js} +599 -655
- package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
- package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
- package/dist/{event-projector-usage-Bk1I90Bg.js → event-projector-usage-C5mcrGjp.js} +15 -148
- package/dist/{harness-Csz0F-DK.js → harness-DXVZ5II5.js} +42 -52
- package/dist/harness.js +1 -1
- package/dist/index.js +458 -404
- package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-kg9kv4iH.js} +4 -4
- package/dist/launch-args-RXQwn8zV.js +90 -0
- package/dist/{managed-binary-DFYK4DWq.js → managed-binary-4-Vjz5GH.js} +14 -9
- package/dist/media-understanding-provider.js +1 -1
- package/dist/model-catalog-CUM68Hkl.js +101 -0
- package/dist/{models-TS3HBU-q.js → models-sYnEc0gd.js} +25 -28
- package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-CnH147jH.js} +55 -115
- package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-CstNHCmC.js} +9 -6
- package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-CNe-Xrjx.js} +30 -3
- package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-CfFlKqDl.js} +15 -14
- package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BFrPhb2c.js} +6 -6
- package/dist/provider-capabilities-DW5p4k9w.js +101 -0
- package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-CawQJfUS.js} +3 -2
- package/dist/{sandbox-guard-kuWTn9C5.js → request-DYAI5FQE.js} +195 -2
- package/dist/{run-attempt-DYc0QCBF.js → run-attempt-D1hJO4fX.js} +828 -2100
- package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-C3jVimFn.js} +21 -14
- package/dist/{session-binding-Bcynfd-_.js → session-binding-BHuTCRZQ.js} +198 -127
- package/dist/{session-catalog-DaLKVtpH.js → session-catalog-yyvTWFOw.js} +564 -408
- package/dist/{session-cli-DMc4oS2w.js → session-cli-CmFEXMAM.js} +1 -1
- package/dist/session-history-C5J1AKzG.js +87 -0
- package/dist/session-initialization-BeWzjGiR.js +74 -0
- package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-DeWVTCMj.js} +2 -2
- package/dist/session-retirement-BujDjXVF.js +97 -0
- package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-C351-5Qw.js} +19 -4
- package/dist/settled-turn-context-D3SNWtUn.js +268 -0
- package/dist/settled-turn-finalizer-BVJx4yoc.js +104 -0
- package/dist/{shared-client-DKz23hHY.js → shared-client-DxcfF9KX.js} +3480 -679
- package/dist/{side-question-DjA_jsNe.js → side-question-BTZAII4l.js} +108 -70
- package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DAcEJ_Om.js} +2188 -1915
- package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-zGokd8i1.js} +25 -14
- package/dist/thread-policy-g4KbJPzp.js +54 -0
- package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-C7pyVKeW.js} +59 -7
- package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-DTUxFWa_.js} +79 -17
- package/dist/transport-stdio-CyPldczm.js +726 -0
- package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
- package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
- package/dist/{usage-ROIaW3wo.js → usage-Bj-rrzkR.js} +3 -3
- package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
- package/openclaw.plugin.json +1 -30
- package/package.json +6 -6
- package/dist/command-rpc-CCeCkf_v.js +0 -90
- package/dist/incognito-session-ET_ufDH-.js +0 -2
- package/dist/model-catalog-i9rNBthX.js +0 -48
- package/dist/protocol-validators-VeJxxJ0U.js +0 -2421
- package/dist/request-DXXGHko0.js +0 -157
- package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
- package/dist/session-retirement-CcNC3YSY.js +0 -36
- package/dist/settled-turn-finalizer-Dc3ApjU4.js +0 -328
- package/dist/thread-resume-BE9qNEW0.js +0 -30
- package/dist/transport-stdio-DhoGy33Z.js +0 -99
- package/dist/upstream-session-fork-DlaHxSLC.js +0 -326
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import {
|
|
3
|
-
import { a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-
|
|
4
|
-
import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, d as unsubscribeCodexThreadBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { n as
|
|
9
|
-
import { t as resumeCodexAppServerThread } from "./thread-resume-BE9qNEW0.js";
|
|
10
|
-
import { u as projectBoundedCodexThreadHistory } from "./transcript-mirror-DsCMWBca.js";
|
|
1
|
+
import { $t as isJsonObject, E as CodexThreadStartRequestError, Et as isCodexAppServerLiveThreadClaimed, H as isCodexAppServerOverloadError, I as getCodexAppServerClientInstanceId, Jt as readCodexSessionMeta, K as resolveCodexAppServerClientInstanceId, Mt as withAbortableTimeout, Ot as readCodexClientSessionMeta, Q as assertCodexThreadForkResponse, Qt as flattenCodexDynamicToolFunctions, St as consumeCodexAppServerLiveThread, T as CodexThreadBindingConflictError, Tt as isCodexAppServerClientRuntimeLive, W as isCodexAppServerRequestTimeoutError, X as assertCodexThreadAcceptsDirectInput, _ as retainSharedCodexAppServerClientByInstanceId, _t as resolveCodexToolAbortTerminalReason, et as assertCodexThreadStartResponse, jt as unsubscribeCodexAppServerLiveThread, kt as releaseCodexAppServerLiveThread, n as captureCodexAppServerClientLifetime, q as CodexAppServerRpcError, w as CodexAdoptedThreadActiveError } from "./shared-client-DxcfF9KX.js";
|
|
2
|
+
import { D as resolveCodexPluginsPolicy, F as resolveCodexAppServerHomeDir, I as resolveCodexAppServerLocalHomeDir, M as CODEX_PLUGINS_MARKETPLACE_NAME, N as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, n as codexSandboxPolicyForTurn, p as assertCodexModelBackedReviewerEffectiveConfig } from "./config-BhmfC1s6.js";
|
|
3
|
+
import { a as hashCodexAppServerBindingFingerprint, c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-BHuTCRZQ.js";
|
|
4
|
+
import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, d as unsubscribeCodexThreadBestEffort, l as retireUnsafeCodexTurnClientBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-DYHcRQAa.js";
|
|
5
|
+
import { c as serializeCodexAppInventoryError, n as buildCodexAppServerConnectionFingerprint, s as defaultCodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-C7pyVKeW.js";
|
|
6
|
+
import { c as withExclusiveCodexAppServerThread, l as isIncognitoSessionKey, o as withCodexAppServerThreadMutation, r as retainCodexAppServerBindingSubscription } from "./thread-ownership-zGokd8i1.js";
|
|
7
|
+
import { f as projectBoundedCodexThreadHistory, g as codexCatalogHomeId } from "./transcript-mirror-DTUxFWa_.js";
|
|
8
|
+
import { i as refreshCodexThreadPolicy, n as CodexThreadPolicyHandoffError, r as assertCodexSupervisionThreadLineage, t as CodexIncognitoPolicyChangeError } from "./thread-policy-g4KbJPzp.js";
|
|
11
9
|
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
|
|
12
10
|
import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
11
|
import fs from "node:fs/promises";
|
|
14
12
|
import path from "node:path";
|
|
15
13
|
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
16
|
-
import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds
|
|
17
|
-
import { SKILL_WORKSHOP_TOOL_NAME,
|
|
14
|
+
import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
|
15
|
+
import { AgentHarnessPreflightError, SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, buildTemporalContextText, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
16
|
+
import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
18
17
|
import * as crypto$1 from "node:crypto";
|
|
19
18
|
import crypto, { createHash } from "node:crypto";
|
|
20
19
|
import os from "node:os";
|
|
21
|
-
import {
|
|
20
|
+
import { isDeepStrictEqual } from "node:util";
|
|
21
|
+
import { resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
|
|
22
|
+
import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
22
23
|
import { z } from "zod";
|
|
23
24
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
24
25
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
26
|
+
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
27
|
+
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
25
28
|
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
26
|
-
import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
27
29
|
import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
30
|
+
import { buildCodexUserMcpServersThreadConfigPatchForRun } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
31
|
+
import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
|
|
32
|
+
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-flags";
|
|
28
33
|
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
29
34
|
import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
|
|
30
|
-
import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
31
|
-
import { emitTrustedDiagnosticEvent, hasPendingInternalDiagnosticEvent, isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
32
|
-
import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
|
|
33
35
|
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
34
36
|
/**
|
|
35
37
|
* Reads Codex plugin marketplace state and app inventory to decide which
|
|
@@ -272,15 +274,34 @@ function resolveOwnedApps(params) {
|
|
|
272
274
|
enabled: false,
|
|
273
275
|
needsAuth: true
|
|
274
276
|
};
|
|
275
|
-
return {
|
|
277
|
+
return Object.assign({
|
|
276
278
|
id: app.id,
|
|
277
279
|
name: app.name,
|
|
278
280
|
accessible: info.isAccessible,
|
|
279
281
|
enabled: info.isEnabled,
|
|
280
282
|
needsAuth: !info.isAccessible
|
|
281
|
-
};
|
|
283
|
+
}, resolveOwnedAppApprovalOverrideKeys(info));
|
|
282
284
|
}).toSorted((left, right) => left.id.localeCompare(right.id));
|
|
283
285
|
}
|
|
286
|
+
/** Returns current tool keys whose overrides could bypass the requested reviewer. */
|
|
287
|
+
function resolveOwnedAppApprovalOverrideKeys(app) {
|
|
288
|
+
if (!app.toolSummaries) return {};
|
|
289
|
+
const appName = app.name.trim();
|
|
290
|
+
const appNameLower = appName.toLowerCase();
|
|
291
|
+
const keys = app.toolSummaries.filter((tool) => !tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
|
|
292
|
+
appName,
|
|
293
|
+
appNameLower,
|
|
294
|
+
tool
|
|
295
|
+
}));
|
|
296
|
+
return { approvalOverrideToolConfigKeys: Array.from(new Set(keys)).toSorted() };
|
|
297
|
+
}
|
|
298
|
+
function resolveAppToolConfigKeys(params) {
|
|
299
|
+
const keys = [params.tool.name];
|
|
300
|
+
if (params.tool.title) keys.push(params.tool.title);
|
|
301
|
+
if (params.appName) keys.push(`${params.appName}_${params.tool.name}`);
|
|
302
|
+
if (params.appNameLower && params.appNameLower !== params.appName) keys.push(`${params.appNameLower}_${params.tool.name}`);
|
|
303
|
+
return keys;
|
|
304
|
+
}
|
|
284
305
|
function findPluginSummary(marketplace, pluginName) {
|
|
285
306
|
const exact = marketplace.plugins.find((plugin) => plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}`);
|
|
286
307
|
if (exact) return exact;
|
|
@@ -538,7 +559,8 @@ function toCodexPluginOwnedAccountApp(app) {
|
|
|
538
559
|
name: app.name,
|
|
539
560
|
accessible: app.isAccessible,
|
|
540
561
|
enabled: app.isEnabled,
|
|
541
|
-
needsAuth: !app.isAccessible
|
|
562
|
+
needsAuth: !app.isAccessible,
|
|
563
|
+
...resolveOwnedAppApprovalOverrideKeys(app)
|
|
542
564
|
};
|
|
543
565
|
}
|
|
544
566
|
function resolveCodexThreadConfigAppsForRecord(params) {
|
|
@@ -595,7 +617,7 @@ function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
|
|
|
595
617
|
* Builds Codex thread config patches that expose only policy-approved apps
|
|
596
618
|
* for native Codex turns.
|
|
597
619
|
*/
|
|
598
|
-
const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION =
|
|
620
|
+
const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 4;
|
|
599
621
|
const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
|
|
600
622
|
/** Returns true when plugin config exists and thread config may need app patches. */
|
|
601
623
|
function shouldBuildCodexPluginThreadConfig(pluginConfig) {
|
|
@@ -968,10 +990,11 @@ function readPersistedAppToolApprovalOverrideNames(config, app) {
|
|
|
968
990
|
const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
|
|
969
991
|
const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
|
|
970
992
|
if (!isJsonObject(tools)) return [];
|
|
971
|
-
|
|
993
|
+
const keys = app.approvalOverrideToolConfigKeys;
|
|
994
|
+
return Object.entries(tools).flatMap(([name, value]) => (!keys || keys.includes(name)) && hasPersistedToolApprovalOverride(value) ? [name] : []).toSorted();
|
|
972
995
|
}
|
|
973
996
|
function hasPersistedToolApprovalOverride(value) {
|
|
974
|
-
return isJsonObject(value) && value.approval_mode !== void 0;
|
|
997
|
+
return isJsonObject(value) && value.approval_mode !== void 0 && value.approval_mode !== null;
|
|
975
998
|
}
|
|
976
999
|
function quoteConfigKeyPathSegment(segment) {
|
|
977
1000
|
return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
|
|
@@ -1015,11 +1038,11 @@ function mergeJsonObjects(left, right) {
|
|
|
1015
1038
|
return merged;
|
|
1016
1039
|
}
|
|
1017
1040
|
function fingerprintJson(value) {
|
|
1018
|
-
return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
1041
|
+
return crypto.createHash("sha256").update(stableStringify$1(value)).digest("hex");
|
|
1019
1042
|
}
|
|
1020
|
-
function stableStringify(value) {
|
|
1021
|
-
if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
1022
|
-
if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
|
|
1043
|
+
function stableStringify$1(value) {
|
|
1044
|
+
if (Array.isArray(value)) return `[${value.map((item) => stableStringify$1(item)).join(",")}]`;
|
|
1045
|
+
if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify$1(item)}`).join(",")}}`;
|
|
1023
1046
|
return JSON.stringify(value);
|
|
1024
1047
|
}
|
|
1025
1048
|
//#endregion
|
|
@@ -1170,6 +1193,7 @@ function renderMessagesForCodexContext(messages, options) {
|
|
|
1170
1193
|
}).filter((value) => Boolean(value)).join("\n\n");
|
|
1171
1194
|
}
|
|
1172
1195
|
function renderMessageBody(message, options) {
|
|
1196
|
+
if (message.role === "compactionSummary" || message.role === "branchSummary") return truncateText(message.summary.trim(), options.maxTextPartChars);
|
|
1173
1197
|
if (!hasMessageContent(message)) return "";
|
|
1174
1198
|
if (typeof message.content === "string") return truncateText(message.content.trim(), options.maxTextPartChars);
|
|
1175
1199
|
if (!Array.isArray(message.content)) return "[non-text content omitted]";
|
|
@@ -1638,7 +1662,8 @@ async function collectPersonalSkillRealPaths(homes, codexHome) {
|
|
|
1638
1662
|
}
|
|
1639
1663
|
const configuredCodexHome = codexHome?.trim() || process.env.CODEX_HOME?.trim();
|
|
1640
1664
|
if (configuredCodexHome) {
|
|
1641
|
-
const
|
|
1665
|
+
const realCodexHome = await canonicalizeExistingPath(configuredCodexHome);
|
|
1666
|
+
const stateOwned = isPathInside(realStateDir, realCodexHome);
|
|
1642
1667
|
roots.push({
|
|
1643
1668
|
dir: path.join(configuredCodexHome, "skills"),
|
|
1644
1669
|
onlyEscapedStateTargets: stateOwned
|
|
@@ -1701,12 +1726,14 @@ async function collectPersonalSkillRealPaths(homes, codexHome) {
|
|
|
1701
1726
|
try {
|
|
1702
1727
|
const stat = await fs.stat(entryPath);
|
|
1703
1728
|
if (entry.name === "SKILL.md" && stat.isFile()) await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
|
|
1704
|
-
else if (stat.isDirectory())
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1729
|
+
else if (stat.isDirectory()) {
|
|
1730
|
+
if (current.depth < MAX_PERSONAL_SKILL_DEPTH) queue.push({
|
|
1731
|
+
dir: entryPath,
|
|
1732
|
+
depth: current.depth + 1,
|
|
1733
|
+
onlyEscapedStateTargets: current.onlyEscapedStateTargets
|
|
1734
|
+
});
|
|
1735
|
+
else complete = false;
|
|
1736
|
+
}
|
|
1710
1737
|
} catch (error) {
|
|
1711
1738
|
if (!isMissingPathError(error)) complete = false;
|
|
1712
1739
|
}
|
|
@@ -1796,32 +1823,64 @@ function applyCodexNativeSkillIsolation(config, isolation) {
|
|
|
1796
1823
|
};
|
|
1797
1824
|
}
|
|
1798
1825
|
//#endregion
|
|
1799
|
-
//#region extensions/codex/src/app-server/
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
}
|
|
1826
|
+
//#region extensions/codex/src/app-server/native-tool-catalog.ts
|
|
1827
|
+
function hasCodexNativeToolCatalog(binding) {
|
|
1828
|
+
return binding?.connectionScope === "supervision" && !binding.pendingSupervisionBranch;
|
|
1829
|
+
}
|
|
1830
|
+
/** Pinned serde omits false deferLoading; every other declaration byte remains native-owned. */
|
|
1831
|
+
function parseCodexNativeToolCatalog(metadata, threadId, fingerprint) {
|
|
1832
|
+
const fail = () => /* @__PURE__ */ new Error("The canonical Codex native tool catalog is missing, corrupt, or changed; the thread is preserved. Reconnect and inspect its native metadata before retrying.");
|
|
1833
|
+
if (!isJsonObject(metadata) || metadata.id !== threadId || !Array.isArray(metadata.dynamic_tools) || Buffer.byteLength(JSON.stringify(metadata.dynamic_tools)) > 1048576) throw fail();
|
|
1834
|
+
const names = /* @__PURE__ */ new Set();
|
|
1835
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
1836
|
+
const validName = (name) => typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/u.test(name);
|
|
1837
|
+
const readFunction = (value) => {
|
|
1838
|
+
if (!isJsonObject(value) || value.type !== "function" || !validName(value.name) || typeof value.description !== "string" || !isJsonObject(value.inputSchema) || value.deferLoading !== void 0 && typeof value.deferLoading !== "boolean" || Object.keys(value).some((key) => ![
|
|
1839
|
+
"type",
|
|
1840
|
+
"name",
|
|
1841
|
+
"description",
|
|
1842
|
+
"inputSchema",
|
|
1843
|
+
"deferLoading"
|
|
1844
|
+
].includes(key)) || names.has(value.name) || names.size >= 2e3) throw fail();
|
|
1845
|
+
names.add(value.name);
|
|
1846
|
+
return {
|
|
1847
|
+
type: "function",
|
|
1848
|
+
name: value.name,
|
|
1849
|
+
description: value.description,
|
|
1850
|
+
inputSchema: structuredClone(value.inputSchema),
|
|
1851
|
+
...value.deferLoading === true ? { deferLoading: true } : {}
|
|
1852
|
+
};
|
|
1853
|
+
};
|
|
1854
|
+
const tools = metadata.dynamic_tools.map((value) => {
|
|
1855
|
+
if (!isJsonObject(value) || value.type !== "namespace") return readFunction(value);
|
|
1856
|
+
if (!validName(value.name) || namespaces.has(value.name) || typeof value.description !== "string" || !Array.isArray(value.tools) || !value.tools.length || Object.keys(value).some((key) => ![
|
|
1857
|
+
"type",
|
|
1858
|
+
"name",
|
|
1859
|
+
"description",
|
|
1860
|
+
"tools"
|
|
1861
|
+
].includes(key))) throw fail();
|
|
1862
|
+
namespaces.add(value.name);
|
|
1863
|
+
return {
|
|
1864
|
+
type: "namespace",
|
|
1865
|
+
name: value.name,
|
|
1866
|
+
description: value.description,
|
|
1867
|
+
tools: value.tools.map(readFunction)
|
|
1868
|
+
};
|
|
1869
|
+
});
|
|
1870
|
+
if (fingerprint !== void 0 && codexDynamicToolsFingerprint(tools) !== fingerprint) throw fail();
|
|
1871
|
+
return tools;
|
|
1872
|
+
}
|
|
1873
|
+
/** Existing supervised bindings identify data, never authorize an executor or a new adoption. */
|
|
1874
|
+
async function loadCodexNativeToolCatalog(params) {
|
|
1875
|
+
const { binding, client, appServer, agentDir, assertCurrent } = params;
|
|
1876
|
+
assertCurrent();
|
|
1877
|
+
const home = resolveCodexAppServerLocalHomeDir(appServer.start, agentDir);
|
|
1878
|
+
const actualHome = client.getRuntimeIdentity()?.codexHome;
|
|
1879
|
+
if (!hasCodexNativeToolCatalog(binding) || !binding.dynamicToolsFingerprint || appServer.start.transport !== "stdio" || appServer.remoteWorkspaceRoot || !actualHome || path.resolve(actualHome) !== path.resolve(home) || binding.appServerRuntimeFingerprint !== buildCodexAppServerConnectionFingerprint(appServer, agentDir)) throw new Error("Canonical Codex declarations require the original verified local binding and selected native connection; the thread is preserved.");
|
|
1880
|
+
const metadata = await readCodexClientSessionMeta(client, path.join(home, "sessions"), binding.rolloutPath, binding.threadId);
|
|
1881
|
+
assertCurrent();
|
|
1882
|
+
return parseCodexNativeToolCatalog(metadata, binding.threadId, binding.dynamicToolsFingerprint);
|
|
1883
|
+
}
|
|
1825
1884
|
//#endregion
|
|
1826
1885
|
//#region extensions/codex/src/app-server/managed-thread-store.ts
|
|
1827
1886
|
const CODEX_MANAGED_THREAD_NAMESPACE = "app-server-managed-threads";
|
|
@@ -1851,6 +1910,10 @@ function managedThreadStoreKey(sourceHomeId, threadId) {
|
|
|
1851
1910
|
/** Durable ownership index for Codex threads created by OpenClaw. */
|
|
1852
1911
|
function createCodexManagedThreadStore(state) {
|
|
1853
1912
|
return {
|
|
1913
|
+
async has(sourceHomeId, threadId) {
|
|
1914
|
+
const parsed = managedThreadSchema.safeParse(state.lookup(managedThreadStoreKey(sourceHomeId, threadId)));
|
|
1915
|
+
return parsed.success && parsed.data.sourceHomeId === sourceHomeId && parsed.data.threadId === threadId;
|
|
1916
|
+
},
|
|
1854
1917
|
async mark(params) {
|
|
1855
1918
|
try {
|
|
1856
1919
|
const value = managedThreadSchema.parse({
|
|
@@ -1881,11 +1944,59 @@ function createCodexManagedThreadStore(state) {
|
|
|
1881
1944
|
};
|
|
1882
1945
|
}
|
|
1883
1946
|
//#endregion
|
|
1947
|
+
//#region extensions/codex/src/app-server/thread-mcp-attestation.ts
|
|
1948
|
+
async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal, expectedActiveServerNames = []) {
|
|
1949
|
+
const configuredServers = threadConfig?.mcp_servers;
|
|
1950
|
+
if (configuredServers !== void 0 && !isJsonObject(configuredServers)) throw new Error("Codex restricted-tool-surface thread config has invalid mcp_servers");
|
|
1951
|
+
const expectedServers = /* @__PURE__ */ new Map();
|
|
1952
|
+
for (const [name, serverConfig] of Object.entries(configuredServers ?? {})) {
|
|
1953
|
+
if (!isJsonObject(serverConfig) || serverConfig.enabled !== false) throw new Error(`Codex restricted-tool-surface MCP server ${name} is not disabled`);
|
|
1954
|
+
expectedServers.set(name, "disabled");
|
|
1955
|
+
}
|
|
1956
|
+
for (const name of expectedActiveServerNames) {
|
|
1957
|
+
if (expectedServers.get(name) === "disabled") throw new Error(`Codex restricted-tool-surface MCP server ${name} has conflicting policy`);
|
|
1958
|
+
expectedServers.set(name, "active");
|
|
1959
|
+
}
|
|
1960
|
+
const response = await client.request("mcpServerStatus/list", {
|
|
1961
|
+
threadId,
|
|
1962
|
+
detail: "toolsAndAuthOnly"
|
|
1963
|
+
}, { signal });
|
|
1964
|
+
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
|
|
1965
|
+
const observedServerNames = /* @__PURE__ */ new Set();
|
|
1966
|
+
for (const status of response.data) {
|
|
1967
|
+
if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
|
|
1968
|
+
if (!expectedServers.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
|
|
1969
|
+
if (observedServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
|
|
1970
|
+
observedServerNames.add(status.name);
|
|
1971
|
+
if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
|
|
1972
|
+
if (expectedServers.get(status.name) === "active") {
|
|
1973
|
+
if (status.serverInfo === null || Object.keys(status.tools).length === 0) throw new Error(`Codex restricted-tool-surface MCP attestation found inactive admitted server ${status.name}`);
|
|
1974
|
+
continue;
|
|
1975
|
+
}
|
|
1976
|
+
if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
|
|
1977
|
+
if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
|
|
1978
|
+
}
|
|
1979
|
+
for (const [expectedName, state] of expectedServers) if (!observedServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing ${state === "active" ? "admitted " : ""}server ${expectedName}`);
|
|
1980
|
+
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
1981
|
+
}
|
|
1982
|
+
//#endregion
|
|
1884
1983
|
//#region extensions/codex/src/app-server/plugin-thread-attestation.ts
|
|
1885
1984
|
/**
|
|
1886
1985
|
* Confirms admitted plugin and account apps against their actual Codex thread before
|
|
1887
1986
|
* OpenClaw commits a binding or starts a turn.
|
|
1888
1987
|
*/
|
|
1988
|
+
/** Every admission path checks the same surface; its lifecycle owner keeps the claim fenced. */
|
|
1989
|
+
async function attestCodexThreadToolSurface(params) {
|
|
1990
|
+
params.assertCurrent();
|
|
1991
|
+
if (params.appIds.length > 0) {
|
|
1992
|
+
await params.lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps(params));
|
|
1993
|
+
params.assertCurrent();
|
|
1994
|
+
}
|
|
1995
|
+
if (params.restrictedToolSurface) {
|
|
1996
|
+
await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, params.threadId, params.threadConfig, params.signal, params.appIds.length > 0 ? ["codex_apps"] : []));
|
|
1997
|
+
params.assertCurrent();
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
1889
2000
|
var CodexPluginThreadAppAttestationError = class extends Error {
|
|
1890
2001
|
constructor(message, options) {
|
|
1891
2002
|
super(message, options);
|
|
@@ -1936,213 +2047,506 @@ async function discardUnattestedCodexPluginThread(params) {
|
|
|
1936
2047
|
}
|
|
1937
2048
|
}
|
|
1938
2049
|
//#endregion
|
|
1939
|
-
//#region extensions/codex/src/app-server/
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
userMcpServersFingerprint: context.userMcpServersFingerprint,
|
|
1956
|
-
mcpServersFingerprint: input.nextMcpServersFingerprint,
|
|
1957
|
-
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
1958
|
-
ringZeroConfigFingerprint: context.ringZeroConfigFingerprint,
|
|
1959
|
-
ringZeroClientInstanceId: context.ringZeroClientInstanceId,
|
|
1960
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
1961
|
-
networkProxyConfigFingerprint: context.networkProxyConfigFingerprint,
|
|
1962
|
-
nativeHookRelayGeneration: input.finalConfigPatch.nativeHookRelayGeneration,
|
|
1963
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
1964
|
-
pluginAppsFingerprint: input.pluginThreadConfig?.fingerprint,
|
|
1965
|
-
pluginAppsInputFingerprint: input.pluginThreadConfig?.inputFingerprint,
|
|
1966
|
-
pluginAppPolicyContext: input.pluginThreadConfig?.policyContext,
|
|
1967
|
-
contextEngine: context.contextEngineBinding,
|
|
1968
|
-
environmentSelectionFingerprint: context.environmentSelectionFingerprint,
|
|
1969
|
-
...!context.preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
|
|
1970
|
-
...startParams,
|
|
1971
|
-
model: response.model ?? startParams.model ?? null,
|
|
1972
|
-
requestedModel: startParams.model ?? null,
|
|
1973
|
-
modelProvider: input.bindingModelProvider ?? null,
|
|
1974
|
-
requestedModelProvider: startParams.modelProvider ?? input.bindingModelProvider ?? null
|
|
1975
|
-
}, params.params.authProfileId, context.dynamicToolsFingerprint) } : {},
|
|
1976
|
-
lifecycle: {
|
|
1977
|
-
action: "started",
|
|
1978
|
-
...context.rotatedContextEngineBinding ? { rotatedContextEngineBinding: true } : {}
|
|
2050
|
+
//#region extensions/codex/src/app-server/native-hook-relay-state.ts
|
|
2051
|
+
const pending = /* @__PURE__ */ new Set();
|
|
2052
|
+
/** Owns delayed hook-relay cleanup across runtime scheduling and test teardown. */
|
|
2053
|
+
const nativeHookRelayUnregisterQueue = {
|
|
2054
|
+
add(entry) {
|
|
2055
|
+
pending.add(entry);
|
|
2056
|
+
},
|
|
2057
|
+
delete(entry) {
|
|
2058
|
+
return pending.delete(entry);
|
|
2059
|
+
},
|
|
2060
|
+
flush() {
|
|
2061
|
+
while (pending.size > 0) {
|
|
2062
|
+
const entry = pending.values().next().value;
|
|
2063
|
+
if (!entry) return;
|
|
2064
|
+
clearTimeout(entry.timeout);
|
|
2065
|
+
entry.unregister();
|
|
1979
2066
|
}
|
|
1980
|
-
}
|
|
1981
|
-
|
|
2067
|
+
},
|
|
2068
|
+
clear() {
|
|
2069
|
+
for (const entry of pending) clearTimeout(entry.timeout);
|
|
2070
|
+
pending.clear();
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
1982
2073
|
//#endregion
|
|
1983
|
-
//#region extensions/codex/src/app-server/
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
"
|
|
1991
|
-
"
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
"medium",
|
|
1995
|
-
"high",
|
|
1996
|
-
"xhigh"
|
|
2074
|
+
//#region extensions/codex/src/app-server/native-hook-relay.ts
|
|
2075
|
+
/**
|
|
2076
|
+
* Bridges Codex native hook callbacks into OpenClaw's native hook relay so
|
|
2077
|
+
* app-server tool events can still run OpenClaw policy and diagnostics.
|
|
2078
|
+
*/
|
|
2079
|
+
/** Codex hook events that can be registered through OpenClaw's native relay. */
|
|
2080
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
|
|
2081
|
+
"pre_tool_use",
|
|
2082
|
+
"post_tool_use",
|
|
2083
|
+
"permission_request",
|
|
2084
|
+
"before_agent_finalize"
|
|
1997
2085
|
];
|
|
1998
|
-
const
|
|
1999
|
-
const
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2086
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
|
|
2087
|
+
const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 18e5;
|
|
2088
|
+
/** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
|
|
2089
|
+
const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 3e5;
|
|
2090
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
|
|
2091
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
|
|
2092
|
+
const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
|
|
2093
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
|
|
2094
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
|
|
2095
|
+
const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
|
|
2096
|
+
const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
|
|
2097
|
+
const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
|
|
2098
|
+
exec: [
|
|
2099
|
+
"Bash",
|
|
2100
|
+
"exec",
|
|
2101
|
+
"exec_command"
|
|
2102
|
+
],
|
|
2103
|
+
apply_patch: [
|
|
2104
|
+
"apply_patch",
|
|
2105
|
+
"Write",
|
|
2106
|
+
"Edit"
|
|
2107
|
+
],
|
|
2108
|
+
spawn_agent: ["spawn_agent", "Agent"]
|
|
2109
|
+
};
|
|
2110
|
+
/** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
|
|
2111
|
+
async function assertCodexNativeHookRelayAllowed(client, signal) {
|
|
2112
|
+
let attestation = nativeHookPolicyByClient.get(client);
|
|
2113
|
+
if (!attestation) {
|
|
2114
|
+
attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
|
|
2115
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
|
|
2116
|
+
const requirements = response.requirements;
|
|
2117
|
+
if (requirements === null) return;
|
|
2118
|
+
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
|
|
2119
|
+
const managedOnly = requirements.allowManagedHooksOnly;
|
|
2120
|
+
if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
|
|
2121
|
+
if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
|
|
2122
|
+
});
|
|
2123
|
+
nativeHookPolicyByClient.set(client, attestation);
|
|
2124
|
+
attestation.catch(() => {
|
|
2125
|
+
if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
await attestation;
|
|
2011
2129
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2130
|
+
/** Defers relay unregister so late native hook subprocesses can still resolve. */
|
|
2131
|
+
function scheduleCodexNativeHookRelayUnregister(params) {
|
|
2132
|
+
let pending;
|
|
2133
|
+
const unregister = () => {
|
|
2134
|
+
if (!pending) return;
|
|
2135
|
+
const current = pending;
|
|
2136
|
+
pending = void 0;
|
|
2137
|
+
if (!nativeHookRelayUnregisterQueue.delete(current)) return;
|
|
2138
|
+
params.relay.unregister();
|
|
2139
|
+
};
|
|
2140
|
+
const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
|
|
2141
|
+
pending = {
|
|
2142
|
+
timeout,
|
|
2143
|
+
unregister
|
|
2144
|
+
};
|
|
2145
|
+
nativeHookRelayUnregisterQueue.add(pending);
|
|
2146
|
+
timeout.unref();
|
|
2018
2147
|
}
|
|
2019
|
-
/**
|
|
2020
|
-
function
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
requested: params.thinkLevel,
|
|
2024
|
-
supportedReasoningEfforts: params.supportedReasoningEfforts
|
|
2025
|
-
}) ?? null;
|
|
2026
|
-
const modelId = params.modelId.trim().toLowerCase();
|
|
2027
|
-
if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
|
|
2028
|
-
requested: params.thinkLevel,
|
|
2029
|
-
supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
|
|
2030
|
-
}) ?? null;
|
|
2031
|
-
if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
|
|
2032
|
-
if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
|
|
2033
|
-
return null;
|
|
2148
|
+
/** Computes the delayed unregister window from Codex's hook timeout. */
|
|
2149
|
+
function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
|
|
2150
|
+
const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
|
|
2151
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
|
|
2034
2152
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2153
|
+
/** Records a native pre-tool failure that Codex does not project as a tool item. */
|
|
2154
|
+
function emitCodexNativePreToolUseFailureDiagnostic(params) {
|
|
2155
|
+
emitTrustedDiagnosticEvent({
|
|
2156
|
+
type: "tool.execution.error",
|
|
2157
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
2158
|
+
sessionId: params.sessionId,
|
|
2159
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
2160
|
+
runId: params.runId,
|
|
2161
|
+
toolName: params.failure.toolName,
|
|
2162
|
+
toolCallId: params.failure.toolCallId,
|
|
2163
|
+
durationMs: params.failure.durationMs,
|
|
2164
|
+
errorCategory: "before_tool_call",
|
|
2165
|
+
terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
|
|
2166
|
+
...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
|
|
2167
|
+
});
|
|
2045
2168
|
}
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
currentModel: params.model,
|
|
2058
|
-
bindingModel: params.binding.model,
|
|
2059
|
-
bindingModelProvider: params.binding.modelProvider
|
|
2060
|
-
}) : void 0;
|
|
2061
|
-
return resolveCodexAppServerRequestModelSelection({
|
|
2062
|
-
model: params.model,
|
|
2063
|
-
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
2064
|
-
authProfileId,
|
|
2065
|
-
authProfileStore: params.authProfileStore,
|
|
2066
|
-
agentDir: params.agentDir,
|
|
2067
|
-
config: params.config
|
|
2068
|
-
});
|
|
2069
|
-
}
|
|
2070
|
-
function resolveCodexAppServerRequestModelSelection(params) {
|
|
2071
|
-
const model = params.model.trim();
|
|
2072
|
-
const modelProvider = params.modelProvider?.trim();
|
|
2073
|
-
if (modelProvider) return {
|
|
2074
|
-
model,
|
|
2075
|
-
modelProvider
|
|
2169
|
+
/** Registers an OpenClaw native hook relay for a Codex app-server turn. */
|
|
2170
|
+
function createCodexNativeHookRelay(params) {
|
|
2171
|
+
if (params.options?.enabled === false) return;
|
|
2172
|
+
const directChildClaims = /* @__PURE__ */ new Map();
|
|
2173
|
+
const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
|
|
2174
|
+
let foregroundClosed = false;
|
|
2175
|
+
let successfulYieldRetentionAuthorized = false;
|
|
2176
|
+
const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
|
|
2177
|
+
const rejectPendingAdmissions = (reason) => {
|
|
2178
|
+
for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
|
|
2179
|
+
pendingDirectChildAdmissions.clear();
|
|
2076
2180
|
};
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2181
|
+
const relay = registerRetainedNativeHookRelayForBundledRuntime({
|
|
2182
|
+
provider: "codex",
|
|
2183
|
+
relayId: buildCodexNativeHookRelayId({
|
|
2184
|
+
agentId: params.agentId,
|
|
2185
|
+
sessionId: params.sessionId,
|
|
2186
|
+
sessionKey: params.sessionKey
|
|
2187
|
+
}),
|
|
2188
|
+
...params.generation ? { generation: params.generation } : {},
|
|
2189
|
+
...params.generationMismatchGraceMs ? { generationMismatchGraceMs: params.generationMismatchGraceMs } : {},
|
|
2190
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
2191
|
+
sessionId: params.sessionId,
|
|
2192
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
2193
|
+
...params.config ? { config: params.config } : {},
|
|
2194
|
+
autoApproveMcpTools: params.autoApproveMcpTools,
|
|
2195
|
+
projectedMcpServers: params.projectedMcpServers,
|
|
2196
|
+
runId: params.runId,
|
|
2197
|
+
...params.channelId ? { channelId: params.channelId } : {},
|
|
2198
|
+
...params.requester ? { requester: params.requester } : {},
|
|
2199
|
+
...params.approvalContext ? { approvalContext: params.approvalContext } : {},
|
|
2200
|
+
allowedEvents: params.events,
|
|
2201
|
+
preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
|
|
2202
|
+
ttlMs: resolveCodexNativeHookRelayTtlMs({
|
|
2203
|
+
explicitTtlMs: params.options?.ttlMs,
|
|
2204
|
+
attemptTimeoutMs: params.attemptTimeoutMs,
|
|
2205
|
+
startupTimeoutMs: params.startupTimeoutMs,
|
|
2206
|
+
turnStartTimeoutMs: params.turnStartTimeoutMs
|
|
2207
|
+
}),
|
|
2208
|
+
signal: params.signal,
|
|
2209
|
+
runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
|
|
2210
|
+
assertActive: params.hostCapabilities.assertActive,
|
|
2211
|
+
retention: {
|
|
2212
|
+
readClaim: readCodexNativeChildThreadId,
|
|
2213
|
+
shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
|
|
2214
|
+
allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
|
|
2215
|
+
awaitForegroundAdmission: (childThreadId) => {
|
|
2216
|
+
if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
|
|
2217
|
+
const existingClaim = directChildClaims.get(childThreadId);
|
|
2218
|
+
if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
|
|
2219
|
+
const existingPending = pendingDirectChildAdmissions.get(childThreadId);
|
|
2220
|
+
if (existingPending) return existingPending.promise.then((claim) => assertClaim(childThreadId, claim));
|
|
2221
|
+
if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
|
|
2222
|
+
const { promise, resolve, reject } = createDeferred();
|
|
2223
|
+
pendingDirectChildAdmissions.set(childThreadId, {
|
|
2224
|
+
promise,
|
|
2225
|
+
resolve,
|
|
2226
|
+
reject
|
|
2227
|
+
});
|
|
2228
|
+
return promise.then((claim) => assertClaim(childThreadId, claim));
|
|
2229
|
+
},
|
|
2230
|
+
onDispose: () => {
|
|
2231
|
+
foregroundClosed = true;
|
|
2232
|
+
rejectPendingAdmissions("native hook relay registration closed");
|
|
2233
|
+
}
|
|
2234
|
+
},
|
|
2235
|
+
onPreToolUseFailure: params.onPreToolUseFailure,
|
|
2236
|
+
command: {
|
|
2237
|
+
nice: 10,
|
|
2238
|
+
timeoutMs: params.options?.gatewayTimeoutMs
|
|
2239
|
+
}
|
|
2085
2240
|
});
|
|
2241
|
+
const unregister = () => {
|
|
2242
|
+
foregroundClosed = true;
|
|
2243
|
+
rejectPendingAdmissions("native hook relay foreground closed");
|
|
2244
|
+
relay.unregister();
|
|
2245
|
+
};
|
|
2086
2246
|
return {
|
|
2087
|
-
|
|
2088
|
-
|
|
2247
|
+
...relay,
|
|
2248
|
+
unregister,
|
|
2249
|
+
authorizeRetentionAfterSuccessfulYield: () => {
|
|
2250
|
+
successfulYieldRetentionAuthorized = true;
|
|
2251
|
+
},
|
|
2252
|
+
hasClaimedDirectChild: () => directChildClaims.size > 0,
|
|
2253
|
+
rejectPendingDirectChild: (threadIdInput, reason) => {
|
|
2254
|
+
const threadId = threadIdInput.trim();
|
|
2255
|
+
const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
|
|
2256
|
+
if (!pending) return;
|
|
2257
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
2258
|
+
pending.reject(new Error(reason));
|
|
2259
|
+
},
|
|
2260
|
+
claimDirectChild: (threadIdInput) => {
|
|
2261
|
+
const threadId = threadIdInput.trim();
|
|
2262
|
+
if (!threadId) return () => void 0;
|
|
2263
|
+
if (directChildClaims.get(threadId)) return () => void 0;
|
|
2264
|
+
const claim = Symbol(threadId);
|
|
2265
|
+
directChildClaims.set(threadId, claim);
|
|
2266
|
+
const pending = pendingDirectChildAdmissions.get(threadId);
|
|
2267
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
2268
|
+
pending?.resolve(claim);
|
|
2269
|
+
let released = false;
|
|
2270
|
+
return () => {
|
|
2271
|
+
if (released) return;
|
|
2272
|
+
released = true;
|
|
2273
|
+
if (directChildClaims.get(threadId) !== claim) return;
|
|
2274
|
+
directChildClaims.delete(threadId);
|
|
2275
|
+
if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2089
2278
|
};
|
|
2090
2279
|
}
|
|
2091
|
-
function
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
2280
|
+
function readCodexNativeChildThreadId(rawPayload) {
|
|
2281
|
+
if (!isJsonObject(rawPayload) || typeof rawPayload.agent_id !== "string") return;
|
|
2282
|
+
return rawPayload.agent_id.trim() || void 0;
|
|
2095
2283
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
2101
|
-
return normalizedLower === "openai" ? "openai" : normalized;
|
|
2284
|
+
/** Selects the native hook events Codex should install for the current approval mode. */
|
|
2285
|
+
function resolveCodexNativeHookRelayEvents(params) {
|
|
2286
|
+
if (params.configuredEvents?.length) return params.configuredEvents;
|
|
2287
|
+
return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
|
|
2102
2288
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
});
|
|
2289
|
+
/** Derives the native hook relay TTL from the turn budget unless explicitly configured. */
|
|
2290
|
+
function resolveCodexNativeHookRelayTtlMs(params) {
|
|
2291
|
+
if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
|
|
2292
|
+
const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
|
|
2293
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
|
|
2109
2294
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2295
|
+
/** Builds a stable relay id scoped to the agent and session identity. */
|
|
2296
|
+
function buildCodexNativeHookRelayId(params) {
|
|
2297
|
+
const hash = createHash("sha256");
|
|
2298
|
+
hash.update("openclaw:codex:native-hook-relay:v1");
|
|
2299
|
+
hash.update("\0");
|
|
2300
|
+
hash.update(params.agentId?.trim() || "");
|
|
2301
|
+
hash.update("\0");
|
|
2302
|
+
hash.update(params.sessionKey?.trim() || params.sessionId);
|
|
2303
|
+
return `codex-${hash.digest("hex").slice(0, 40)}`;
|
|
2116
2304
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2305
|
+
const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
|
|
2306
|
+
pre_tool_use: "PreToolUse",
|
|
2307
|
+
post_tool_use: "PostToolUse",
|
|
2308
|
+
permission_request: "PermissionRequest",
|
|
2309
|
+
before_agent_finalize: "Stop"
|
|
2310
|
+
};
|
|
2311
|
+
const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
|
|
2312
|
+
pre_tool_use: "pre_tool_use",
|
|
2313
|
+
post_tool_use: "post_tool_use",
|
|
2314
|
+
permission_request: "permission_request",
|
|
2315
|
+
before_agent_finalize: "stop"
|
|
2316
|
+
};
|
|
2317
|
+
const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
|
|
2318
|
+
/** Builds the Codex config overlay that installs trusted command hooks for relay events. */
|
|
2319
|
+
function buildCodexNativeHookRelayConfig(params) {
|
|
2320
|
+
const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
|
|
2321
|
+
const selectedEvents = new Set(events);
|
|
2322
|
+
const config = { "features.hooks": true };
|
|
2323
|
+
const hookState = {};
|
|
2324
|
+
for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
|
|
2325
|
+
const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
|
|
2326
|
+
const selected = selectedEvents.has(event);
|
|
2327
|
+
const shouldRelay = params.relay.shouldRelayEvent(event);
|
|
2328
|
+
if (!selected || !shouldRelay) {
|
|
2329
|
+
if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
|
|
2330
|
+
if (params.clearOmittedEvents) for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = { enabled: false };
|
|
2331
|
+
continue;
|
|
2140
2332
|
}
|
|
2333
|
+
const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
|
|
2334
|
+
const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
|
|
2335
|
+
const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
|
|
2336
|
+
config[`hooks.${codexEvent}`] = [{
|
|
2337
|
+
...matcher ? { matcher } : {},
|
|
2338
|
+
hooks: [{
|
|
2339
|
+
type: "command",
|
|
2340
|
+
command,
|
|
2341
|
+
timeout,
|
|
2342
|
+
async: false,
|
|
2343
|
+
statusMessage: "OpenClaw native hook relay"
|
|
2344
|
+
}]
|
|
2345
|
+
}];
|
|
2346
|
+
const state = {
|
|
2347
|
+
enabled: true,
|
|
2348
|
+
trusted_hash: codexCommandHookTrustedHash({
|
|
2349
|
+
event,
|
|
2350
|
+
command,
|
|
2351
|
+
matcher,
|
|
2352
|
+
timeout,
|
|
2353
|
+
statusMessage: "OpenClaw native hook relay"
|
|
2354
|
+
})
|
|
2355
|
+
};
|
|
2356
|
+
for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
|
|
2141
2357
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2358
|
+
config["hooks.state"] = hookState;
|
|
2359
|
+
return config;
|
|
2360
|
+
}
|
|
2361
|
+
/** Builds a Codex config overlay that disables native hooks and clears hook arrays. */
|
|
2362
|
+
function buildCodexNativeHookRelayDisabledConfig() {
|
|
2363
|
+
return {
|
|
2364
|
+
"features.hooks": false,
|
|
2365
|
+
"hooks.PreToolUse": [],
|
|
2366
|
+
"hooks.PostToolUse": [],
|
|
2367
|
+
"hooks.PermissionRequest": [],
|
|
2368
|
+
"hooks.Stop": []
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
function normalizeHookTimeoutSec(value) {
|
|
2372
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC;
|
|
2373
|
+
}
|
|
2374
|
+
function resolveCodexNativeHookRelayCommandTimeoutMs(hookTimeoutSec) {
|
|
2375
|
+
const parentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 5e3;
|
|
2376
|
+
const parentMarginMs = Math.min(CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS, Math.max(CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS, Math.floor(parentTimeoutMs / 5)));
|
|
2377
|
+
return Math.max(1, parentTimeoutMs - parentMarginMs);
|
|
2378
|
+
}
|
|
2379
|
+
function buildCodexNativeToolMatcher(toolNames) {
|
|
2380
|
+
if (toolNames === void 0) return;
|
|
2381
|
+
if (toolNames.length === 0) throw new TypeError("Codex native hook matcher requires at least one tool name");
|
|
2382
|
+
const nativeNames = /* @__PURE__ */ new Set();
|
|
2383
|
+
let hasCustomToolName = false;
|
|
2384
|
+
for (const toolName of toolNames) {
|
|
2385
|
+
const canonicalToolName = toolName.trim();
|
|
2386
|
+
if (!canonicalToolName || canonicalToolName === "*") throw new TypeError("Codex native hook matcher requires canonical OpenClaw tool ids");
|
|
2387
|
+
const nativeAliases = CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID[canonicalToolName];
|
|
2388
|
+
if (!nativeAliases) hasCustomToolName = true;
|
|
2389
|
+
for (const nativeName of nativeAliases ?? [canonicalToolName]) nativeNames.add(nativeName);
|
|
2390
|
+
}
|
|
2391
|
+
const sortedNames = Array.from(nativeNames).toSorted();
|
|
2392
|
+
if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
|
|
2393
|
+
return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
|
|
2394
|
+
}
|
|
2395
|
+
function codexCommandHookTrustedHash(params) {
|
|
2396
|
+
const identity = {
|
|
2397
|
+
event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
|
|
2398
|
+
...params.matcher ? { matcher: params.matcher } : {},
|
|
2399
|
+
hooks: [{
|
|
2400
|
+
async: false,
|
|
2401
|
+
command: params.command,
|
|
2402
|
+
statusMessage: params.statusMessage,
|
|
2403
|
+
timeout: params.timeout,
|
|
2404
|
+
type: "command"
|
|
2405
|
+
}]
|
|
2406
|
+
};
|
|
2407
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
|
|
2408
|
+
}
|
|
2409
|
+
function sortJsonValue(value) {
|
|
2410
|
+
if (!value || typeof value !== "object") return value;
|
|
2411
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
2412
|
+
const sorted = {};
|
|
2413
|
+
for (const [key, entry] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) sorted[key] = sortJsonValue(entry);
|
|
2414
|
+
return sorted;
|
|
2415
|
+
}
|
|
2416
|
+
//#endregion
|
|
2417
|
+
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
2418
|
+
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
2419
|
+
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
2420
|
+
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
2421
|
+
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
2422
|
+
}
|
|
2423
|
+
//#endregion
|
|
2424
|
+
//#region extensions/codex/src/app-server/mcp-tool-metadata.ts
|
|
2425
|
+
/** Hosted app ownership is authoritative only on metadata supplied by Codex. */
|
|
2426
|
+
function readCodexMcpToolConnectorId(tool) {
|
|
2427
|
+
const metadata = asOptionalRecord(asOptionalRecord(tool)?.["_meta"]);
|
|
2428
|
+
return normalizeOptionalString(metadata?.connector_id) ?? normalizeOptionalString(metadata?.connectorId);
|
|
2429
|
+
}
|
|
2430
|
+
/** Preserve MCP App visibility so model-only tools cannot become widget authority. */
|
|
2431
|
+
function readCodexMcpToolUiVisibility(tool) {
|
|
2432
|
+
const metadata = asOptionalRecord(asOptionalRecord(tool)?.["_meta"]);
|
|
2433
|
+
const visibility = asOptionalRecord(metadata?.ui)?.visibility;
|
|
2434
|
+
if (!Array.isArray(visibility)) return;
|
|
2435
|
+
return [...new Set(visibility.filter((value) => value === "app" || value === "model"))].toSorted();
|
|
2436
|
+
}
|
|
2437
|
+
//#endregion
|
|
2438
|
+
//#region extensions/codex/src/app-server/project-doc-thread-config.ts
|
|
2439
|
+
const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES = 131072;
|
|
2440
|
+
function buildCodexProjectDocThreadConfig(config) {
|
|
2441
|
+
const defaults = { project_doc_max_bytes: CODEX_NATIVE_PROJECT_DOC_MAX_BYTES };
|
|
2442
|
+
return mergeCodexThreadConfigs(defaults, config) ?? defaults;
|
|
2443
|
+
}
|
|
2444
|
+
//#endregion
|
|
2445
|
+
//#region extensions/codex/src/app-server/thread-model-selection.ts
|
|
2446
|
+
const CODEX_NATIVE_PERSONALITY_NONE = "none";
|
|
2447
|
+
function resolveCodexBindingModelProviderFallback(params) {
|
|
2448
|
+
const provider = params.provider?.trim().toLowerCase();
|
|
2449
|
+
if (provider && provider !== "codex") return;
|
|
2450
|
+
const currentModel = params.currentModel?.trim();
|
|
2451
|
+
const bindingModel = params.bindingModel?.trim();
|
|
2452
|
+
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
2453
|
+
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
2454
|
+
}
|
|
2455
|
+
function resolveCodexAppServerThreadModelSelection(params) {
|
|
2456
|
+
const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
|
|
2457
|
+
const explicitModelProvider = resolveCodexAppServerModelProvider({
|
|
2458
|
+
provider: params.provider,
|
|
2459
|
+
authProfileId,
|
|
2460
|
+
authProfileStore: params.authProfileStore,
|
|
2461
|
+
agentDir: params.agentDir,
|
|
2462
|
+
config: params.config
|
|
2463
|
+
});
|
|
2464
|
+
const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
|
|
2465
|
+
provider: params.provider,
|
|
2466
|
+
currentModel: params.model,
|
|
2467
|
+
bindingModel: params.binding.model,
|
|
2468
|
+
bindingModelProvider: params.binding.modelProvider
|
|
2469
|
+
}) : void 0;
|
|
2470
|
+
return resolveCodexAppServerRequestModelSelection({
|
|
2471
|
+
model: params.model,
|
|
2472
|
+
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
2473
|
+
authProfileId,
|
|
2474
|
+
authProfileStore: params.authProfileStore,
|
|
2475
|
+
agentDir: params.agentDir,
|
|
2476
|
+
config: params.config
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
function resolveCodexAppServerRequestModelSelection(params) {
|
|
2480
|
+
const model = params.model.trim();
|
|
2481
|
+
const modelProvider = params.modelProvider?.trim();
|
|
2482
|
+
if (modelProvider) return {
|
|
2483
|
+
model,
|
|
2484
|
+
modelProvider
|
|
2485
|
+
};
|
|
2486
|
+
const slashIndex = model.indexOf("/");
|
|
2487
|
+
if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
|
|
2488
|
+
const inferredModelProvider = resolveCodexAppServerModelProvider({
|
|
2489
|
+
provider: model.slice(0, slashIndex),
|
|
2490
|
+
authProfileId: params.authProfileId,
|
|
2491
|
+
authProfileStore: params.authProfileStore,
|
|
2492
|
+
agentDir: params.agentDir,
|
|
2493
|
+
config: params.config
|
|
2494
|
+
});
|
|
2495
|
+
return {
|
|
2496
|
+
model: model.slice(slashIndex + 1).trim(),
|
|
2497
|
+
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
function hasProviderQualifiedModelRef(model) {
|
|
2501
|
+
const trimmed = model?.trim();
|
|
2502
|
+
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
|
2503
|
+
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
2504
|
+
}
|
|
2505
|
+
function resolveCodexAppServerModelProvider(params) {
|
|
2506
|
+
const normalized = params.provider.trim();
|
|
2507
|
+
const normalizedLower = normalized.toLowerCase();
|
|
2508
|
+
if (!normalized || normalizedLower === "codex") return;
|
|
2509
|
+
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
2510
|
+
return normalizedLower === "openai" ? "openai" : normalized;
|
|
2511
|
+
}
|
|
2512
|
+
//#endregion
|
|
2513
|
+
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
2514
|
+
function buildDeveloperInstructions(params, options = {}) {
|
|
2515
|
+
const deferredToolNames = /* @__PURE__ */ new Set();
|
|
2516
|
+
let secretsToolName;
|
|
2517
|
+
let showWidgetToolName;
|
|
2518
|
+
let dashboardToolName;
|
|
2519
|
+
let portalToolName;
|
|
2520
|
+
let hasSkillWorkshop = false;
|
|
2521
|
+
let hasSessionsSpawn = false;
|
|
2522
|
+
let hasSessionsYield = false;
|
|
2523
|
+
let hasSubagentsList = false;
|
|
2524
|
+
let hasSessionsSend = false;
|
|
2525
|
+
let hasSeenDirectNamespace = false;
|
|
2526
|
+
let messageToolAvailable = options.dynamicTools ? false : params.disableMessageTool !== true;
|
|
2527
|
+
for (const spec of options.dynamicTools ?? []) {
|
|
2528
|
+
const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
|
|
2529
|
+
if (isDirectNamespace) hasSeenDirectNamespace = true;
|
|
2530
|
+
for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
|
|
2531
|
+
const name = tool.name.trim();
|
|
2532
|
+
const qualifiedName = spec.type === "namespace" ? `${spec.name}.${name}` : name;
|
|
2533
|
+
if (tool.deferLoading === true && name) deferredToolNames.add(name);
|
|
2534
|
+
if (name === "secrets" && params.disableTools !== true) secretsToolName ??= qualifiedName;
|
|
2535
|
+
if (name === "show_widget") showWidgetToolName ??= qualifiedName;
|
|
2536
|
+
if (name === "dashboard") dashboardToolName ??= qualifiedName;
|
|
2537
|
+
if (name === "portal") portalToolName ??= qualifiedName;
|
|
2538
|
+
hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
|
|
2539
|
+
hasSessionsSpawn ||= name === "sessions_spawn";
|
|
2540
|
+
hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
|
|
2541
|
+
hasSubagentsList ||= name === "subagents";
|
|
2542
|
+
hasSessionsSend ||= name === "sessions_send";
|
|
2543
|
+
messageToolAvailable ||= name === "message";
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
2547
|
+
surface: "codex_app_server",
|
|
2548
|
+
includeLegacyGlobalGuidance: false
|
|
2549
|
+
}).join("\n");
|
|
2146
2550
|
const delegationGuidanceAvailable = params.disableTools !== true && params.delegationCapability !== "report_only" && !isMessageOnlyCodexSourceReply(params);
|
|
2147
2551
|
const nativeDelegationAvailable = delegationGuidanceAvailable && !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) && !shouldDisableCodexToolSearchForModel(params.modelId);
|
|
2148
2552
|
const deferredToolDiscoveryGuidance = deferredToolNames.size > 0 || nativeDelegationAvailable ? "Deferred tools may be absent from the direct tool list. Use `tool_search` when directly callable. On code-mode-only models, use `exec` instead: filter `ALL_TOOLS` by name and description, then call the matching entry through `tools`." : void 0;
|
|
@@ -2168,9 +2572,14 @@ function buildDeveloperInstructions(params, options = {}) {
|
|
|
2168
2572
|
}).join("\n") : void 0,
|
|
2169
2573
|
buildHarnessVisibleReplyGuidance({
|
|
2170
2574
|
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
|
2171
|
-
messageToolAvailable
|
|
2575
|
+
messageToolAvailable,
|
|
2576
|
+
uiPresentation: params.disableTools !== true && params.promptMode !== "minimal" && params.promptMode !== "none" ? {
|
|
2577
|
+
showWidgetToolName,
|
|
2578
|
+
dashboardToolName,
|
|
2579
|
+
portalToolName
|
|
2580
|
+
} : void 0
|
|
2172
2581
|
}),
|
|
2173
|
-
|
|
2582
|
+
buildCredentialSafetyPrompt(secretsToolName),
|
|
2174
2583
|
nativeCommandGuidance,
|
|
2175
2584
|
params.extraSystemPrompt
|
|
2176
2585
|
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
@@ -2364,8 +2773,31 @@ const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
|
2364
2773
|
"project",
|
|
2365
2774
|
"sessionFlags"
|
|
2366
2775
|
]);
|
|
2776
|
+
/** Common deterministic start/resume/fork fields; no run resources or unsupported setters. */
|
|
2777
|
+
function buildCodexThreadConfiguration(params, options) {
|
|
2778
|
+
return {
|
|
2779
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
2780
|
+
...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
|
|
2781
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
2782
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2783
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2784
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2785
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2786
|
+
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2787
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2788
|
+
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2789
|
+
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
2790
|
+
webSearchAllowed: options.webSearchAllowed,
|
|
2791
|
+
appServer: options.appServer,
|
|
2792
|
+
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
2793
|
+
restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
|
|
2794
|
+
shellEnvironment: options.shellEnvironment,
|
|
2795
|
+
disableLoginShell: options.disableLoginShell
|
|
2796
|
+
}),
|
|
2797
|
+
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2367
2800
|
function buildThreadStartParams(params, options) {
|
|
2368
|
-
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
2369
2801
|
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
2370
2802
|
provider: params.provider,
|
|
2371
2803
|
authProfileId: params.authProfileId,
|
|
@@ -2384,29 +2816,11 @@ function buildThreadStartParams(params, options) {
|
|
|
2384
2816
|
return {
|
|
2385
2817
|
model: modelSelection.model,
|
|
2386
2818
|
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
2387
|
-
|
|
2388
|
-
...options.
|
|
2389
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
2390
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2391
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2392
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2819
|
+
...buildCodexThreadConfiguration(params, options),
|
|
2820
|
+
...(options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) ? { baseInstructions: "" } : {},
|
|
2393
2821
|
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2394
2822
|
serviceName: "OpenClaw",
|
|
2395
|
-
...ringZeroActive ? { baseInstructions: "" } : {},
|
|
2396
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2397
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2398
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2399
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2400
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
2401
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2402
|
-
appServer: options.appServer,
|
|
2403
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
2404
|
-
restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
|
|
2405
|
-
shellEnvironment: options.shellEnvironment,
|
|
2406
|
-
disableLoginShell: options.disableLoginShell
|
|
2407
|
-
}),
|
|
2408
2823
|
...resolveCodexThreadEnvironmentSelection(options),
|
|
2409
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
2410
2824
|
dynamicTools: [...options.dynamicTools],
|
|
2411
2825
|
experimentalRawEvents: true,
|
|
2412
2826
|
...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
|
|
@@ -2429,8 +2843,6 @@ function buildThreadResumeParams(params, options) {
|
|
|
2429
2843
|
});
|
|
2430
2844
|
return {
|
|
2431
2845
|
threadId: options.threadId,
|
|
2432
|
-
...options.cwd ? { cwd: options.cwd } : {},
|
|
2433
|
-
...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
|
|
2434
2846
|
excludeTurns: true,
|
|
2435
2847
|
initialTurnsPage: {
|
|
2436
2848
|
limit: 1,
|
|
@@ -2441,24 +2853,8 @@ function buildThreadResumeParams(params, options) {
|
|
|
2441
2853
|
model: modelSelection.model,
|
|
2442
2854
|
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
|
|
2443
2855
|
} : {},
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2447
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2448
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2449
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2450
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2451
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2452
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2453
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
2454
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2455
|
-
appServer: options.appServer,
|
|
2456
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
2457
|
-
restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
|
|
2458
|
-
shellEnvironment: options.shellEnvironment,
|
|
2459
|
-
disableLoginShell: options.disableLoginShell
|
|
2460
|
-
}),
|
|
2461
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
2856
|
+
...buildCodexThreadConfiguration(params, options),
|
|
2857
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
2462
2858
|
};
|
|
2463
2859
|
}
|
|
2464
2860
|
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
@@ -2468,33 +2864,24 @@ function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
|
2468
2864
|
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
2469
2865
|
};
|
|
2470
2866
|
if (options.nativeCodeModeEnabled === false) {
|
|
2471
|
-
const disabledConfig = mergeCodexThreadConfigs(configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG)
|
|
2472
|
-
...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG,
|
|
2473
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
2474
|
-
...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG
|
|
2475
|
-
};
|
|
2867
|
+
const disabledConfig = expectDefined(mergeCodexThreadConfigs(configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG), "Codex disabled code mode config");
|
|
2476
2868
|
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
2477
2869
|
return disabledConfig;
|
|
2478
2870
|
}
|
|
2479
|
-
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true })
|
|
2480
|
-
|
|
2481
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
2482
|
-
...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG,
|
|
2483
|
-
"features.code_mode_only": true
|
|
2484
|
-
}, options.directOnlyToolNamespaces);
|
|
2485
|
-
return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG) ?? {
|
|
2486
|
-
...codeModeConfig,
|
|
2487
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
2488
|
-
...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG
|
|
2489
|
-
}, options.directOnlyToolNamespaces);
|
|
2871
|
+
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(expectDefined(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true }), "Codex code mode only config"), options.directOnlyToolNamespaces);
|
|
2872
|
+
return ensureDirectOnlyToolNamespaces(expectDefined(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG), "Codex code mode config"), options.directOnlyToolNamespaces);
|
|
2490
2873
|
}
|
|
2491
2874
|
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
2492
2875
|
if (!requiredNamespaces?.length) return config;
|
|
2493
|
-
const
|
|
2494
|
-
const
|
|
2876
|
+
const feature = expectDefined(config["features.code_mode"], "Codex code mode config");
|
|
2877
|
+
const configured = isJsonObject(feature) ? feature : { enabled: feature };
|
|
2878
|
+
const namespaces = Array.isArray(configured.direct_only_tool_namespaces) ? configured.direct_only_tool_namespaces.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2495
2879
|
return {
|
|
2496
2880
|
...config,
|
|
2497
|
-
"code_mode
|
|
2881
|
+
"features.code_mode": {
|
|
2882
|
+
...configured,
|
|
2883
|
+
direct_only_tool_namespaces: [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
2884
|
+
}
|
|
2498
2885
|
};
|
|
2499
2886
|
}
|
|
2500
2887
|
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
@@ -2508,10 +2895,6 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
2508
2895
|
const configMcpServers = config?.mcp_servers;
|
|
2509
2896
|
if (restrictedToolSurface && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex restricted tool surface received invalid thread mcp_servers config");
|
|
2510
2897
|
const restrictedToolSurfaceMcpServerNames = [...options.restrictedToolSurfaceInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
2511
|
-
const restrictedRunConfig = restrictedToolSurface && isJsonObject(configMcpServers) ? {
|
|
2512
|
-
...config,
|
|
2513
|
-
mcp_servers: {}
|
|
2514
|
-
} : config;
|
|
2515
2898
|
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
2516
2899
|
config: params.config,
|
|
2517
2900
|
disableTools: params.disableTools,
|
|
@@ -2519,9 +2902,9 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
2519
2902
|
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2520
2903
|
webSearchAllowed: options.webSearchAllowed
|
|
2521
2904
|
}).threadConfig;
|
|
2522
|
-
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(
|
|
2905
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
2523
2906
|
return applyCodexManagedShellEnvironment({
|
|
2524
|
-
...mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") ? { "features.image_generation": false } : void 0, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildRestrictedToolConfigPatch(restrictedToolSurfaceMcpServerNames) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames), restrictedTurnDisablesProjectDocs ? CODEX_NO_PROJECT_DOCS_CONFIG : void 0, params.authoredContextTokenCap === void 0 ? void 0 : { model_context_window: params.authoredContextTokenCap }) ?? baseConfig,
|
|
2907
|
+
...mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") ? { "features.image_generation": false } : void 0, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildRestrictedToolConfigPatch(restrictedToolSurfaceMcpServerNames, Boolean(params.scheduledRuntimeAuthority)) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames), restrictedTurnDisablesProjectDocs ? CODEX_NO_PROJECT_DOCS_CONFIG : void 0, params.authoredContextTokenCap === void 0 ? void 0 : { model_context_window: params.authoredContextTokenCap }) ?? baseConfig,
|
|
2525
2908
|
...params.bootstrapContextMode === "lightweight" ? CODEX_NO_PROJECT_DOCS_CONFIG : {}
|
|
2526
2909
|
}, options.shellEnvironment, options.disableLoginShell);
|
|
2527
2910
|
}
|
|
@@ -2532,10 +2915,14 @@ function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isH
|
|
|
2532
2915
|
...CODEX_NO_PROJECT_DOCS_CONFIG
|
|
2533
2916
|
};
|
|
2534
2917
|
}
|
|
2535
|
-
function buildRestrictedToolConfigPatch(inheritedMcpServerNames) {
|
|
2918
|
+
function buildRestrictedToolConfigPatch(inheritedMcpServerNames, scheduledAppAuthorityActive = false) {
|
|
2536
2919
|
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
2537
2920
|
return {
|
|
2538
2921
|
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
2922
|
+
...scheduledAppAuthorityActive ? {
|
|
2923
|
+
"features.apps": true,
|
|
2924
|
+
"orchestrator.mcp.enabled": true
|
|
2925
|
+
} : {},
|
|
2539
2926
|
...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
|
|
2540
2927
|
};
|
|
2541
2928
|
}
|
|
@@ -2557,60 +2944,55 @@ async function readCodexInheritedMcpServerNames(client, cwd, signal) {
|
|
|
2557
2944
|
return Object.keys(configuredServers).toSorted();
|
|
2558
2945
|
}
|
|
2559
2946
|
async function assertCodexManagedRequirementsDoNotOverrideToolPolicy(client, options, signal) {
|
|
2560
|
-
const
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2947
|
+
const requirements = await readCodexManagedRequirements(client, signal);
|
|
2948
|
+
const managedRequirementsFingerprint = buildCodexManagedRequirementsFingerprint(requirements);
|
|
2949
|
+
const managedRequirementsMatch = options.allowedManagedRequirementsFingerprint !== void 0 && managedRequirementsFingerprint === options.allowedManagedRequirementsFingerprint;
|
|
2950
|
+
const managedHooksAllowed = managedRequirementsMatch || options.allowConfiguredManagedHooks === true;
|
|
2951
|
+
if (options.allowedManagedRequirementsFingerprint !== void 0 && !managedRequirementsMatch) throw new Error("Codex managed requirements changed since this automation was authorized; reauthorize the automation from a fresh owner turn");
|
|
2952
|
+
if (requirements === null) return;
|
|
2564
2953
|
if (options.restrictedToolSurface) for (const key of [
|
|
2565
2954
|
"hooks",
|
|
2566
2955
|
"managedHooks",
|
|
2567
2956
|
"managed_hooks"
|
|
2568
2957
|
]) {
|
|
2569
|
-
const hooks =
|
|
2958
|
+
const hooks = requirements[key];
|
|
2570
2959
|
if (hooks === void 0 || hooks === null) continue;
|
|
2571
2960
|
if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
|
|
2572
|
-
if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex restricted tool surface cannot override managed hooks");
|
|
2961
|
+
if (hasNonEmptyJsonValue(hooks) && !managedHooksAllowed) throw new Error("Codex restricted tool surface cannot override managed hooks");
|
|
2573
2962
|
}
|
|
2574
2963
|
const additionalDeniedFeatures = new Set(options.additionalDeniedFeatures);
|
|
2575
2964
|
for (const key of ["featureRequirements", "feature_requirements"]) {
|
|
2576
|
-
const
|
|
2577
|
-
if (
|
|
2578
|
-
if (!isJsonObject(
|
|
2579
|
-
for (const [feature, enabled] of Object.entries(
|
|
2965
|
+
const featureRequirements = requirements[key];
|
|
2966
|
+
if (featureRequirements === void 0 || featureRequirements === null) continue;
|
|
2967
|
+
if (!isJsonObject(featureRequirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
2968
|
+
for (const [feature, enabled] of Object.entries(featureRequirements)) {
|
|
2580
2969
|
if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
2581
2970
|
const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
|
|
2582
2971
|
const deniedByToolPolicy = options.restrictedToolSurface && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature) || additionalDeniedFeatures.has(canonicalFeature);
|
|
2972
|
+
if (canonicalFeature === "hooks" && managedHooksAllowed) continue;
|
|
2583
2973
|
if (enabled && deniedByToolPolicy) throw new Error(`Codex tool policy cannot override required feature ${feature}`);
|
|
2584
2974
|
}
|
|
2585
2975
|
}
|
|
2586
2976
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
}
|
|
2595
|
-
const response = await client.request("mcpServerStatus/list", {
|
|
2596
|
-
threadId,
|
|
2597
|
-
detail: "toolsAndAuthOnly"
|
|
2598
|
-
}, { signal });
|
|
2599
|
-
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
|
|
2600
|
-
const observedDisabledServerNames = /* @__PURE__ */ new Set();
|
|
2601
|
-
for (const status of response.data) {
|
|
2602
|
-
if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
|
|
2603
|
-
if (!expectedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
|
|
2604
|
-
if (observedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
|
|
2605
|
-
observedDisabledServerNames.add(status.name);
|
|
2606
|
-
if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
|
|
2607
|
-
if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
|
|
2608
|
-
if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
|
|
2609
|
-
}
|
|
2610
|
-
for (const expectedName of expectedDisabledServerNames) if (!observedDisabledServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing server ${expectedName}`);
|
|
2611
|
-
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
2977
|
+
/** Hashes the exact managed requirements without retaining their hook commands or policy details. */
|
|
2978
|
+
function buildCodexManagedRequirementsFingerprint(requirements) {
|
|
2979
|
+
const fingerprint = fingerprintJsonObject({
|
|
2980
|
+
version: 1,
|
|
2981
|
+
requirements
|
|
2982
|
+
});
|
|
2983
|
+
return crypto.createHash("sha256").update(fingerprint).digest("hex");
|
|
2612
2984
|
}
|
|
2613
|
-
|
|
2985
|
+
/** Reads and fingerprints the exact managed requirements active on this app-server. */
|
|
2986
|
+
async function readCodexManagedRequirementsFingerprint(client, signal) {
|
|
2987
|
+
return buildCodexManagedRequirementsFingerprint(await readCodexManagedRequirements(client, signal));
|
|
2988
|
+
}
|
|
2989
|
+
async function readCodexManagedRequirements(client, signal) {
|
|
2990
|
+
const response = await client.request("configRequirements/read", void 0, { signal });
|
|
2991
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
|
|
2992
|
+
if (response.requirements !== null && !isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
|
|
2993
|
+
return response.requirements;
|
|
2994
|
+
}
|
|
2995
|
+
function hasNonEmptyJsonValue(value) {
|
|
2614
2996
|
if (value === null || value === false || value === "") return false;
|
|
2615
2997
|
if (Array.isArray(value)) return value.length > 0;
|
|
2616
2998
|
if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
|
|
@@ -2629,1334 +3011,940 @@ function resolveCodexThreadEnvironmentSelection(options) {
|
|
|
2629
3011
|
return {};
|
|
2630
3012
|
}
|
|
2631
3013
|
//#endregion
|
|
2632
|
-
//#region extensions/codex/src/app-server/
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
3014
|
+
//#region extensions/codex/src/app-server/scheduled-app-authority.ts
|
|
3015
|
+
const CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE = "codex.apps";
|
|
3016
|
+
const CODEX_APPS_MCP_SERVER = "codex_apps";
|
|
3017
|
+
const MCP_STATUS_PAGE_SIZE = 100;
|
|
3018
|
+
const MCP_STATUS_MAX_PAGES = 100;
|
|
3019
|
+
const CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS = 6e4;
|
|
3020
|
+
const CODEX_APP_AUTHORITY_CAPTURE_MIN_TIMEOUT_MS = 100;
|
|
3021
|
+
/** Hashes stable configured endpoint identity without retaining credentials or endpoint details. */
|
|
3022
|
+
function buildScheduledCodexAppServerConnectionIdentity(appServer) {
|
|
3023
|
+
const start = appServer.start;
|
|
3024
|
+
return crypto.createHash("sha256").update("openclaw:codex:scheduled-app-server:v1\0").update(JSON.stringify({
|
|
3025
|
+
transport: start.transport,
|
|
3026
|
+
command: start.command,
|
|
3027
|
+
commandSource: start.commandSource ?? null,
|
|
3028
|
+
args: start.args,
|
|
3029
|
+
cwd: start.cwd ?? null,
|
|
3030
|
+
url: start.url ?? null,
|
|
3031
|
+
homeScope: start.homeScope ?? null,
|
|
3032
|
+
connectionClass: appServer.connectionClass,
|
|
3033
|
+
remoteWorkspaceRoot: appServer.remoteWorkspaceRoot ?? null
|
|
3034
|
+
})).digest("hex");
|
|
3035
|
+
}
|
|
3036
|
+
function resolveScheduledCodexAppCreatorCaptureDecision(params) {
|
|
3037
|
+
if (!params.appsMayBeVisible) return {
|
|
3038
|
+
required: false,
|
|
3039
|
+
supported: false
|
|
3040
|
+
};
|
|
3041
|
+
const unavailableReason = params.authenticatedScheduledMode ? "A scheduled Codex continuation cannot create new app-authorized automations. Recreate it from a fresh authenticated owner turn; no automation changes were saved." : params.usesSupervisionConnection ? "Codex apps are visible through a supervised connection that cannot capture creator authority. Use an isolated prepared-profile Codex creator turn; no automation changes were saved." : params.homeScope === "user" ? "Codex apps are visible through a user-home runtime that cannot capture isolated creator authority. Use an agent-scoped prepared-profile Codex creator turn; no automation changes were saved." : !params.hasPreparedAccountIdentity && !params.hasConfiguredAppServerIdentity ? "Codex app authority requires either a prepared ChatGPT profile or an isolated configured app-server identity. Reauthenticate the selected Codex profile or configured app-server, then retry; no automation changes were saved." : void 0;
|
|
3042
|
+
return {
|
|
3043
|
+
required: true,
|
|
3044
|
+
supported: !unavailableReason,
|
|
3045
|
+
...unavailableReason ? { unavailableReason } : {}
|
|
3046
|
+
};
|
|
2641
3047
|
}
|
|
2642
|
-
function
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
3048
|
+
function normalizeApprovalMode(value) {
|
|
3049
|
+
return value === "allow" || value === "deny" || value === "auto" || value === "ask" ? value : void 0;
|
|
3050
|
+
}
|
|
3051
|
+
function normalizeAppToolApprovalMode(value) {
|
|
3052
|
+
return value === "auto" || value === "prompt" || value === "writes" || value === "approve" ? value : void 0;
|
|
3053
|
+
}
|
|
3054
|
+
function defaultApprovalMode(entry) {
|
|
3055
|
+
return entry.destructiveApprovalMode ?? (entry.allowDestructiveActions ? "allow" : "deny");
|
|
3056
|
+
}
|
|
3057
|
+
function parseScheduledCodexAppAuthority(authority) {
|
|
3058
|
+
if (!authority || authority.runtimeId !== "codex") return;
|
|
3059
|
+
if (authority.version !== 1) throw new Error("Unsupported Codex scheduled authority version; reauthorize this automation.");
|
|
3060
|
+
if (authority.namespace !== CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE) throw new Error(`Unsupported Codex scheduled authority namespace ${authority.namespace}; reauthorize this automation.`);
|
|
3061
|
+
const payload = asOptionalRecord(authority.payload);
|
|
3062
|
+
const auth = asOptionalRecord(payload?.auth);
|
|
3063
|
+
const profileId = normalizeOptionalString(auth?.profileId);
|
|
3064
|
+
const connectionFingerprint = normalizeOptionalString(auth?.connectionFingerprint);
|
|
3065
|
+
const managedRequirementsFingerprint = normalizeOptionalString(auth?.managedRequirementsFingerprint);
|
|
3066
|
+
const accountId = normalizeOptionalString(auth?.accountId);
|
|
3067
|
+
const parsedAuth = auth?.kind === "configured-app-server" && connectionFingerprint && managedRequirementsFingerprint ? {
|
|
3068
|
+
kind: "configured-app-server",
|
|
3069
|
+
connectionFingerprint,
|
|
3070
|
+
managedRequirementsFingerprint
|
|
3071
|
+
} : auth?.kind === void 0 && profileId && accountId ? {
|
|
3072
|
+
profileId,
|
|
3073
|
+
accountId
|
|
3074
|
+
} : void 0;
|
|
3075
|
+
if (payload?.version !== 1 || !parsedAuth || !Array.isArray(payload.apps)) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
|
|
3076
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3077
|
+
return {
|
|
3078
|
+
version: 1,
|
|
3079
|
+
auth: parsedAuth,
|
|
3080
|
+
apps: payload.apps.map((raw) => {
|
|
3081
|
+
const app = asOptionalRecord(raw);
|
|
3082
|
+
const id = normalizeOptionalString(app?.id);
|
|
3083
|
+
const destructiveApprovalMode = normalizeApprovalMode(app?.destructiveApprovalMode);
|
|
3084
|
+
const rawTools = asOptionalRecord(app?.tools);
|
|
3085
|
+
if (!id || seen.has(id) || typeof app?.allowDestructiveActions !== "boolean" || typeof app.allowOpenWorld !== "boolean" || !destructiveApprovalMode || !rawTools) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
|
|
3086
|
+
seen.add(id);
|
|
3087
|
+
const tools = {};
|
|
3088
|
+
for (const [name, rawMode] of Object.entries(rawTools)) {
|
|
3089
|
+
const toolName = normalizeOptionalString(name);
|
|
3090
|
+
const mode = normalizeAppToolApprovalMode(rawMode);
|
|
3091
|
+
if (!toolName || !mode) throw new Error("Stored Codex app authority is invalid; reauthorize this automation.");
|
|
3092
|
+
tools[toolName] = mode;
|
|
3093
|
+
}
|
|
3094
|
+
return {
|
|
3095
|
+
id,
|
|
3096
|
+
allowDestructiveActions: app.allowDestructiveActions,
|
|
3097
|
+
allowOpenWorld: app.allowOpenWorld,
|
|
3098
|
+
destructiveApprovalMode,
|
|
3099
|
+
tools
|
|
3100
|
+
};
|
|
3101
|
+
})
|
|
3102
|
+
};
|
|
2646
3103
|
}
|
|
2647
|
-
async function
|
|
2648
|
-
const
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
const
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2658
|
-
};
|
|
2659
|
-
const pluginAppsConfigPatch = context.prebuiltPluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
|
|
2660
|
-
const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
|
|
2661
|
-
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2662
|
-
threadId: resumeBinding.threadId,
|
|
2663
|
-
cwd: params.cwd,
|
|
2664
|
-
authProfileId,
|
|
2665
|
-
model: startModelSelection.model,
|
|
2666
|
-
modelProvider: startModelProvider,
|
|
2667
|
-
preserveNativeModel: resumeBinding.preserveNativeModel === true,
|
|
2668
|
-
appServer: params.appServer,
|
|
2669
|
-
dynamicTools: params.dynamicTools,
|
|
2670
|
-
developerInstructions: params.developerInstructions,
|
|
2671
|
-
config: resumeConfig,
|
|
2672
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2673
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2674
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2675
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2676
|
-
hostSystemAgentActive,
|
|
2677
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
2678
|
-
shellEnvironment: params.shellEnvironment,
|
|
2679
|
-
disableLoginShell: params.disableLoginShell
|
|
2680
|
-
}));
|
|
2681
|
-
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2682
|
-
throwIfAborted();
|
|
2683
|
-
if (resumeBinding.preserveNativeModel === true) {
|
|
2684
|
-
const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
2685
|
-
threadId: resumeBinding.threadId,
|
|
2686
|
-
includeTurns: false
|
|
2687
|
-
}, { signal: params.signal }));
|
|
2688
|
-
throwIfAborted();
|
|
2689
|
-
if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
2690
|
-
}
|
|
2691
|
-
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
2692
|
-
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
2693
|
-
client: params.client,
|
|
2694
|
-
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
2695
|
-
request: resumeParams,
|
|
2696
|
-
signal: params.signal
|
|
2697
|
-
}));
|
|
2698
|
-
if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
|
|
2699
|
-
await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
|
|
2700
|
-
} catch (error) {
|
|
2701
|
-
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2702
|
-
throw new CodexRestrictedToolSurfaceAttestationError(error);
|
|
2703
|
-
}
|
|
2704
|
-
throwIfAborted();
|
|
2705
|
-
const boundAuthProfileId = authProfileId;
|
|
2706
|
-
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2707
|
-
const resumePatch = {
|
|
2708
|
-
clientId: resolveCodexAppServerClientInstanceId(params.client),
|
|
2709
|
-
cwd: params.cwd,
|
|
2710
|
-
rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
|
|
2711
|
-
authProfileId: boundAuthProfileId,
|
|
2712
|
-
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
2713
|
-
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
2714
|
-
modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
2715
|
-
dynamicToolsFingerprint,
|
|
2716
|
-
dynamicToolsContainDeferred,
|
|
2717
|
-
webSearchThreadConfigFingerprint,
|
|
2718
|
-
nativeSkillIsolationFingerprint,
|
|
2719
|
-
userMcpServersFingerprint,
|
|
2720
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2721
|
-
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2722
|
-
ringZeroConfigFingerprint,
|
|
2723
|
-
ringZeroClientInstanceId,
|
|
2724
|
-
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
2725
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2726
|
-
networkProxyConfigFingerprint,
|
|
2727
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
2728
|
-
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2729
|
-
pluginAppsFingerprint: context.prebuiltPluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
|
|
2730
|
-
pluginAppsInputFingerprint: context.prebuiltPluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
|
|
2731
|
-
pluginAppPolicyContext: context.prebuiltPluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
|
|
2732
|
-
contextEngine: contextEngineBinding,
|
|
2733
|
-
environmentSelectionFingerprint
|
|
2734
|
-
};
|
|
2735
|
-
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2736
|
-
kind: "patch",
|
|
2737
|
-
threadId: resumeBinding.threadId,
|
|
2738
|
-
patch: resumePatch
|
|
2739
|
-
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2740
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2741
|
-
sessionId: params.params.sessionId,
|
|
2742
|
-
sessionKey: params.params.sessionKey,
|
|
2743
|
-
threadId: response.thread.id,
|
|
2744
|
-
engineId: contextEngineBinding.engineId,
|
|
2745
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2746
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2747
|
-
action: "resumed"
|
|
2748
|
-
});
|
|
2749
|
-
lifecycleTiming.mark("thread-ready");
|
|
2750
|
-
lifecycleTiming.logSummary({
|
|
2751
|
-
runId: params.params.runId,
|
|
2752
|
-
sessionId: params.params.sessionId,
|
|
2753
|
-
sessionKey: params.params.sessionKey,
|
|
2754
|
-
threadId: response.thread.id,
|
|
2755
|
-
action: "resumed"
|
|
3104
|
+
async function readCodexScheduledAppToolsByApp(params) {
|
|
3105
|
+
const toolsByApp = /* @__PURE__ */ new Map();
|
|
3106
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3107
|
+
let cursor;
|
|
3108
|
+
for (let page = 0; page < MCP_STATUS_MAX_PAGES; page += 1) {
|
|
3109
|
+
const response = await params.request("mcpServerStatus/list", {
|
|
3110
|
+
...params.threadId ? { threadId: params.threadId } : {},
|
|
3111
|
+
detail: "toolsAndAuthOnly",
|
|
3112
|
+
limit: MCP_STATUS_PAGE_SIZE,
|
|
3113
|
+
...cursor ? { cursor } : {}
|
|
2756
3114
|
});
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
3115
|
+
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned invalid scheduled app inventory");
|
|
3116
|
+
for (const status of response.data) {
|
|
3117
|
+
if (!isJsonObject(status) || !isJsonObject(status.tools)) throw new Error("Codex scheduled app inventory contained an invalid server status");
|
|
3118
|
+
if (status.name !== CODEX_APPS_MCP_SERVER) continue;
|
|
3119
|
+
for (const [toolName, tool] of Object.entries(status.tools)) {
|
|
3120
|
+
const connectorId = readCodexMcpToolConnectorId(tool);
|
|
3121
|
+
if (connectorId) {
|
|
3122
|
+
const tools = toolsByApp.get(connectorId) ?? /* @__PURE__ */ new Map();
|
|
3123
|
+
const metadata = asOptionalRecord(tool);
|
|
3124
|
+
const annotations = asOptionalRecord(metadata?.annotations);
|
|
3125
|
+
tools.set(toolName, {
|
|
3126
|
+
title: typeof metadata?.title === "string" ? metadata.title : void 0,
|
|
3127
|
+
destructiveHint: annotations?.destructiveHint === false ? false : void 0,
|
|
3128
|
+
openWorldHint: annotations?.openWorldHint === false ? false : void 0
|
|
3129
|
+
});
|
|
3130
|
+
toolsByApp.set(connectorId, tools);
|
|
3131
|
+
}
|
|
2772
3132
|
}
|
|
2773
|
-
};
|
|
2774
|
-
} catch (error) {
|
|
2775
|
-
resumeReservation?.release();
|
|
2776
|
-
if (isCodexAppServerStartSelectionChangedError(error)) throw error;
|
|
2777
|
-
if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
|
|
2778
|
-
await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
|
|
2779
|
-
throw error;
|
|
2780
3133
|
}
|
|
2781
|
-
if (
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
if (
|
|
2785
|
-
|
|
2786
|
-
timeoutMs: 5e3
|
|
2787
|
-
}) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
2788
|
-
if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
|
|
2789
|
-
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
2790
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
3134
|
+
if (response.nextCursor !== void 0 && response.nextCursor !== null && typeof response.nextCursor !== "string") throw new Error("Codex scheduled app inventory returned an invalid pagination cursor");
|
|
3135
|
+
cursor = response.nextCursor;
|
|
3136
|
+
if (!cursor) return toolsByApp;
|
|
3137
|
+
if (seenCursors.has(cursor)) throw new Error("Codex app connector inventory repeated its pagination cursor");
|
|
3138
|
+
seenCursors.add(cursor);
|
|
2791
3139
|
}
|
|
3140
|
+
throw new Error("Codex app connector inventory exceeded its bounded page limit");
|
|
3141
|
+
}
|
|
3142
|
+
/** Reads current account policy and connector-backed tool metadata under one caller deadline. */
|
|
3143
|
+
async function readCurrentCodexScheduledAppPolicy(params) {
|
|
3144
|
+
const [configResponse, toolsByApp] = await Promise.all([params.request("config/read", {
|
|
3145
|
+
includeLayers: false,
|
|
3146
|
+
...params.configCwd ? { cwd: params.configCwd } : {}
|
|
3147
|
+
}), readCodexScheduledAppToolsByApp(params)]);
|
|
3148
|
+
if (!isJsonObject(configResponse)) throw new Error("Codex config/read returned an invalid scheduled app policy response");
|
|
3149
|
+
return {
|
|
3150
|
+
config: isJsonObject(configResponse.config) ? configResponse.config : {},
|
|
3151
|
+
toolsByApp
|
|
3152
|
+
};
|
|
2792
3153
|
}
|
|
2793
|
-
|
|
2794
|
-
const
|
|
2795
|
-
const
|
|
2796
|
-
const
|
|
2797
|
-
const
|
|
2798
|
-
|
|
2799
|
-
|
|
3154
|
+
function readCurrentToolPolicy(config, appId, toolName, metadata, fallbackApprovalMode = "auto") {
|
|
3155
|
+
const apps = asOptionalRecord(config.apps);
|
|
3156
|
+
const app = asOptionalRecord(apps?.[appId]);
|
|
3157
|
+
const defaults = asOptionalRecord(apps?.["_default"]);
|
|
3158
|
+
const tools = asOptionalRecord(app?.tools);
|
|
3159
|
+
const tool = asOptionalRecord(tools?.[toolName] ?? (metadata?.title !== void 0 ? tools?.[metadata.title] : void 0));
|
|
3160
|
+
const defaultToolsEnabled = app?.default_tools_enabled;
|
|
3161
|
+
return {
|
|
3162
|
+
enabled: (app ? app.enabled !== false : defaults?.enabled !== false) && (typeof tool?.enabled === "boolean" ? tool.enabled : typeof defaultToolsEnabled === "boolean" ? defaultToolsEnabled : appToolHintsAllowed(metadata, {
|
|
3163
|
+
allowDestructiveActions: (app?.destructive_enabled ?? defaults?.destructive_enabled) !== false,
|
|
3164
|
+
allowOpenWorld: (app?.open_world_enabled ?? defaults?.open_world_enabled) !== false
|
|
3165
|
+
})),
|
|
3166
|
+
approvalMode: normalizeAppToolApprovalMode(tool?.approval_mode) ?? normalizeAppToolApprovalMode(app?.default_tools_approval_mode) ?? normalizeAppToolApprovalMode(defaults?.default_tools_approval_mode) ?? fallbackApprovalMode
|
|
2800
3167
|
};
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
hostSystemAgentActive,
|
|
2816
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
2817
|
-
shellEnvironment: params.shellEnvironment,
|
|
2818
|
-
disableLoginShell: params.disableLoginShell
|
|
2819
|
-
}));
|
|
2820
|
-
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2821
|
-
const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2822
|
-
try {
|
|
2823
|
-
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2824
|
-
} catch (error) {
|
|
2825
|
-
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2826
|
-
throw error;
|
|
2827
|
-
}
|
|
2828
|
-
}));
|
|
2829
|
-
const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
|
|
2830
|
-
if (provisionalAppIds?.length) try {
|
|
2831
|
-
await lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
|
|
2832
|
-
client: params.client,
|
|
2833
|
-
threadId: response.thread.id,
|
|
2834
|
-
appIds: provisionalAppIds,
|
|
3168
|
+
}
|
|
3169
|
+
function appToolHintsAllowed(tool, policy) {
|
|
3170
|
+
return (policy.allowDestructiveActions || tool?.destructiveHint === false) && (policy.allowOpenWorld !== false || tool?.openWorldHint === false);
|
|
3171
|
+
}
|
|
3172
|
+
/** Captures only apps callable on the exact active Codex client/thread. */
|
|
3173
|
+
async function captureScheduledCodexAppAuthority(params) {
|
|
3174
|
+
const requestedTimeoutMs = params.timeoutMs ?? CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS;
|
|
3175
|
+
const timeoutMs = Math.min(CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS, Math.max(CODEX_APP_AUTHORITY_CAPTURE_MIN_TIMEOUT_MS, Number.isFinite(requestedTimeoutMs) ? Math.floor(requestedTimeoutMs) : CODEX_APP_AUTHORITY_CAPTURE_TIMEOUT_MS));
|
|
3176
|
+
const deadlineMs = Date.now() + timeoutMs;
|
|
3177
|
+
const boundedClient = { request: ((method, requestParams) => {
|
|
3178
|
+
const remainingTimeoutMs = deadlineMs - Date.now();
|
|
3179
|
+
if (remainingTimeoutMs <= 0) throw new CodexScheduledAppAuthorityCaptureTimeoutError();
|
|
3180
|
+
return params.client.request(method, requestParams, {
|
|
3181
|
+
timeoutMs: remainingTimeoutMs,
|
|
2835
3182
|
signal: params.signal
|
|
2836
|
-
})
|
|
3183
|
+
});
|
|
3184
|
+
}) };
|
|
3185
|
+
let installed;
|
|
3186
|
+
let currentPolicy;
|
|
3187
|
+
let auth;
|
|
3188
|
+
const creatorAuth = params.auth;
|
|
3189
|
+
try {
|
|
3190
|
+
[installed, currentPolicy, auth] = await withAbortableTimeout({
|
|
3191
|
+
promise: Promise.all([
|
|
3192
|
+
boundedClient.request("app/installed", {
|
|
3193
|
+
threadId: params.threadId,
|
|
3194
|
+
forceRefresh: false
|
|
3195
|
+
}),
|
|
3196
|
+
readCurrentCodexScheduledAppPolicy({
|
|
3197
|
+
request: (method, requestParams) => boundedClient.request(method, requestParams),
|
|
3198
|
+
threadId: params.threadId,
|
|
3199
|
+
configCwd: params.configCwd
|
|
3200
|
+
}),
|
|
3201
|
+
creatorAuth.kind === "prepared-profile" ? Promise.resolve({
|
|
3202
|
+
profileId: creatorAuth.profileId,
|
|
3203
|
+
accountId: creatorAuth.accountId
|
|
3204
|
+
}) : readCodexManagedRequirementsFingerprint(boundedClient, params.signal).then((managedRequirementsFingerprint) => ({
|
|
3205
|
+
kind: creatorAuth.kind,
|
|
3206
|
+
connectionFingerprint: creatorAuth.connectionFingerprint,
|
|
3207
|
+
managedRequirementsFingerprint
|
|
3208
|
+
}))
|
|
3209
|
+
]),
|
|
3210
|
+
timeoutMs,
|
|
3211
|
+
signal: params.signal,
|
|
3212
|
+
timeoutMessage: "Codex scheduled app authority capture deadline elapsed",
|
|
3213
|
+
createTimeoutError: () => new CodexScheduledAppAuthorityCaptureTimeoutError()
|
|
3214
|
+
});
|
|
2837
3215
|
} catch (error) {
|
|
2838
|
-
if (!
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
3216
|
+
if (params.signal?.aborted || !(error instanceof CodexScheduledAppAuthorityCaptureTimeoutError) && !isCodexAppServerRequestTimeoutError(error)) throw error;
|
|
3217
|
+
throw new Error(`Codex app authority capture exceeded its ${timeoutMs} ms total budget. No automation changes were saved; retry after Codex app inventory is responsive.`, { cause: error });
|
|
3218
|
+
}
|
|
3219
|
+
const callableIds = new Set(installed.apps.filter((app) => app.enabled && app.callable).map((app) => app.id));
|
|
3220
|
+
const apps = Object.entries(params.policyContext.apps).filter(([id]) => callableIds.has(id) && currentPolicy.toolsByApp.has(id)).map(([id, policy]) => ({
|
|
3221
|
+
id,
|
|
3222
|
+
allowDestructiveActions: policy.allowDestructiveActions,
|
|
3223
|
+
allowOpenWorld: policy.allowOpenWorld !== false,
|
|
3224
|
+
destructiveApprovalMode: defaultApprovalMode(policy),
|
|
3225
|
+
tools: Object.fromEntries([...currentPolicy.toolsByApp.get(id)?.keys() ?? []].toSorted().map((toolName) => [toolName, readCurrentToolPolicy(currentPolicy.config, id, toolName, currentPolicy.toolsByApp.get(id)?.get(toolName), appApprovalCeiling(defaultApprovalMode(policy))).approvalMode]))
|
|
3226
|
+
})).toSorted((left, right) => left.id.localeCompare(right.id));
|
|
3227
|
+
if (apps.length === 0) return;
|
|
3228
|
+
return {
|
|
3229
|
+
version: 1,
|
|
3230
|
+
runtimeId: "codex",
|
|
3231
|
+
namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
|
|
3232
|
+
payload: {
|
|
3233
|
+
version: 1,
|
|
3234
|
+
auth,
|
|
3235
|
+
apps
|
|
2845
3236
|
}
|
|
2846
|
-
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
var CodexScheduledAppAuthorityCaptureTimeoutError = class extends Error {
|
|
3240
|
+
constructor() {
|
|
3241
|
+
super("Codex scheduled app authority capture deadline elapsed");
|
|
3242
|
+
this.name = "CodexScheduledAppAuthorityCaptureTimeoutError";
|
|
2847
3243
|
}
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3244
|
+
};
|
|
3245
|
+
const APPROVAL_RANK = {
|
|
3246
|
+
deny: 0,
|
|
3247
|
+
ask: 1,
|
|
3248
|
+
auto: 2,
|
|
3249
|
+
allow: 3
|
|
3250
|
+
};
|
|
3251
|
+
function stricterApprovalMode(left, right) {
|
|
3252
|
+
return APPROVAL_RANK[left] <= APPROVAL_RANK[right] ? left : right;
|
|
3253
|
+
}
|
|
3254
|
+
function intersectToolApprovalMode(captured, current) {
|
|
3255
|
+
if (captured === current) return captured;
|
|
3256
|
+
if (captured === "prompt" || current === "prompt") return "prompt";
|
|
3257
|
+
if (captured === "approve") return current;
|
|
3258
|
+
if (current === "approve") return captured;
|
|
3259
|
+
return "prompt";
|
|
3260
|
+
}
|
|
3261
|
+
function appApprovalCeiling(mode) {
|
|
3262
|
+
if (mode === "allow") return "approve";
|
|
3263
|
+
return mode === "ask" ? "prompt" : "auto";
|
|
3264
|
+
}
|
|
3265
|
+
function stableStringify(value) {
|
|
3266
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
3267
|
+
if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
|
|
3268
|
+
return JSON.stringify(value);
|
|
3269
|
+
}
|
|
3270
|
+
/** Intersects a stored app-ID cap with current policy without admitting new apps. */
|
|
3271
|
+
function intersectCodexPluginThreadConfigWithScheduledAuthority(config, authority, currentPolicy = {
|
|
3272
|
+
config: {},
|
|
3273
|
+
toolsByApp: /* @__PURE__ */ new Map()
|
|
3274
|
+
}) {
|
|
3275
|
+
const scheduled = parseScheduledCodexAppAuthority(authority);
|
|
3276
|
+
if (!scheduled) return config;
|
|
3277
|
+
const omittedAppIds = scheduled.apps.map((app) => app.id).filter((id) => {
|
|
3278
|
+
const currentTools = currentPolicy.toolsByApp.get(id);
|
|
3279
|
+
return !Object.hasOwn(config.policyContext.apps, id) || !currentTools || currentTools.size === 0;
|
|
3280
|
+
}).toSorted();
|
|
3281
|
+
if (omittedAppIds.length > 0) {
|
|
3282
|
+
const visibleIds = omittedAppIds.slice(0, 10).join(", ");
|
|
3283
|
+
const remaining = omittedAppIds.length - Math.min(omittedAppIds.length, 10);
|
|
3284
|
+
throw new AgentHarnessPreflightError(`Scheduled Codex apps are unavailable under the current policy or account: ${visibleIds}${remaining > 0 ? ` (and ${remaining} more)` : ""}. Restore access or reauthorize the automation from a fresh authenticated Codex owner turn.`);
|
|
2854
3285
|
}
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
throw error;
|
|
3286
|
+
const capturedById = new Map(scheduled.apps.map((app) => [app.id, app]));
|
|
3287
|
+
const apps = {};
|
|
3288
|
+
for (const [id, current] of Object.entries(config.policyContext.apps)) {
|
|
3289
|
+
const captured = capturedById.get(id);
|
|
3290
|
+
if (!captured) continue;
|
|
3291
|
+
apps[id] = {
|
|
3292
|
+
...current,
|
|
3293
|
+
allowDestructiveActions: current.allowDestructiveActions && captured.allowDestructiveActions,
|
|
3294
|
+
allowOpenWorld: current.allowOpenWorld !== false && captured.allowOpenWorld,
|
|
3295
|
+
destructiveApprovalMode: stricterApprovalMode(defaultApprovalMode(current), captured.destructiveApprovalMode)
|
|
3296
|
+
};
|
|
2869
3297
|
}
|
|
2870
|
-
const
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
const
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
modelProvider: bindingModelProvider,
|
|
2889
|
-
dynamicToolsFingerprint,
|
|
2890
|
-
dynamicToolsContainDeferred,
|
|
2891
|
-
webSearchThreadConfigFingerprint,
|
|
2892
|
-
nativeSkillIsolationFingerprint,
|
|
2893
|
-
userMcpServersFingerprint,
|
|
2894
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2895
|
-
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2896
|
-
ringZeroConfigFingerprint,
|
|
2897
|
-
ringZeroClientInstanceId,
|
|
2898
|
-
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
2899
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2900
|
-
networkProxyConfigFingerprint,
|
|
2901
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2902
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2903
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2904
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2905
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2906
|
-
contextEngine: contextEngineBinding,
|
|
2907
|
-
environmentSelectionFingerprint
|
|
2908
|
-
};
|
|
2909
|
-
const cleanupUncommittedSuccessor = async (cause) => {
|
|
2910
|
-
if (!await discardUnattestedCodexPluginThread({
|
|
2911
|
-
client: params.client,
|
|
2912
|
-
threadId: response.thread.id,
|
|
2913
|
-
ephemeral: startParams.ephemeral === true
|
|
2914
|
-
})) {
|
|
2915
|
-
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2916
|
-
throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
|
|
2917
|
-
}
|
|
2918
|
-
};
|
|
2919
|
-
const managedSourceHomeId = resolveManagedThreadSourceHomeId(params);
|
|
2920
|
-
await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
|
|
2921
|
-
sourceHomeId: managedSourceHomeId,
|
|
2922
|
-
threadId: response.thread.id,
|
|
2923
|
-
...rolloutPath ? { rolloutPath } : {}
|
|
3298
|
+
const policyContext = buildPluginAppPolicyContext(apps, Object.fromEntries(Object.entries(config.policyContext.pluginAppIds).map(([key, ids]) => [key, ids.filter((id) => Object.hasOwn(apps, id))]).filter(([, ids]) => ids.length > 0)));
|
|
3299
|
+
const configPatch = buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext);
|
|
3300
|
+
const appsPatch = asOptionalRecord(configPatch.apps);
|
|
3301
|
+
for (const [appId, captured] of capturedById) {
|
|
3302
|
+
const appPatch = asOptionalRecord(appsPatch?.[appId]);
|
|
3303
|
+
if (!appPatch || !Object.hasOwn(apps, appId)) continue;
|
|
3304
|
+
const currentApp = apps[appId];
|
|
3305
|
+
if (!currentApp) continue;
|
|
3306
|
+
const storedAppCeiling = appApprovalCeiling(captured.destructiveApprovalMode);
|
|
3307
|
+
const currentAppCeiling = appApprovalCeiling(defaultApprovalMode(currentApp));
|
|
3308
|
+
const tools = currentPolicy.toolsByApp.get(appId) ?? /* @__PURE__ */ new Map();
|
|
3309
|
+
appPatch.tools = Object.fromEntries([...tools.keys()].toSorted().map((toolName) => {
|
|
3310
|
+
const capturedMode = captured.tools[toolName] ?? storedAppCeiling;
|
|
3311
|
+
const currentToolPolicy = readCurrentToolPolicy(currentPolicy.config, appId, toolName, tools.get(toolName), currentAppCeiling);
|
|
3312
|
+
return [toolName, {
|
|
3313
|
+
enabled: currentToolPolicy.enabled && appToolHintsAllowed(tools.get(toolName), currentApp),
|
|
3314
|
+
approval_mode: intersectToolApprovalMode(intersectToolApprovalMode(capturedMode, storedAppCeiling), intersectToolApprovalMode(currentToolPolicy.approvalMode, currentAppCeiling))
|
|
3315
|
+
}];
|
|
2924
3316
|
}));
|
|
2925
|
-
let committed;
|
|
2926
|
-
try {
|
|
2927
|
-
committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
|
|
2928
|
-
kind: "replace-thread",
|
|
2929
|
-
expectedThreadId: replacementPredecessor.threadId,
|
|
2930
|
-
binding: nextBinding
|
|
2931
|
-
} : {
|
|
2932
|
-
kind: "set",
|
|
2933
|
-
if: { kind: "absent" },
|
|
2934
|
-
binding: nextBinding
|
|
2935
|
-
}));
|
|
2936
|
-
} catch (error) {
|
|
2937
|
-
if (replacementPredecessor) await cleanupUncommittedSuccessor(error);
|
|
2938
|
-
throw error;
|
|
2939
|
-
}
|
|
2940
|
-
if (!committed) {
|
|
2941
|
-
if (replacementPredecessor) await cleanupUncommittedSuccessor();
|
|
2942
|
-
throw new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread");
|
|
2943
|
-
}
|
|
2944
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2945
|
-
sessionId: params.params.sessionId,
|
|
2946
|
-
sessionKey: params.params.sessionKey,
|
|
2947
|
-
threadId: response.thread.id,
|
|
2948
|
-
engineId: contextEngineBinding.engineId,
|
|
2949
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2950
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2951
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2952
|
-
});
|
|
2953
|
-
}
|
|
2954
|
-
lifecycleTiming.mark("thread-ready");
|
|
2955
|
-
lifecycleTiming.logSummary({
|
|
2956
|
-
runId: params.params.runId,
|
|
2957
|
-
sessionId: params.params.sessionId,
|
|
2958
|
-
sessionKey: params.params.sessionKey,
|
|
2959
|
-
threadId: response.thread.id,
|
|
2960
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2961
|
-
});
|
|
2962
|
-
return buildStartedCodexThreadBinding({
|
|
2963
|
-
bindingModelProvider,
|
|
2964
|
-
clientId,
|
|
2965
|
-
context,
|
|
2966
|
-
finalConfigPatch,
|
|
2967
|
-
nextMcpServersFingerprint,
|
|
2968
|
-
params,
|
|
2969
|
-
pluginThreadConfig,
|
|
2970
|
-
response,
|
|
2971
|
-
rolloutPath,
|
|
2972
|
-
startModelProvider: requestModelProvider ?? startModelProvider,
|
|
2973
|
-
startParams,
|
|
2974
|
-
modelProvider
|
|
2975
|
-
});
|
|
2976
|
-
}
|
|
2977
|
-
//#endregion
|
|
2978
|
-
//#region extensions/codex/src/app-server/dynamic-tool-response-state.ts
|
|
2979
|
-
/** Retains the host-owned app preview without adding it to Codex's response payload. */
|
|
2980
|
-
function withDynamicToolTranscriptDetails(response, details) {
|
|
2981
|
-
if (details === void 0) return response;
|
|
2982
|
-
Object.defineProperty(response, "transcriptDetails", {
|
|
2983
|
-
configurable: true,
|
|
2984
|
-
enumerable: false,
|
|
2985
|
-
value: details
|
|
2986
|
-
});
|
|
2987
|
-
return response;
|
|
2988
|
-
}
|
|
2989
|
-
function withDynamicToolTerminalResolution(response, terminalResolution) {
|
|
2990
|
-
if (terminalResolution) {
|
|
2991
|
-
Object.defineProperties(response, {
|
|
2992
|
-
terminalResolution: {
|
|
2993
|
-
configurable: true,
|
|
2994
|
-
enumerable: false,
|
|
2995
|
-
value: terminalResolution
|
|
2996
|
-
},
|
|
2997
|
-
executionStarted: {
|
|
2998
|
-
configurable: true,
|
|
2999
|
-
enumerable: false,
|
|
3000
|
-
value: terminalResolution.executionStarted
|
|
3001
|
-
},
|
|
3002
|
-
...terminalResolution.executedArguments ? { executedArguments: {
|
|
3003
|
-
configurable: true,
|
|
3004
|
-
enumerable: false,
|
|
3005
|
-
value: terminalResolution.executedArguments
|
|
3006
|
-
} } : {}
|
|
3007
|
-
});
|
|
3008
|
-
withDynamicToolSideEffectEvidence(response, terminalResolution.sideEffectEvidence);
|
|
3009
|
-
}
|
|
3010
|
-
return response;
|
|
3011
|
-
}
|
|
3012
|
-
function withDynamicToolExecutionState(response, state) {
|
|
3013
|
-
Object.defineProperties(response, {
|
|
3014
|
-
executedArguments: {
|
|
3015
|
-
configurable: true,
|
|
3016
|
-
enumerable: false,
|
|
3017
|
-
value: state.executedArguments
|
|
3018
|
-
},
|
|
3019
|
-
executionStarted: {
|
|
3020
|
-
configurable: true,
|
|
3021
|
-
enumerable: false,
|
|
3022
|
-
value: state.executionStarted
|
|
3023
|
-
}
|
|
3024
|
-
});
|
|
3025
|
-
return withDynamicToolSideEffectEvidence(response, state.sideEffectEvidence === true);
|
|
3026
|
-
}
|
|
3027
|
-
function withDynamicToolSideEffectEvidence(response, sideEffectEvidence) {
|
|
3028
|
-
if (!sideEffectEvidence) {
|
|
3029
|
-
delete response.sideEffectEvidence;
|
|
3030
|
-
return response;
|
|
3031
3317
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3318
|
+
const fingerprint = crypto.createHash("sha256").update(stableStringify({
|
|
3319
|
+
version: 1,
|
|
3320
|
+
namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
|
|
3321
|
+
authority: scheduled,
|
|
3322
|
+
inputFingerprint: config.inputFingerprint,
|
|
3323
|
+
policyContext,
|
|
3324
|
+
configPatch
|
|
3325
|
+
})).digest("hex");
|
|
3326
|
+
return {
|
|
3327
|
+
...config,
|
|
3328
|
+
fingerprint,
|
|
3329
|
+
configPatch,
|
|
3330
|
+
provisionalAppIds: Object.keys(apps).toSorted(),
|
|
3331
|
+
policyContext
|
|
3046
3332
|
};
|
|
3047
|
-
Object.defineProperties(response, {
|
|
3048
|
-
diagnosticTerminalReason: {
|
|
3049
|
-
configurable: true,
|
|
3050
|
-
enumerable: false,
|
|
3051
|
-
value: options?.terminalReason ?? "failed"
|
|
3052
|
-
},
|
|
3053
|
-
diagnosticTerminalType: {
|
|
3054
|
-
configurable: true,
|
|
3055
|
-
enumerable: false,
|
|
3056
|
-
value: "error"
|
|
3057
|
-
}
|
|
3058
|
-
});
|
|
3059
|
-
if (options?.executionStarted !== void 0) Object.defineProperty(response, "executionStarted", {
|
|
3060
|
-
configurable: true,
|
|
3061
|
-
enumerable: false,
|
|
3062
|
-
value: options.executionStarted
|
|
3063
|
-
});
|
|
3064
|
-
if (options?.executedArguments !== void 0) Object.defineProperty(response, "executedArguments", {
|
|
3065
|
-
configurable: true,
|
|
3066
|
-
enumerable: false,
|
|
3067
|
-
value: options.executedArguments
|
|
3068
|
-
});
|
|
3069
|
-
return withDynamicToolSideEffectEvidence(response, options?.sideEffectEvidence === true);
|
|
3070
3333
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
const reason = signal.reason;
|
|
3084
|
-
if (typeof reason === "string") {
|
|
3085
|
-
if (CODEX_TIMEOUT_ABORT_REASONS.has(reason)) return "timed_out";
|
|
3086
|
-
return reason === "client_closed" ? "failed" : "cancelled";
|
|
3087
|
-
}
|
|
3088
|
-
if (reason && typeof reason === "object") {
|
|
3089
|
-
const record = reason;
|
|
3090
|
-
if (record.name === "TimeoutError" || record.reason === "timeout") return "timed_out";
|
|
3091
|
-
}
|
|
3092
|
-
} catch {
|
|
3093
|
-
return "cancelled";
|
|
3334
|
+
/** Returns the managed-requirements identity captured for a configured app-server job. */
|
|
3335
|
+
function readScheduledCodexAppManagedRequirementsFingerprint(authority) {
|
|
3336
|
+
const auth = parseScheduledCodexAppAuthority(authority)?.auth;
|
|
3337
|
+
return auth?.kind === "configured-app-server" ? auth.managedRequirementsFingerprint : void 0;
|
|
3338
|
+
}
|
|
3339
|
+
function assertScheduledCodexAppAuthorityRuntime(connection, params) {
|
|
3340
|
+
const scheduledAuth = parseScheduledCodexAppAuthority(params.scheduledRuntimeAuthority)?.auth;
|
|
3341
|
+
if (!scheduledAuth) return;
|
|
3342
|
+
if (params.trigger !== "cron" || connection.usesSupervisionConnection || connection.appServer.start.homeScope === "user") throw new AgentHarnessPreflightError("This automation's Codex app authority requires an isolated scheduled runtime. Reauthorize it from a supported Codex creator turn.");
|
|
3343
|
+
if (scheduledAuth.kind === "configured-app-server") {
|
|
3344
|
+
if (buildScheduledCodexAppServerConnectionIdentity(connection.appServer) !== scheduledAuth.connectionFingerprint) throw new AgentHarnessPreflightError("This automation was authorized for a different configured Codex app-server. Restore that connection or reauthorize the automation from a fresh owner turn.");
|
|
3345
|
+
return;
|
|
3094
3346
|
}
|
|
3095
|
-
|
|
3347
|
+
const prepared = connection.startupPreparedAuth;
|
|
3348
|
+
if (prepared?.kind !== "profile" || prepared.profileId !== scheduledAuth.profileId || prepared.snapshot?.loginParams.type !== "chatgptAuthTokens" || prepared.snapshot.chatgptAccountId !== scheduledAuth.accountId) throw new AgentHarnessPreflightError(`This automation was authorized for Codex profile ${scheduledAuth.profileId}, but that exact prepared account is not active. Restore the profile or reauthorize the automation from a fresh owner turn.`);
|
|
3349
|
+
}
|
|
3350
|
+
function buildLegacyScheduledCodexAppRecoveryPrompt(params) {
|
|
3351
|
+
if (params.trigger !== "cron" || !params.scheduledRuntimeAuthorityRecoveryRequired || params.scheduledRuntimeAuthority) return;
|
|
3352
|
+
return "Scheduled Codex app access is unavailable because this automation predates runtime-specific app authority capture. Tell the operator to recreate or reauthorize it from a fresh authenticated Codex owner turn; do not claim an app action succeeded.";
|
|
3353
|
+
}
|
|
3354
|
+
/** Makes stored-cap identity part of thread reuse admission, including cap removal. */
|
|
3355
|
+
function buildScheduledCodexAppAuthorityInputFingerprint(baseFingerprint, authority) {
|
|
3356
|
+
const scheduled = parseScheduledCodexAppAuthority(authority);
|
|
3357
|
+
if (!scheduled) return baseFingerprint;
|
|
3358
|
+
return crypto.createHash("sha256").update(stableStringify({
|
|
3359
|
+
version: 1,
|
|
3360
|
+
namespace: CODEX_SCHEDULED_APP_AUTHORITY_NAMESPACE,
|
|
3361
|
+
baseFingerprint,
|
|
3362
|
+
authority: scheduled
|
|
3363
|
+
})).digest("hex");
|
|
3096
3364
|
}
|
|
3097
3365
|
//#endregion
|
|
3098
|
-
//#region extensions/codex/src/app-server/
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
/** Hard cap for per-call Codex dynamic tool timeout overrides. */
|
|
3106
|
-
const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
|
|
3107
|
-
const CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS = 3e4;
|
|
3108
|
-
const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
|
|
3109
|
-
const CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS = 3e4;
|
|
3110
|
-
const CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS = 3e4;
|
|
3111
|
-
/** Timeout for image-understanding style dynamic tool calls. */
|
|
3112
|
-
const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
|
|
3113
|
-
/** Timeout for message-delivery dynamic tool calls. */
|
|
3114
|
-
const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 6e5;
|
|
3115
|
-
/** Outer default for collector waits: full swarm budget plus completion grace. */
|
|
3116
|
-
const CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS = 63e4;
|
|
3117
|
-
const LOG_FIELD_MAX_LENGTH = 160;
|
|
3118
|
-
function normalizeLogField(value) {
|
|
3119
|
-
if (typeof value !== "string") return;
|
|
3120
|
-
const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
|
|
3121
|
-
if (!normalized) return;
|
|
3122
|
-
return normalized.length > LOG_FIELD_MAX_LENGTH ? `${truncateUtf16Safe(normalized, LOG_FIELD_MAX_LENGTH - 3)}...` : normalized;
|
|
3123
|
-
}
|
|
3124
|
-
function readNumericTimeoutMs(value) {
|
|
3125
|
-
if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
|
|
3126
|
-
if (typeof value === "string") {
|
|
3127
|
-
const parsed = parseStrictNonNegativeInteger(value);
|
|
3128
|
-
if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
|
|
3129
|
-
}
|
|
3366
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
|
|
3367
|
+
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
|
|
3368
|
+
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
|
|
3369
|
+
function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
|
|
3370
|
+
const totalThresholdMs = options.totalThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS;
|
|
3371
|
+
const stageThresholdMs = options.stageThresholdMs ?? CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS;
|
|
3372
|
+
return summary.totalMs >= totalThresholdMs || summary.spans.some((span) => span.durationMs >= stageThresholdMs);
|
|
3130
3373
|
}
|
|
3131
|
-
function
|
|
3132
|
-
const
|
|
3133
|
-
|
|
3134
|
-
tool: params.call.tool,
|
|
3135
|
-
toolCallId: params.call.callId,
|
|
3136
|
-
threadId: params.call.threadId,
|
|
3137
|
-
turnId: params.call.turnId,
|
|
3138
|
-
timeoutMs: params.timeoutMs,
|
|
3139
|
-
timeoutKind: "codex_dynamic_tool_rpc"
|
|
3140
|
-
};
|
|
3141
|
-
if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
|
|
3142
|
-
responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
|
|
3143
|
-
consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
|
|
3144
|
-
meta: baseMeta
|
|
3145
|
-
};
|
|
3146
|
-
const action = normalizeLogField(params.call.arguments.action);
|
|
3147
|
-
const sessionId = normalizeLogField(params.call.arguments.sessionId);
|
|
3148
|
-
const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
|
|
3149
|
-
const actionPart = action ? ` action=${action}` : "";
|
|
3150
|
-
const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
|
|
3151
|
-
const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
|
|
3152
|
-
const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
|
|
3153
|
-
const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
|
|
3154
|
-
return {
|
|
3155
|
-
responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
|
|
3156
|
-
consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
|
|
3157
|
-
meta: {
|
|
3158
|
-
...baseMeta,
|
|
3159
|
-
processAction: action,
|
|
3160
|
-
processSessionId: sessionId,
|
|
3161
|
-
processRequestedTimeoutMs: requestedTimeoutMs
|
|
3162
|
-
}
|
|
3163
|
-
};
|
|
3374
|
+
function formatCodexThreadLifecycleTimingSummary(params) {
|
|
3375
|
+
const spans = params.summary.spans.length > 0 ? params.summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
|
|
3376
|
+
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}`;
|
|
3164
3377
|
}
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
const terminalResolution = params.observeToolTerminal?.({
|
|
3177
|
-
toolCallId: params.call.callId,
|
|
3178
|
-
toolName: params.call.tool,
|
|
3179
|
-
arguments: response.executedArguments ?? executionSnapshot?.executedArguments ?? params.call.arguments,
|
|
3180
|
-
...params.toolMeta ? { meta: params.toolMeta } : {},
|
|
3181
|
-
...ownerKey ? { ownerMutation: { ownerKey } } : {},
|
|
3182
|
-
...observedExecutionStarted !== void 0 ? { executionStarted: observedExecutionStarted } : {},
|
|
3183
|
-
outcome: response.success ? "success" : "failure",
|
|
3184
|
-
...!response.success ? { failure: { error: readDynamicToolResponseText(response) } } : {}
|
|
3185
|
-
});
|
|
3186
|
-
return withDynamicToolTerminalResolution(response, terminalResolution);
|
|
3187
|
-
};
|
|
3188
|
-
const createFailedAfterPossibleDispatch = (message, terminalReason) => {
|
|
3189
|
-
const response = createFailedDynamicToolResponse(message, {
|
|
3190
|
-
executionStarted: true,
|
|
3191
|
-
sideEffectEvidence: true,
|
|
3192
|
-
terminalReason
|
|
3193
|
-
});
|
|
3194
|
-
conservativeRaceResponses.add(response);
|
|
3195
|
-
return response;
|
|
3196
|
-
};
|
|
3197
|
-
const notifyAgentToolResult = (event) => {
|
|
3198
|
-
if (didNotifyAgentToolResult) return;
|
|
3199
|
-
didNotifyAgentToolResult = true;
|
|
3200
|
-
try {
|
|
3201
|
-
params.onAgentToolResult?.(event);
|
|
3202
|
-
} catch (error) {
|
|
3203
|
-
embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
|
|
3204
|
-
}
|
|
3378
|
+
function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
3379
|
+
const log = options.log ?? embeddedAgentLog;
|
|
3380
|
+
if (!options.enabled && log.isEnabled?.("trace") !== true) return {
|
|
3381
|
+
async measure(_name, run) {
|
|
3382
|
+
return await run();
|
|
3383
|
+
},
|
|
3384
|
+
measureSync(_name, run) {
|
|
3385
|
+
return run();
|
|
3386
|
+
},
|
|
3387
|
+
mark() {},
|
|
3388
|
+
logSummary() {}
|
|
3205
3389
|
};
|
|
3206
|
-
const
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
}
|
|
3218
|
-
},
|
|
3219
|
-
isError: true
|
|
3390
|
+
const now = options.now ?? Date.now;
|
|
3391
|
+
const startedAt = now();
|
|
3392
|
+
let didLog = false;
|
|
3393
|
+
const spans = [];
|
|
3394
|
+
const toMs = (value) => Math.max(0, Math.round(value));
|
|
3395
|
+
const record = (name, spanStartedAt) => {
|
|
3396
|
+
const currentAt = now();
|
|
3397
|
+
spans.push({
|
|
3398
|
+
name,
|
|
3399
|
+
durationMs: toMs(currentAt - spanStartedAt),
|
|
3400
|
+
elapsedMs: toMs(currentAt - startedAt)
|
|
3220
3401
|
});
|
|
3221
3402
|
};
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
params.onFallbackSelected?.();
|
|
3226
|
-
notifyFailedToolResult(message, terminalReason);
|
|
3227
|
-
return finalizeTerminal(createFailedDynamicToolResponse(message, {
|
|
3228
|
-
executionStarted: false,
|
|
3229
|
-
terminalReason
|
|
3230
|
-
}));
|
|
3231
|
-
}
|
|
3232
|
-
const controller = new AbortController();
|
|
3233
|
-
let timeout;
|
|
3234
|
-
let timedOut = false;
|
|
3235
|
-
let resolveAbort;
|
|
3236
|
-
const abortFromRun = () => {
|
|
3237
|
-
const message = "OpenClaw dynamic tool call aborted.";
|
|
3238
|
-
const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
|
|
3239
|
-
params.onFallbackSelected?.();
|
|
3240
|
-
controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
|
|
3241
|
-
notifyFailedToolResult(message, terminalReason);
|
|
3242
|
-
resolveAbort?.(createFailedAfterPossibleDispatch(message, terminalReason));
|
|
3243
|
-
};
|
|
3244
|
-
const abortPromise = new Promise((resolve) => {
|
|
3245
|
-
resolveAbort = resolve;
|
|
3246
|
-
});
|
|
3247
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
3248
|
-
const timeoutMs = clampDynamicToolTimeoutMs(params.timeoutMs);
|
|
3249
|
-
timeout = setTimeout(() => {
|
|
3250
|
-
timedOut = true;
|
|
3251
|
-
const timeoutDetails = formatDynamicToolTimeoutDetails({
|
|
3252
|
-
call: params.call,
|
|
3253
|
-
timeoutMs
|
|
3254
|
-
});
|
|
3255
|
-
params.onFallbackSelected?.();
|
|
3256
|
-
controller.abort(new Error(timeoutDetails.responseMessage));
|
|
3257
|
-
params.onTimeout?.();
|
|
3258
|
-
embeddedAgentLog.warn("codex dynamic tool call timed out", {
|
|
3259
|
-
...timeoutDetails.meta,
|
|
3260
|
-
consoleMessage: timeoutDetails.consoleMessage
|
|
3261
|
-
});
|
|
3262
|
-
notifyFailedToolResult(timeoutDetails.responseMessage, "timed_out");
|
|
3263
|
-
resolve(createFailedAfterPossibleDispatch(timeoutDetails.responseMessage, "timed_out"));
|
|
3264
|
-
}, timeoutMs);
|
|
3265
|
-
timeout.unref?.();
|
|
3403
|
+
const snapshot = () => ({
|
|
3404
|
+
totalMs: toMs(now() - startedAt),
|
|
3405
|
+
spans: spans.slice()
|
|
3266
3406
|
});
|
|
3267
|
-
try {
|
|
3268
|
-
params.signal.addEventListener("abort", abortFromRun, { once: true });
|
|
3269
|
-
if (params.signal.aborted) abortFromRun();
|
|
3270
|
-
const response = await Promise.race([
|
|
3271
|
-
params.toolBridge.handleToolCall(params.call, {
|
|
3272
|
-
signal: controller.signal,
|
|
3273
|
-
onAgentToolResult: notifyAgentToolResult,
|
|
3274
|
-
toolCallOrdinal: params.toolCallOrdinal,
|
|
3275
|
-
retainExecutionSnapshot: true
|
|
3276
|
-
}),
|
|
3277
|
-
abortPromise,
|
|
3278
|
-
timeoutPromise
|
|
3279
|
-
]);
|
|
3280
|
-
if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response), response.diagnosticTerminalReason ?? "failed");
|
|
3281
|
-
return finalizeTerminal(response);
|
|
3282
|
-
} catch (error) {
|
|
3283
|
-
const terminalReason = params.signal.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : resolveToolExecutionErrorKind(error);
|
|
3284
|
-
const message = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
|
|
3285
|
-
notifyFailedToolResult(message, terminalReason);
|
|
3286
|
-
return finalizeTerminal(createFailedAfterPossibleDispatch(message, terminalReason));
|
|
3287
|
-
} finally {
|
|
3288
|
-
if (timeout) clearTimeout(timeout);
|
|
3289
|
-
params.signal.removeEventListener("abort", abortFromRun);
|
|
3290
|
-
resolveAbort = void 0;
|
|
3291
|
-
if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
|
|
3292
|
-
}
|
|
3293
|
-
}
|
|
3294
|
-
function readDynamicToolResponseText(response) {
|
|
3295
|
-
return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
|
|
3296
|
-
}
|
|
3297
|
-
/** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
|
|
3298
|
-
function toCodexDynamicToolProtocolResponse(response) {
|
|
3299
3407
|
return {
|
|
3300
|
-
|
|
3301
|
-
|
|
3408
|
+
async measure(name, run) {
|
|
3409
|
+
const spanStartedAt = now();
|
|
3410
|
+
try {
|
|
3411
|
+
return await run();
|
|
3412
|
+
} finally {
|
|
3413
|
+
record(name, spanStartedAt);
|
|
3414
|
+
}
|
|
3415
|
+
},
|
|
3416
|
+
measureSync(name, run) {
|
|
3417
|
+
const spanStartedAt = now();
|
|
3418
|
+
try {
|
|
3419
|
+
return run();
|
|
3420
|
+
} finally {
|
|
3421
|
+
record(name, spanStartedAt);
|
|
3422
|
+
}
|
|
3423
|
+
},
|
|
3424
|
+
mark(name) {
|
|
3425
|
+
record(name, now());
|
|
3426
|
+
},
|
|
3427
|
+
logSummary(params) {
|
|
3428
|
+
if (didLog) return;
|
|
3429
|
+
const summary = snapshot();
|
|
3430
|
+
const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
|
|
3431
|
+
if (!shouldWarn && !log.isEnabled?.("trace")) return;
|
|
3432
|
+
didLog = true;
|
|
3433
|
+
const message = formatCodexThreadLifecycleTimingSummary({
|
|
3434
|
+
runId: params.runId,
|
|
3435
|
+
sessionId: params.sessionId,
|
|
3436
|
+
sessionKey: params.sessionKey,
|
|
3437
|
+
action: params.action,
|
|
3438
|
+
summary
|
|
3439
|
+
});
|
|
3440
|
+
const meta = {
|
|
3441
|
+
runId: params.runId,
|
|
3442
|
+
sessionId: params.sessionId,
|
|
3443
|
+
sessionKey: params.sessionKey,
|
|
3444
|
+
action: params.action,
|
|
3445
|
+
threadId: params.threadId,
|
|
3446
|
+
totalMs: summary.totalMs,
|
|
3447
|
+
spans: summary.spans
|
|
3448
|
+
};
|
|
3449
|
+
if (shouldWarn) log.warn(message, meta);
|
|
3450
|
+
else log.trace(message, meta);
|
|
3451
|
+
}
|
|
3302
3452
|
};
|
|
3303
3453
|
}
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
const
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
...response.asyncStarted === true ? { details: {
|
|
3314
|
-
...progressDetails,
|
|
3315
|
-
async: true,
|
|
3316
|
-
status: "started"
|
|
3317
|
-
} } : {}
|
|
3318
|
-
};
|
|
3454
|
+
//#endregion
|
|
3455
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
|
|
3456
|
+
function resolveCodexThreadAgentDir(params) {
|
|
3457
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
3458
|
+
config: params.params.config,
|
|
3459
|
+
sessionKey: params.params.sessionKey,
|
|
3460
|
+
agentId: params.agentId ?? params.params.agentId
|
|
3461
|
+
}).sessionAgentId;
|
|
3462
|
+
return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
|
|
3319
3463
|
}
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
return response.asyncStarted !== true;
|
|
3327
|
-
}
|
|
3328
|
-
/** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
|
|
3329
|
-
function resolveTerminalDynamicToolBatchAction(state) {
|
|
3330
|
-
if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
|
|
3331
|
-
if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
|
|
3332
|
-
if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
|
|
3333
|
-
return "idle";
|
|
3334
|
-
}
|
|
3335
|
-
/** Returns true for diagnostic events that terminate a dynamic tool call. */
|
|
3336
|
-
function isDynamicToolTerminalDiagnosticEvent(event) {
|
|
3337
|
-
return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
|
|
3338
|
-
}
|
|
3339
|
-
/** Matches terminal diagnostics to a specific dynamic tool call id/name. */
|
|
3340
|
-
function isMatchingDynamicToolTerminalDiagnostic(params) {
|
|
3341
|
-
if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
|
|
3342
|
-
if (params.runId !== void 0) return params.event.runId === params.runId;
|
|
3343
|
-
if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
|
|
3344
|
-
if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
|
|
3345
|
-
return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
|
|
3346
|
-
}
|
|
3347
|
-
/** Checks pending diagnostics for a terminal event matching a tool call. */
|
|
3348
|
-
function hasPendingDynamicToolTerminalDiagnostic(params) {
|
|
3349
|
-
return hasPendingInternalDiagnosticEvent((event) => {
|
|
3350
|
-
if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
|
|
3351
|
-
return isMatchingDynamicToolTerminalDiagnostic({
|
|
3352
|
-
event,
|
|
3353
|
-
call: params.call,
|
|
3354
|
-
runId: params.runId,
|
|
3355
|
-
sessionId: params.sessionId,
|
|
3356
|
-
sessionKey: params.sessionKey
|
|
3357
|
-
});
|
|
3464
|
+
async function prepareCodexThreadLifecyclePreflight(params) {
|
|
3465
|
+
await assertCodexModelBackedReviewerEffectiveConfig({
|
|
3466
|
+
client: params.client,
|
|
3467
|
+
approvalsReviewer: params.appServer.approvalsReviewer,
|
|
3468
|
+
cwd: params.cwd,
|
|
3469
|
+
signal: params.signal
|
|
3358
3470
|
});
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
if (params.call.tool === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
|
|
3364
|
-
if (params.call.tool === "agents_wait") {
|
|
3365
|
-
const requestedMs = readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS;
|
|
3366
|
-
return Math.max(1, Math.min(63e4, Math.floor(requestedMs)));
|
|
3367
|
-
}
|
|
3368
|
-
return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_TOOL_TIMEOUT_MS);
|
|
3369
|
-
}
|
|
3370
|
-
function readComputerToolTimeoutMs(value) {
|
|
3371
|
-
const args = isJsonObject(value) ? value : void 0;
|
|
3372
|
-
const action = typeof args?.action === "string" ? args.action : void 0;
|
|
3373
|
-
const gatewayTimeoutMs = readPositiveFiniteTimeoutMs(args?.timeoutMs) ?? CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS;
|
|
3374
|
-
const gatewayCallCount = action === "screenshot" || action === "wait" ? 3 : 4;
|
|
3375
|
-
return (action === "wait" || action === "hold_key" ? Math.max(0, Number(args?.duration) || 0) * 1e3 : 0) + gatewayCallCount * gatewayTimeoutMs + CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS;
|
|
3376
|
-
}
|
|
3377
|
-
function readDynamicToolCallTimeoutMs(value) {
|
|
3378
|
-
if (!isJsonObject(value)) return;
|
|
3379
|
-
const timeoutMs = readPositiveFiniteTimeoutMs(value.timeoutMs);
|
|
3380
|
-
if (timeoutMs !== void 0) return timeoutMs;
|
|
3381
|
-
const timeoutSecondsMs = readDynamicToolTimeoutSecondsAsMs(value.timeoutSeconds);
|
|
3382
|
-
return timeoutSecondsMs === void 0 ? void 0 : addTimerTimeoutGraceMs(timeoutSecondsMs, CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS);
|
|
3383
|
-
}
|
|
3384
|
-
function readConfiguredDynamicToolTimeoutMs(toolName, config) {
|
|
3385
|
-
if (toolName === "image_generate") {
|
|
3386
|
-
const imageModel = config?.agents?.defaults?.mediaModels?.image;
|
|
3387
|
-
if (!imageModel || typeof imageModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
|
|
3388
|
-
return readPositiveFiniteTimeoutMs(imageModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
|
|
3389
|
-
}
|
|
3390
|
-
if (toolName === "view_image") {
|
|
3391
|
-
const candidates = (config?.tools?.media?.models ?? []).filter((entry) => !entry.capabilities || entry.capabilities.includes("image"));
|
|
3392
|
-
const capabilityTimeoutMs = readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds);
|
|
3393
|
-
return Math.max(capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS, ...candidates.map((entry) => readTimeoutSecondsAsMs(entry.timeoutSeconds) ?? capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS));
|
|
3394
|
-
}
|
|
3395
|
-
if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
|
|
3396
|
-
}
|
|
3397
|
-
function readTimeoutSecondsAsMs(value) {
|
|
3398
|
-
const seconds = readPositiveFiniteTimeoutMs(value);
|
|
3399
|
-
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
3400
|
-
}
|
|
3401
|
-
function readDynamicToolTimeoutSecondsAsMs(value) {
|
|
3402
|
-
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) return;
|
|
3403
|
-
return value * 1e3;
|
|
3404
|
-
}
|
|
3405
|
-
function readPositiveFiniteTimeoutMs(value) {
|
|
3406
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
3407
|
-
}
|
|
3408
|
-
function clampDynamicToolTimeoutMs(timeoutMs) {
|
|
3409
|
-
return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
|
|
3410
|
-
}
|
|
3411
|
-
//#endregion
|
|
3412
|
-
//#region extensions/codex/src/app-server/native-hook-relay-state.ts
|
|
3413
|
-
const pending = /* @__PURE__ */ new Set();
|
|
3414
|
-
/** Owns delayed hook-relay cleanup across runtime scheduling and test teardown. */
|
|
3415
|
-
const nativeHookRelayUnregisterQueue = {
|
|
3416
|
-
add(entry) {
|
|
3417
|
-
pending.add(entry);
|
|
3418
|
-
},
|
|
3419
|
-
delete(entry) {
|
|
3420
|
-
return pending.delete(entry);
|
|
3421
|
-
},
|
|
3422
|
-
flush() {
|
|
3423
|
-
while (pending.size > 0) {
|
|
3424
|
-
const entry = pending.values().next().value;
|
|
3425
|
-
if (!entry) return;
|
|
3426
|
-
clearTimeout(entry.timeout);
|
|
3427
|
-
entry.unregister();
|
|
3428
|
-
}
|
|
3429
|
-
},
|
|
3430
|
-
clear() {
|
|
3431
|
-
for (const entry of pending) clearTimeout(entry.timeout);
|
|
3432
|
-
pending.clear();
|
|
3433
|
-
}
|
|
3434
|
-
};
|
|
3435
|
-
//#endregion
|
|
3436
|
-
//#region extensions/codex/src/app-server/native-hook-relay.ts
|
|
3437
|
-
/**
|
|
3438
|
-
* Bridges Codex native hook callbacks into OpenClaw's native hook relay so
|
|
3439
|
-
* app-server tool events can still run OpenClaw policy and diagnostics.
|
|
3440
|
-
*/
|
|
3441
|
-
/** Codex hook events that can be registered through OpenClaw's native relay. */
|
|
3442
|
-
const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
|
|
3443
|
-
"pre_tool_use",
|
|
3444
|
-
"post_tool_use",
|
|
3445
|
-
"permission_request",
|
|
3446
|
-
"before_agent_finalize"
|
|
3447
|
-
];
|
|
3448
|
-
const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
|
|
3449
|
-
const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
|
|
3450
|
-
/** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
|
|
3451
|
-
const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 6e4;
|
|
3452
|
-
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
|
|
3453
|
-
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
|
|
3454
|
-
const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
|
|
3455
|
-
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
|
|
3456
|
-
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
|
|
3457
|
-
const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
|
|
3458
|
-
const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
|
|
3459
|
-
const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
|
|
3460
|
-
exec: [
|
|
3461
|
-
"Bash",
|
|
3462
|
-
"exec",
|
|
3463
|
-
"exec_command"
|
|
3464
|
-
],
|
|
3465
|
-
apply_patch: [
|
|
3466
|
-
"apply_patch",
|
|
3467
|
-
"Write",
|
|
3468
|
-
"Edit"
|
|
3469
|
-
],
|
|
3470
|
-
spawn_agent: ["spawn_agent", "Agent"]
|
|
3471
|
-
};
|
|
3472
|
-
/** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
|
|
3473
|
-
async function assertCodexNativeHookRelayAllowed(client, signal) {
|
|
3474
|
-
let attestation = nativeHookPolicyByClient.get(client);
|
|
3475
|
-
if (!attestation) {
|
|
3476
|
-
attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
|
|
3477
|
-
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
|
|
3478
|
-
const requirements = response.requirements;
|
|
3479
|
-
if (requirements === null) return;
|
|
3480
|
-
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
|
|
3481
|
-
const managedOnly = requirements.allowManagedHooksOnly;
|
|
3482
|
-
if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
|
|
3483
|
-
if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
|
|
3484
|
-
});
|
|
3485
|
-
nativeHookPolicyByClient.set(client, attestation);
|
|
3486
|
-
attestation.catch(() => {
|
|
3487
|
-
if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
|
|
3488
|
-
});
|
|
3489
|
-
}
|
|
3490
|
-
await attestation;
|
|
3491
|
-
}
|
|
3492
|
-
/** Defers relay unregister so late native hook subprocesses can still resolve. */
|
|
3493
|
-
function scheduleCodexNativeHookRelayUnregister(params) {
|
|
3494
|
-
let pending;
|
|
3495
|
-
const unregister = () => {
|
|
3496
|
-
if (!pending) return;
|
|
3497
|
-
const current = pending;
|
|
3498
|
-
pending = void 0;
|
|
3499
|
-
if (!nativeHookRelayUnregisterQueue.delete(current)) return;
|
|
3500
|
-
params.relay.unregister();
|
|
3501
|
-
};
|
|
3502
|
-
const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
|
|
3503
|
-
pending = {
|
|
3504
|
-
timeout,
|
|
3505
|
-
unregister
|
|
3506
|
-
};
|
|
3507
|
-
nativeHookRelayUnregisterQueue.add(pending);
|
|
3508
|
-
timeout.unref();
|
|
3509
|
-
}
|
|
3510
|
-
/** Computes the delayed unregister window from Codex's hook timeout. */
|
|
3511
|
-
function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
|
|
3512
|
-
const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
|
|
3513
|
-
return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
|
|
3514
|
-
}
|
|
3515
|
-
/** Records a native pre-tool failure that Codex does not project as a tool item. */
|
|
3516
|
-
function emitCodexNativePreToolUseFailureDiagnostic(params) {
|
|
3517
|
-
emitTrustedDiagnosticEvent({
|
|
3518
|
-
type: "tool.execution.error",
|
|
3519
|
-
...params.agentId ? { agentId: params.agentId } : {},
|
|
3520
|
-
sessionId: params.sessionId,
|
|
3521
|
-
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
3522
|
-
runId: params.runId,
|
|
3523
|
-
toolName: params.failure.toolName,
|
|
3524
|
-
toolCallId: params.failure.toolCallId,
|
|
3525
|
-
durationMs: params.failure.durationMs,
|
|
3526
|
-
errorCategory: "before_tool_call",
|
|
3527
|
-
terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
|
|
3528
|
-
...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
|
|
3471
|
+
if (params.nativeHookRelayRequired) await assertCodexNativeHookRelayAllowed(params.client, params.signal);
|
|
3472
|
+
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
3473
|
+
...params.timing,
|
|
3474
|
+
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
3529
3475
|
});
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
...params.agentId ? { agentId: params.agentId } : {},
|
|
3553
|
-
sessionId: params.sessionId,
|
|
3554
|
-
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
3555
|
-
...params.config ? { config: params.config } : {},
|
|
3556
|
-
runId: params.runId,
|
|
3557
|
-
...params.channelId ? { channelId: params.channelId } : {},
|
|
3558
|
-
...params.requester ? { requester: params.requester } : {},
|
|
3559
|
-
...params.approvalContext ? { approvalContext: params.approvalContext } : {},
|
|
3560
|
-
allowedEvents: params.events,
|
|
3561
|
-
preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
|
|
3562
|
-
ttlMs: resolveCodexNativeHookRelayTtlMs({
|
|
3563
|
-
explicitTtlMs: params.options?.ttlMs,
|
|
3564
|
-
attemptTimeoutMs: params.attemptTimeoutMs,
|
|
3565
|
-
startupTimeoutMs: params.startupTimeoutMs,
|
|
3566
|
-
turnStartTimeoutMs: params.turnStartTimeoutMs
|
|
3567
|
-
}),
|
|
3568
|
-
signal: params.signal,
|
|
3569
|
-
runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
|
|
3570
|
-
assertActive: params.hostCapabilities.assertActive,
|
|
3571
|
-
retention: {
|
|
3572
|
-
readClaim: readCodexNativeChildThreadId,
|
|
3573
|
-
shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
|
|
3574
|
-
allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
|
|
3575
|
-
awaitForegroundAdmission: (childThreadId) => {
|
|
3576
|
-
if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
|
|
3577
|
-
const existingClaim = directChildClaims.get(childThreadId);
|
|
3578
|
-
if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
|
|
3579
|
-
const existingPending = pendingDirectChildAdmissions.get(childThreadId);
|
|
3580
|
-
if (existingPending) return existingPending.promise.then((claim) => assertClaim(childThreadId, claim));
|
|
3581
|
-
if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
|
|
3582
|
-
const { promise, resolve, reject } = createDeferred();
|
|
3583
|
-
pendingDirectChildAdmissions.set(childThreadId, {
|
|
3584
|
-
promise,
|
|
3585
|
-
resolve,
|
|
3586
|
-
reject
|
|
3587
|
-
});
|
|
3588
|
-
return promise.then((claim) => assertClaim(childThreadId, claim));
|
|
3589
|
-
},
|
|
3590
|
-
onDispose: () => {
|
|
3591
|
-
foregroundClosed = true;
|
|
3592
|
-
rejectPendingAdmissions("native hook relay registration closed");
|
|
3593
|
-
}
|
|
3594
|
-
},
|
|
3595
|
-
onPreToolUseFailure: params.onPreToolUseFailure,
|
|
3596
|
-
command: {
|
|
3597
|
-
nice: 10,
|
|
3598
|
-
timeoutMs: params.options?.gatewayTimeoutMs
|
|
3599
|
-
}
|
|
3476
|
+
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
3477
|
+
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
3478
|
+
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
3479
|
+
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
3480
|
+
config: params.params.config,
|
|
3481
|
+
disableTools: params.params.disableTools,
|
|
3482
|
+
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
3483
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3484
|
+
webSearchAllowed: params.webSearchAllowed
|
|
3485
|
+
})).threadConfig);
|
|
3486
|
+
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
3487
|
+
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
3488
|
+
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRun({
|
|
3489
|
+
run: params.params,
|
|
3490
|
+
cwd: params.cwd,
|
|
3491
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
3492
|
+
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
3493
|
+
warn: (message) => embeddedAgentLog.warn(message),
|
|
3494
|
+
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
3495
|
+
serverName,
|
|
3496
|
+
error: formatErrorMessage(error)
|
|
3497
|
+
})
|
|
3600
3498
|
});
|
|
3601
|
-
const
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3499
|
+
const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
|
|
3500
|
+
client: params.client,
|
|
3501
|
+
codexHome: params.appServer.start.env?.CODEX_HOME,
|
|
3502
|
+
cwd: params.cwd,
|
|
3503
|
+
home: params.appServer.start.env?.HOME,
|
|
3504
|
+
signal: params.signal,
|
|
3505
|
+
userProfile: params.appServer.start.env?.USERPROFILE
|
|
3506
|
+
}));
|
|
3507
|
+
const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
|
|
3508
|
+
version: 1,
|
|
3509
|
+
disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
|
|
3510
|
+
}) : void 0;
|
|
3511
|
+
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
3512
|
+
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
3513
|
+
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
3514
|
+
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
3515
|
+
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
3516
|
+
const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
|
|
3517
|
+
const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
|
|
3518
|
+
const allowConfiguredManagedHooks = params.params.pluginHarnessToolPolicyRestricted === true && !ringZeroActive && !messageOnlySourceReply && params.params.scheduledRuntimeAuthority === void 0;
|
|
3519
|
+
const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
|
|
3520
|
+
if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
|
|
3521
|
+
const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
3522
|
+
if (restrictedToolSurface || imageGenerationDenied) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
|
|
3523
|
+
restrictedToolSurface,
|
|
3524
|
+
additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0,
|
|
3525
|
+
allowedManagedRequirementsFingerprint: readScheduledCodexAppManagedRequirementsFingerprint(params.params.scheduledRuntimeAuthority),
|
|
3526
|
+
allowConfiguredManagedHooks
|
|
3527
|
+
}, params.signal));
|
|
3528
|
+
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
3529
|
+
version: 1,
|
|
3530
|
+
baseInstructions: "",
|
|
3531
|
+
config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
|
|
3532
|
+
}) : void 0;
|
|
3606
3533
|
return {
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
const pending = pendingDirectChildAdmissions.get(threadId);
|
|
3627
|
-
pendingDirectChildAdmissions.delete(threadId);
|
|
3628
|
-
pending?.resolve(claim);
|
|
3629
|
-
let released = false;
|
|
3630
|
-
return () => {
|
|
3631
|
-
if (released) return;
|
|
3632
|
-
released = true;
|
|
3633
|
-
if (directChildClaims.get(threadId) !== claim) return;
|
|
3634
|
-
directChildClaims.delete(threadId);
|
|
3635
|
-
if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
|
|
3636
|
-
};
|
|
3637
|
-
}
|
|
3534
|
+
contextEngineBinding,
|
|
3535
|
+
dynamicToolsContainDeferred,
|
|
3536
|
+
dynamicToolsFingerprint,
|
|
3537
|
+
environmentSelectionFingerprint,
|
|
3538
|
+
hostSystemAgentActive,
|
|
3539
|
+
legacyDynamicToolsFingerprint,
|
|
3540
|
+
legacyUserMcpServersFingerprint,
|
|
3541
|
+
lifecycleTiming,
|
|
3542
|
+
nativeSkillIsolation,
|
|
3543
|
+
nativeSkillIsolationFingerprint,
|
|
3544
|
+
networkProxyConfigFingerprint,
|
|
3545
|
+
ringZeroActive,
|
|
3546
|
+
ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
|
|
3547
|
+
ringZeroConfigFingerprint,
|
|
3548
|
+
restrictedToolSurface,
|
|
3549
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3550
|
+
userMcpServersConfigPatch,
|
|
3551
|
+
userMcpServersFingerprint,
|
|
3552
|
+
webSearchThreadConfigFingerprint
|
|
3638
3553
|
};
|
|
3639
3554
|
}
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
if (params.configuredEvents?.length) return params.configuredEvents;
|
|
3647
|
-
return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
|
|
3648
|
-
}
|
|
3649
|
-
/** Derives the native hook relay TTL from the turn budget unless explicitly configured. */
|
|
3650
|
-
function resolveCodexNativeHookRelayTtlMs(params) {
|
|
3651
|
-
if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
|
|
3652
|
-
const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
|
|
3653
|
-
return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
|
|
3654
|
-
}
|
|
3655
|
-
/** Builds a stable relay id scoped to the agent and session identity. */
|
|
3656
|
-
function buildCodexNativeHookRelayId(params) {
|
|
3657
|
-
const hash = createHash("sha256");
|
|
3658
|
-
hash.update("openclaw:codex:native-hook-relay:v1");
|
|
3659
|
-
hash.update("\0");
|
|
3660
|
-
hash.update(params.agentId?.trim() || "");
|
|
3661
|
-
hash.update("\0");
|
|
3662
|
-
hash.update(params.sessionKey?.trim() || params.sessionId);
|
|
3663
|
-
return `codex-${hash.digest("hex").slice(0, 40)}`;
|
|
3664
|
-
}
|
|
3665
|
-
const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
|
|
3666
|
-
pre_tool_use: "PreToolUse",
|
|
3667
|
-
post_tool_use: "PostToolUse",
|
|
3668
|
-
permission_request: "PermissionRequest",
|
|
3669
|
-
before_agent_finalize: "Stop"
|
|
3670
|
-
};
|
|
3671
|
-
const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
|
|
3672
|
-
pre_tool_use: "pre_tool_use",
|
|
3673
|
-
post_tool_use: "post_tool_use",
|
|
3674
|
-
permission_request: "permission_request",
|
|
3675
|
-
before_agent_finalize: "stop"
|
|
3676
|
-
};
|
|
3677
|
-
const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
|
|
3678
|
-
/** Builds the Codex config overlay that installs trusted command hooks for relay events. */
|
|
3679
|
-
function buildCodexNativeHookRelayConfig(params) {
|
|
3680
|
-
const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
|
|
3681
|
-
const selectedEvents = new Set(events);
|
|
3682
|
-
const config = { "features.hooks": true };
|
|
3683
|
-
const hookState = {};
|
|
3684
|
-
for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
|
|
3685
|
-
const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
|
|
3686
|
-
const selected = selectedEvents.has(event);
|
|
3687
|
-
const shouldRelay = params.relay.shouldRelayEvent(event);
|
|
3688
|
-
if (!selected || !shouldRelay) {
|
|
3689
|
-
if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
|
|
3690
|
-
if (params.clearOmittedEvents) for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = { enabled: false };
|
|
3691
|
-
continue;
|
|
3692
|
-
}
|
|
3693
|
-
const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
|
|
3694
|
-
const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
|
|
3695
|
-
const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
|
|
3696
|
-
config[`hooks.${codexEvent}`] = [{
|
|
3697
|
-
...matcher ? { matcher } : {},
|
|
3698
|
-
hooks: [{
|
|
3699
|
-
type: "command",
|
|
3700
|
-
command,
|
|
3701
|
-
timeout,
|
|
3702
|
-
async: false,
|
|
3703
|
-
statusMessage: "OpenClaw native hook relay"
|
|
3704
|
-
}]
|
|
3705
|
-
}];
|
|
3706
|
-
const state = {
|
|
3707
|
-
enabled: true,
|
|
3708
|
-
trusted_hash: codexCommandHookTrustedHash({
|
|
3709
|
-
event,
|
|
3710
|
-
command,
|
|
3711
|
-
matcher,
|
|
3712
|
-
timeout,
|
|
3713
|
-
statusMessage: "OpenClaw native hook relay"
|
|
3714
|
-
})
|
|
3715
|
-
};
|
|
3716
|
-
for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
|
|
3717
|
-
}
|
|
3718
|
-
config["hooks.state"] = hookState;
|
|
3719
|
-
return config;
|
|
3555
|
+
//#endregion
|
|
3556
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-result.ts
|
|
3557
|
+
function resolveCodexThreadRolloutPath(thread) {
|
|
3558
|
+
const rolloutPath = thread.path?.trim();
|
|
3559
|
+
if (!rolloutPath || !path.isAbsolute(rolloutPath) || path.extname(rolloutPath) !== ".jsonl" || !path.basename(rolloutPath).includes(thread.id)) return;
|
|
3560
|
+
return rolloutPath;
|
|
3720
3561
|
}
|
|
3721
|
-
/**
|
|
3722
|
-
function
|
|
3562
|
+
/** Materializes the public lifecycle result after a fresh thread is durably committed. */
|
|
3563
|
+
function buildStartedCodexThreadBinding(input) {
|
|
3564
|
+
const { context, params, response, startParams } = input;
|
|
3723
3565
|
return {
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
command: params.command,
|
|
3762
|
-
statusMessage: params.statusMessage,
|
|
3763
|
-
timeout: params.timeout,
|
|
3764
|
-
type: "command"
|
|
3765
|
-
}]
|
|
3566
|
+
threadId: response.thread.id,
|
|
3567
|
+
...input.clientId ? { clientId: input.clientId } : {},
|
|
3568
|
+
cwd: params.cwd,
|
|
3569
|
+
...input.rolloutPath ? { rolloutPath: input.rolloutPath } : {},
|
|
3570
|
+
authProfileId: params.params.authProfileId,
|
|
3571
|
+
agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
|
|
3572
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
3573
|
+
modelProvider: response.modelProvider ?? input.startModelProvider ?? input.modelProvider,
|
|
3574
|
+
dynamicToolsFingerprint: context.dynamicToolsFingerprint,
|
|
3575
|
+
dynamicToolsContainDeferred: context.dynamicToolsContainDeferred,
|
|
3576
|
+
nativeSkillIsolationFingerprint: context.nativeSkillIsolationFingerprint,
|
|
3577
|
+
userMcpServersFingerprint: context.userMcpServersFingerprint,
|
|
3578
|
+
mcpServersFingerprint: input.nextMcpServersFingerprint,
|
|
3579
|
+
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
3580
|
+
ringZeroConfigFingerprint: context.ringZeroConfigFingerprint,
|
|
3581
|
+
ringZeroClientInstanceId: context.ringZeroClientInstanceId,
|
|
3582
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
3583
|
+
networkProxyConfigFingerprint: context.networkProxyConfigFingerprint,
|
|
3584
|
+
nativeHookRelayGeneration: input.finalConfigPatch.nativeHookRelayGeneration,
|
|
3585
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
3586
|
+
pluginAppsFingerprint: input.pluginThreadConfig?.fingerprint,
|
|
3587
|
+
pluginAppsInputFingerprint: input.pluginThreadConfig?.inputFingerprint,
|
|
3588
|
+
pluginAppPolicyContext: input.pluginThreadConfig?.policyContext,
|
|
3589
|
+
contextEngine: context.contextEngineBinding,
|
|
3590
|
+
environmentSelectionFingerprint: context.environmentSelectionFingerprint,
|
|
3591
|
+
...startParams.ephemeral ? { liveThreadEphemeralPolicy: startParams.developerInstructions } : {},
|
|
3592
|
+
...!context.preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
|
|
3593
|
+
...startParams,
|
|
3594
|
+
model: response.model ?? startParams.model ?? null,
|
|
3595
|
+
requestedModel: startParams.model ?? null,
|
|
3596
|
+
modelProvider: input.bindingModelProvider ?? null,
|
|
3597
|
+
requestedModelProvider: startParams.modelProvider ?? input.bindingModelProvider ?? null
|
|
3598
|
+
}, params.params.authProfileId, context.dynamicToolsFingerprint) } : {},
|
|
3599
|
+
lifecycle: {
|
|
3600
|
+
action: "started",
|
|
3601
|
+
...context.rotatedContextEngineBinding ? { rotatedContextEngineBinding: true } : {}
|
|
3602
|
+
}
|
|
3766
3603
|
};
|
|
3767
|
-
return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
|
|
3768
|
-
}
|
|
3769
|
-
function sortJsonValue(value) {
|
|
3770
|
-
if (!value || typeof value !== "object") return value;
|
|
3771
|
-
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
3772
|
-
const sorted = {};
|
|
3773
|
-
for (const [key, entry] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) sorted[key] = sortJsonValue(entry);
|
|
3774
|
-
return sorted;
|
|
3775
|
-
}
|
|
3776
|
-
//#endregion
|
|
3777
|
-
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
3778
|
-
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
3779
|
-
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
3780
|
-
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
3781
|
-
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
3782
3604
|
}
|
|
3783
3605
|
//#endregion
|
|
3784
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-
|
|
3785
|
-
|
|
3786
|
-
const
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
3797
|
-
const log = options.log ?? embeddedAgentLog;
|
|
3798
|
-
if (!options.enabled && log.isEnabled?.("trace") !== true) return {
|
|
3799
|
-
async measure(_name, run) {
|
|
3800
|
-
return await run();
|
|
3801
|
-
},
|
|
3802
|
-
measureSync(_name, run) {
|
|
3803
|
-
return run();
|
|
3804
|
-
},
|
|
3805
|
-
mark() {},
|
|
3806
|
-
logSummary() {}
|
|
3606
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-io.ts
|
|
3607
|
+
async function resumeExistingCodexThread(params, context) {
|
|
3608
|
+
const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
|
|
3609
|
+
let resumeReservation;
|
|
3610
|
+
let resumeResponseAccepted = false;
|
|
3611
|
+
let ordinaryAppConfigChanged = false;
|
|
3612
|
+
let policyOutcome = "not-written";
|
|
3613
|
+
const assertHandoffCurrent = () => {
|
|
3614
|
+
params.params.hostCapabilities.assertActive();
|
|
3615
|
+
throwIfAborted();
|
|
3616
|
+
context.assertResumeOwnership();
|
|
3617
|
+
context.assertResumeConfiguration();
|
|
3807
3618
|
};
|
|
3808
|
-
const
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3619
|
+
const abandonClient = params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client));
|
|
3620
|
+
try {
|
|
3621
|
+
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
3622
|
+
const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
|
|
3623
|
+
action: "resume",
|
|
3624
|
+
binding: resumeBinding
|
|
3625
|
+
}) ?? {
|
|
3626
|
+
configPatch: params.finalConfigPatch,
|
|
3627
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
3628
|
+
};
|
|
3629
|
+
const pluginThreadConfig = context.prebuiltPluginThreadConfig ?? (params.pluginThreadConfig?.requiresCurrentPolicyCheck ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0);
|
|
3630
|
+
const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
|
|
3631
|
+
const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
|
|
3632
|
+
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
3633
|
+
threadId: resumeBinding.threadId,
|
|
3634
|
+
cwd: params.cwd,
|
|
3635
|
+
authProfileId,
|
|
3636
|
+
model: startModelSelection.model,
|
|
3637
|
+
modelProvider: startModelProvider,
|
|
3638
|
+
preserveNativeModel: resumeBinding.preserveNativeModel === true,
|
|
3639
|
+
appServer: params.appServer,
|
|
3640
|
+
dynamicTools: params.dynamicTools,
|
|
3641
|
+
developerInstructions: params.developerInstructions,
|
|
3642
|
+
config: resumeConfig,
|
|
3643
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
3644
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3645
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
3646
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
3647
|
+
hostSystemAgentActive,
|
|
3648
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3649
|
+
shellEnvironment: params.shellEnvironment,
|
|
3650
|
+
disableLoginShell: params.disableLoginShell
|
|
3651
|
+
}));
|
|
3652
|
+
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
3653
|
+
throwIfAborted();
|
|
3654
|
+
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
3655
|
+
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
3656
|
+
client: params.client,
|
|
3657
|
+
abandonClient,
|
|
3658
|
+
request: resumeParams,
|
|
3659
|
+
signal: params.signal,
|
|
3660
|
+
assertCurrent: context.assertResumeOwnership
|
|
3661
|
+
}));
|
|
3662
|
+
resumeResponseAccepted = true;
|
|
3663
|
+
assertCodexThreadAcceptsDirectInput(response.thread);
|
|
3664
|
+
context.assertResumeConfiguration();
|
|
3665
|
+
const loadedPluginThreadConfig = await context.buildLoadedPluginThreadConfig?.(resumeBinding);
|
|
3666
|
+
if (loadedPluginThreadConfig && loadedPluginThreadConfig.fingerprint !== (pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint)) {
|
|
3667
|
+
ordinaryAppConfigChanged = resumeBinding.connectionScope !== "supervision" && !resumeBinding.pendingResumeConfiguration;
|
|
3668
|
+
throw new Error("Codex thread app policy changed; a fresh thread configuration is required");
|
|
3669
|
+
}
|
|
3670
|
+
const provisionalAppIds = loadedPluginThreadConfig?.provisionalAppIds ?? pluginThreadConfig?.provisionalAppIds ?? [];
|
|
3671
|
+
await attestCodexThreadToolSurface({
|
|
3672
|
+
client: params.client,
|
|
3673
|
+
threadId: response.thread.id,
|
|
3674
|
+
appIds: provisionalAppIds,
|
|
3675
|
+
signal: params.signal,
|
|
3676
|
+
threadConfig: resumeParams.config,
|
|
3677
|
+
restrictedToolSurface,
|
|
3678
|
+
lifecycleTiming,
|
|
3679
|
+
assertCurrent: assertHandoffCurrent
|
|
3819
3680
|
});
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3681
|
+
throwIfAborted();
|
|
3682
|
+
await refreshCodexThreadPolicy({
|
|
3683
|
+
client: params.client,
|
|
3684
|
+
threadId: resumeBinding.threadId,
|
|
3685
|
+
developerInstructions: resumeParams.developerInstructions,
|
|
3686
|
+
timeoutMs: params.appServer.requestTimeoutMs,
|
|
3687
|
+
signal: params.signal,
|
|
3688
|
+
assertCurrent: assertHandoffCurrent
|
|
3689
|
+
});
|
|
3690
|
+
policyOutcome = "acknowledged";
|
|
3691
|
+
assertHandoffCurrent();
|
|
3692
|
+
const resumePatch = {
|
|
3693
|
+
clientId: resolveCodexAppServerClientInstanceId(params.client),
|
|
3694
|
+
pendingResumeConfiguration: void 0,
|
|
3695
|
+
cwd: params.cwd,
|
|
3696
|
+
rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
|
|
3697
|
+
authProfileId,
|
|
3698
|
+
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
3699
|
+
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
3700
|
+
modelProvider: normalizeBindingModelProvider(authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
3701
|
+
dynamicToolsFingerprint,
|
|
3702
|
+
dynamicToolsContainDeferred,
|
|
3703
|
+
webSearchThreadConfigFingerprint,
|
|
3704
|
+
nativeSkillIsolationFingerprint,
|
|
3705
|
+
userMcpServersFingerprint,
|
|
3706
|
+
mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint,
|
|
3707
|
+
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
3708
|
+
ringZeroConfigFingerprint,
|
|
3709
|
+
ringZeroClientInstanceId,
|
|
3710
|
+
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
3711
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
3712
|
+
networkProxyConfigFingerprint,
|
|
3713
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
3714
|
+
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
3715
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
|
|
3716
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
|
|
3717
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
|
|
3718
|
+
contextEngine: contextEngineBinding,
|
|
3719
|
+
environmentSelectionFingerprint
|
|
3720
|
+
};
|
|
3721
|
+
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
3722
|
+
kind: "patch",
|
|
3723
|
+
threadId: resumeBinding.threadId,
|
|
3724
|
+
patch: resumePatch
|
|
3725
|
+
}, assertHandoffCurrent))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
3726
|
+
assertHandoffCurrent();
|
|
3727
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
3728
|
+
sessionId: params.params.sessionId,
|
|
3729
|
+
sessionKey: params.params.sessionKey,
|
|
3730
|
+
threadId: response.thread.id,
|
|
3731
|
+
engineId: contextEngineBinding.engineId,
|
|
3732
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
3733
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
3734
|
+
action: "resumed"
|
|
3735
|
+
});
|
|
3736
|
+
lifecycleTiming.mark("thread-ready");
|
|
3737
|
+
lifecycleTiming.logSummary({
|
|
3738
|
+
runId: params.params.runId,
|
|
3739
|
+
sessionId: params.params.sessionId,
|
|
3740
|
+
sessionKey: params.params.sessionKey,
|
|
3741
|
+
threadId: response.thread.id,
|
|
3742
|
+
action: "resumed"
|
|
3743
|
+
});
|
|
3744
|
+
const activeTurnIds = readActiveCodexTurnIdsFromResume(response);
|
|
3745
|
+
return {
|
|
3746
|
+
...resumeBinding,
|
|
3747
|
+
threadId: response.thread.id,
|
|
3748
|
+
...resumePatch,
|
|
3749
|
+
liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
|
|
3750
|
+
...resumeParams,
|
|
3751
|
+
model: resumeBinding.preserveNativeModel === true ? null : response.model ?? resumeParams.model ?? null,
|
|
3752
|
+
requestedModel: resumeBinding.preserveNativeModel === true ? null : resumeParams.model ?? null,
|
|
3753
|
+
modelProvider: resumeBinding.preserveNativeModel === true ? null : resumePatch.modelProvider ?? null,
|
|
3754
|
+
requestedModelProvider: resumeBinding.preserveNativeModel === true ? null : resumeParams.modelProvider ?? resumePatch.modelProvider ?? null
|
|
3755
|
+
}, authProfileId, dynamicToolsFingerprint),
|
|
3756
|
+
lifecycle: {
|
|
3757
|
+
action: "resumed",
|
|
3758
|
+
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
3832
3759
|
}
|
|
3833
|
-
}
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3760
|
+
};
|
|
3761
|
+
} catch (error) {
|
|
3762
|
+
resumeReservation?.release();
|
|
3763
|
+
if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
|
|
3764
|
+
if (resumeResponseAccepted) {
|
|
3765
|
+
const handoffError = error instanceof CodexThreadPolicyHandoffError || error instanceof CodexAppServerUnsafeSubscriptionError ? error : new CodexThreadPolicyHandoffError(policyOutcome, error);
|
|
3766
|
+
const subscriptionReleased = await unsubscribeCodexThreadBestEffort(params.client, {
|
|
3767
|
+
threadId: resumeBinding.threadId,
|
|
3768
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,
|
|
3769
|
+
assertCurrent: context.assertResumeOwnership
|
|
3770
|
+
}).catch(() => false);
|
|
3771
|
+
if (!subscriptionReleased || handoffError instanceof CodexThreadPolicyHandoffError && handoffError.outcome === "unknown") {
|
|
3772
|
+
if (resumeBinding.connectionScope === "supervision") await retireUnsafeCodexTurnClientBestEffort(params.client, "session policy handoff");
|
|
3773
|
+
else try {
|
|
3774
|
+
await abandonClient();
|
|
3775
|
+
} catch (cause) {
|
|
3776
|
+
throw new CodexThreadPolicyHandoffError(handoffError instanceof CodexThreadPolicyHandoffError ? handoffError.outcome : policyOutcome, new AggregateError([handoffError, cause], "Codex thread/resume client could not be retired"));
|
|
3777
|
+
}
|
|
3840
3778
|
}
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3779
|
+
if (!ordinaryAppConfigChanged || !subscriptionReleased) throw handoffError;
|
|
3780
|
+
assertHandoffCurrent();
|
|
3781
|
+
}
|
|
3782
|
+
if (resumeBinding.pendingResumeConfiguration || resumeBinding.connectionScope === "supervision" || params.signal?.aborted) throw error;
|
|
3783
|
+
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
3784
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
async function startFreshCodexThread(params, context) {
|
|
3788
|
+
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
3789
|
+
const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding, replacementPredecessor } = context;
|
|
3790
|
+
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
3791
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
3792
|
+
configPatch: params.finalConfigPatch,
|
|
3793
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
3794
|
+
};
|
|
3795
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch), nativeSkillIsolation));
|
|
3796
|
+
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
3797
|
+
cwd: params.cwd,
|
|
3798
|
+
dynamicTools: params.dynamicTools,
|
|
3799
|
+
appServer: params.appServer,
|
|
3800
|
+
developerInstructions: params.developerInstructions,
|
|
3801
|
+
config,
|
|
3802
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
3803
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3804
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
3805
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
3806
|
+
environmentSelection: params.environmentSelection,
|
|
3807
|
+
model: startModelSelection.model,
|
|
3808
|
+
modelProvider: startModelProvider,
|
|
3809
|
+
hostSystemAgentActive,
|
|
3810
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3811
|
+
shellEnvironment: params.shellEnvironment,
|
|
3812
|
+
disableLoginShell: params.disableLoginShell
|
|
3813
|
+
}));
|
|
3814
|
+
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
3815
|
+
const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
|
|
3816
|
+
try {
|
|
3817
|
+
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
3818
|
+
} catch (error) {
|
|
3819
|
+
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
3820
|
+
throw error;
|
|
3869
3821
|
}
|
|
3822
|
+
});
|
|
3823
|
+
const response = assertCodexThreadStartResponse(threadStartResponse);
|
|
3824
|
+
const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
|
|
3825
|
+
const assertCurrent = () => {
|
|
3826
|
+
throwIfAborted();
|
|
3827
|
+
params.params.hostCapabilities.assertActive();
|
|
3870
3828
|
};
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3829
|
+
const rejectUncommittedThread = async (cause) => {
|
|
3830
|
+
if (!await discardUnattestedCodexPluginThread({
|
|
3831
|
+
client: params.client,
|
|
3832
|
+
threadId: response.thread.id,
|
|
3833
|
+
ephemeral: startParams.ephemeral === true
|
|
3834
|
+
})) {
|
|
3835
|
+
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
3836
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex uncommitted thread cleanup failed", { cause });
|
|
3837
|
+
}
|
|
3838
|
+
throw cause;
|
|
3839
|
+
};
|
|
3840
|
+
try {
|
|
3841
|
+
await attestCodexThreadToolSurface({
|
|
3842
|
+
client: params.client,
|
|
3843
|
+
threadId: response.thread.id,
|
|
3844
|
+
appIds: provisionalAppIds ?? [],
|
|
3845
|
+
signal: params.signal,
|
|
3846
|
+
threadConfig: startParams.config,
|
|
3847
|
+
restrictedToolSurface,
|
|
3848
|
+
lifecycleTiming,
|
|
3849
|
+
assertCurrent
|
|
3850
|
+
});
|
|
3851
|
+
assertCurrent();
|
|
3852
|
+
} catch (error) {
|
|
3853
|
+
return await rejectUncommittedThread(error);
|
|
3854
|
+
}
|
|
3855
|
+
const rolloutPath = resolveCodexThreadRolloutPath(response.thread);
|
|
3856
|
+
const modelProvider = resolveCodexAppServerModelProvider({
|
|
3857
|
+
provider: params.params.provider,
|
|
3858
|
+
authProfileId: params.params.authProfileId,
|
|
3859
|
+
authProfileStore: params.params.authProfileStore,
|
|
3860
|
+
agentDir: params.params.agentDir,
|
|
3861
|
+
config: params.params.config
|
|
3880
3862
|
});
|
|
3881
|
-
|
|
3882
|
-
const
|
|
3883
|
-
|
|
3884
|
-
|
|
3863
|
+
const bindingModelProvider = normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider);
|
|
3864
|
+
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
3865
|
+
if (!preserveExistingBinding) {
|
|
3866
|
+
const nextBinding = {
|
|
3867
|
+
threadId: response.thread.id,
|
|
3868
|
+
...clientId ? { clientId } : {},
|
|
3869
|
+
cwd: params.cwd,
|
|
3870
|
+
...rolloutPath ? { rolloutPath } : {},
|
|
3871
|
+
authProfileId: params.params.authProfileId,
|
|
3872
|
+
agentWorkspaceDeveloperInstructions: params.agentWorkspaceDeveloperInstructions,
|
|
3873
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
3874
|
+
modelProvider: bindingModelProvider,
|
|
3875
|
+
dynamicToolsFingerprint,
|
|
3876
|
+
dynamicToolsContainDeferred,
|
|
3877
|
+
webSearchThreadConfigFingerprint,
|
|
3878
|
+
nativeSkillIsolationFingerprint,
|
|
3879
|
+
userMcpServersFingerprint,
|
|
3880
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
3881
|
+
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
3882
|
+
ringZeroConfigFingerprint,
|
|
3883
|
+
ringZeroClientInstanceId,
|
|
3884
|
+
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
3885
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
3886
|
+
networkProxyConfigFingerprint,
|
|
3887
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
3888
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
3889
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
3890
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
3891
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
3892
|
+
contextEngine: contextEngineBinding,
|
|
3893
|
+
environmentSelectionFingerprint
|
|
3894
|
+
};
|
|
3895
|
+
const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
|
|
3896
|
+
let committed;
|
|
3897
|
+
try {
|
|
3898
|
+
await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
|
|
3899
|
+
sourceHomeId: managedSourceHomeId,
|
|
3900
|
+
threadId: response.thread.id,
|
|
3901
|
+
...rolloutPath ? { rolloutPath } : {}
|
|
3902
|
+
}));
|
|
3903
|
+
committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
|
|
3904
|
+
kind: "replace-thread",
|
|
3905
|
+
expectedThreadId: replacementPredecessor.threadId,
|
|
3906
|
+
binding: nextBinding
|
|
3907
|
+
} : {
|
|
3908
|
+
kind: "set",
|
|
3909
|
+
if: { kind: "absent" },
|
|
3910
|
+
binding: nextBinding
|
|
3911
|
+
}, assertCurrent));
|
|
3912
|
+
} catch (error) {
|
|
3913
|
+
return await rejectUncommittedThread(error);
|
|
3914
|
+
}
|
|
3915
|
+
if (!committed) return await rejectUncommittedThread(new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread"));
|
|
3916
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
3917
|
+
sessionId: params.params.sessionId,
|
|
3918
|
+
sessionKey: params.params.sessionKey,
|
|
3919
|
+
threadId: response.thread.id,
|
|
3920
|
+
engineId: contextEngineBinding.engineId,
|
|
3921
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
3922
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
3923
|
+
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
lifecycleTiming.mark("thread-ready");
|
|
3927
|
+
lifecycleTiming.logSummary({
|
|
3928
|
+
runId: params.params.runId,
|
|
3929
|
+
sessionId: params.params.sessionId,
|
|
3930
|
+
sessionKey: params.params.sessionKey,
|
|
3931
|
+
threadId: response.thread.id,
|
|
3932
|
+
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
3885
3933
|
});
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
agentId: params.agentId ?? params.params.agentId,
|
|
3900
|
-
agentDir: params.params.agentDir,
|
|
3901
|
-
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
3902
|
-
toolOverrides: params.params.toolOverrides,
|
|
3903
|
-
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
3904
|
-
serverName,
|
|
3905
|
-
error: formatErrorMessage(error)
|
|
3906
|
-
})
|
|
3934
|
+
return buildStartedCodexThreadBinding({
|
|
3935
|
+
bindingModelProvider,
|
|
3936
|
+
clientId,
|
|
3937
|
+
context,
|
|
3938
|
+
finalConfigPatch,
|
|
3939
|
+
nextMcpServersFingerprint,
|
|
3940
|
+
params,
|
|
3941
|
+
pluginThreadConfig,
|
|
3942
|
+
response,
|
|
3943
|
+
rolloutPath,
|
|
3944
|
+
startModelProvider: requestModelProvider ?? startModelProvider,
|
|
3945
|
+
startParams,
|
|
3946
|
+
modelProvider
|
|
3907
3947
|
});
|
|
3908
|
-
const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
|
|
3909
|
-
client: params.client,
|
|
3910
|
-
codexHome: params.appServer.start.env?.CODEX_HOME,
|
|
3911
|
-
cwd: params.cwd,
|
|
3912
|
-
home: params.appServer.start.env?.HOME,
|
|
3913
|
-
signal: params.signal,
|
|
3914
|
-
userProfile: params.appServer.start.env?.USERPROFILE
|
|
3915
|
-
}));
|
|
3916
|
-
const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
|
|
3917
|
-
version: 1,
|
|
3918
|
-
disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
|
|
3919
|
-
}) : void 0;
|
|
3920
|
-
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
3921
|
-
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
3922
|
-
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
3923
|
-
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
3924
|
-
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
3925
|
-
const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
|
|
3926
|
-
const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
|
|
3927
|
-
const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
|
|
3928
|
-
if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
|
|
3929
|
-
const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
3930
|
-
if (restrictedToolSurface || imageGenerationDenied) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
|
|
3931
|
-
restrictedToolSurface,
|
|
3932
|
-
additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0
|
|
3933
|
-
}, params.signal));
|
|
3934
|
-
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
3935
|
-
version: 1,
|
|
3936
|
-
baseInstructions: "",
|
|
3937
|
-
config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
|
|
3938
|
-
}) : void 0;
|
|
3939
|
-
return {
|
|
3940
|
-
contextEngineBinding,
|
|
3941
|
-
dynamicToolsContainDeferred,
|
|
3942
|
-
dynamicToolsFingerprint,
|
|
3943
|
-
environmentSelectionFingerprint,
|
|
3944
|
-
hostSystemAgentActive,
|
|
3945
|
-
legacyDynamicToolsFingerprint,
|
|
3946
|
-
legacyUserMcpServersFingerprint,
|
|
3947
|
-
lifecycleTiming,
|
|
3948
|
-
nativeSkillIsolation,
|
|
3949
|
-
nativeSkillIsolationFingerprint,
|
|
3950
|
-
networkProxyConfigFingerprint,
|
|
3951
|
-
ringZeroActive,
|
|
3952
|
-
ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
|
|
3953
|
-
ringZeroConfigFingerprint,
|
|
3954
|
-
restrictedToolSurface,
|
|
3955
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3956
|
-
userMcpServersConfigPatch,
|
|
3957
|
-
userMcpServersFingerprint,
|
|
3958
|
-
webSearchThreadConfigFingerprint
|
|
3959
|
-
};
|
|
3960
3948
|
}
|
|
3961
3949
|
//#endregion
|
|
3962
3950
|
//#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
|
|
@@ -3973,66 +3961,150 @@ function throwIfCodexThreadLifecycleAborted(signal) {
|
|
|
3973
3961
|
async function releaseCodexConsumedLiveThread(options) {
|
|
3974
3962
|
if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
|
|
3975
3963
|
threadId: options.threadId,
|
|
3976
|
-
timeoutMs: 5e3
|
|
3964
|
+
timeoutMs: 5e3,
|
|
3965
|
+
assertCurrent: options.assertCurrent
|
|
3977
3966
|
}))) return;
|
|
3967
|
+
return await abandonCodexLiveThreadRelease(options, options.cause);
|
|
3968
|
+
}
|
|
3969
|
+
async function abandonCodexLiveThreadRelease(options, cause) {
|
|
3970
|
+
options.assertCurrent?.();
|
|
3978
3971
|
await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
|
|
3979
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`,
|
|
3972
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, cause !== void 0 ? { cause } : void 0);
|
|
3980
3973
|
}
|
|
3981
|
-
/**
|
|
3974
|
+
/** Releases through the retained owner, preserving its guarded callback and rollback. */
|
|
3982
3975
|
async function releaseCodexRetainedLiveThread(options) {
|
|
3983
|
-
|
|
3976
|
+
try {
|
|
3977
|
+
return await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => releaseCodexAppServerLiveThread(options.client, options.threadId, options.assertCurrent));
|
|
3978
|
+
} catch (error) {
|
|
3979
|
+
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
3980
|
+
return await abandonCodexLiveThreadRelease(options, error);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
/** Release follows the physical owner across connection rotation, never a copied thread id. */
|
|
3984
|
+
async function releaseCodexBoundLiveThread(options) {
|
|
3985
|
+
const changedClient = options.ownerClientId && options.ownerClientId !== options.clientId;
|
|
3986
|
+
const previous = changedClient ? retainSharedCodexAppServerClientByInstanceId(options.ownerClientId) : void 0;
|
|
3987
|
+
if (changedClient && !previous) return false;
|
|
3988
|
+
try {
|
|
3989
|
+
const client = previous?.client ?? options.client;
|
|
3990
|
+
const assertPrevious = previous && options.assertCurrent ? captureCodexAppServerClientLifetime(client, "connection") : void 0;
|
|
3991
|
+
if (isCodexAppServerLiveThreadClaimed(client, options.threadId)) throw new Error(`Codex thread ${options.threadId} is claimed by active work; stop it first.`);
|
|
3992
|
+
return await releaseCodexRetainedLiveThread({
|
|
3993
|
+
...options,
|
|
3994
|
+
client,
|
|
3995
|
+
abandonClient: previous ? void 0 : options.abandonClient,
|
|
3996
|
+
assertCurrent: options.assertCurrent ? () => {
|
|
3997
|
+
options.assertCurrent?.();
|
|
3998
|
+
assertPrevious?.();
|
|
3999
|
+
} : void 0
|
|
4000
|
+
});
|
|
4001
|
+
} finally {
|
|
4002
|
+
previous?.release();
|
|
4003
|
+
}
|
|
3984
4004
|
}
|
|
3985
4005
|
/** Reuses one safely owned, fully matching subscription on its original client. */
|
|
3986
4006
|
async function tryReuseCodexLiveThread(options) {
|
|
3987
|
-
const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation,
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
preserveNativeModel: false,
|
|
4006
|
-
appServer: params.appServer,
|
|
4007
|
-
dynamicTools: params.dynamicTools,
|
|
4008
|
-
developerInstructions: params.developerInstructions,
|
|
4009
|
-
config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
|
|
4010
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
4011
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
4012
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
4013
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
4014
|
-
hostSystemAgentActive,
|
|
4015
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
4016
|
-
shellEnvironment: params.shellEnvironment,
|
|
4017
|
-
disableLoginShell: params.disableLoginShell
|
|
4018
|
-
}));
|
|
4019
|
-
const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
|
|
4020
|
-
...resumeParams,
|
|
4021
|
-
model: binding.model ?? resumeParams.model ?? null,
|
|
4022
|
-
requestedModel: resumeParams.model ?? null,
|
|
4023
|
-
modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
|
|
4024
|
-
requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
|
|
4025
|
-
}, resumeAuthProfileId, dynamicToolsFingerprint);
|
|
4026
|
-
const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint);
|
|
4027
|
-
if (!retainedThread) {
|
|
4028
|
-
const incompatibleOwnership = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
|
|
4029
|
-
if (incompatibleOwnership) await incompatibleOwnership.release(binding.threadId);
|
|
4030
|
-
return { prebuiltFinalConfigPatch };
|
|
4007
|
+
const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
|
|
4008
|
+
const incognito = isIncognitoSessionKey(params.params.sessionKey);
|
|
4009
|
+
if (incognito && (binding.preserveNativeModel || binding.connectionScope === "supervision")) {
|
|
4010
|
+
if (binding.clientId === clientId && binding.clientId && ((await options.buildLoadedPluginThreadConfig(binding))?.fingerprint ?? binding.pluginAppsFingerprint) === binding.pluginAppsFingerprint) {
|
|
4011
|
+
params.buildFinalConfigPatch?.({
|
|
4012
|
+
action: "resume",
|
|
4013
|
+
binding
|
|
4014
|
+
});
|
|
4015
|
+
throwIfAborted();
|
|
4016
|
+
return {
|
|
4017
|
+
kind: "ready",
|
|
4018
|
+
binding: {
|
|
4019
|
+
...binding,
|
|
4020
|
+
lifecycle: { action: "resumed" }
|
|
4021
|
+
}
|
|
4022
|
+
};
|
|
4023
|
+
}
|
|
4024
|
+
return { kind: "rotate" };
|
|
4031
4025
|
}
|
|
4026
|
+
if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive && !incognito) return { kind: "resume" };
|
|
4027
|
+
const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
|
|
4028
|
+
if (!retainedThread) return { kind: "resume" };
|
|
4029
|
+
const assertWarmOwner = () => {
|
|
4030
|
+
throwIfAborted();
|
|
4031
|
+
if (!isCodexAppServerClientRuntimeLive(params.client)) throw params.client.getCloseError() ?? /* @__PURE__ */ new Error("codex app-server client is closed");
|
|
4032
|
+
try {
|
|
4033
|
+
retainedThread.assertCurrent();
|
|
4034
|
+
params.params.hostCapabilities.assertActive();
|
|
4035
|
+
} catch (cause) {
|
|
4036
|
+
throw new AgentHarnessPreflightError("Codex warm thread ownership changed before this turn could run. No turn was sent; reconnect before continuing, or start a new conversation if the original thread was closed.", { cause });
|
|
4037
|
+
}
|
|
4038
|
+
};
|
|
4039
|
+
let ownershipTransferred = false;
|
|
4040
|
+
let preserveSubscription = false;
|
|
4032
4041
|
try {
|
|
4042
|
+
assertWarmOwner();
|
|
4043
|
+
const pluginThreadConfig = await options.buildLoadedPluginThreadConfig(binding);
|
|
4044
|
+
assertWarmOwner();
|
|
4045
|
+
if (pluginThreadConfig && pluginThreadConfig.fingerprint !== binding.pluginAppsFingerprint) return { kind: "rotate" };
|
|
4046
|
+
const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
|
|
4047
|
+
action: "resume",
|
|
4048
|
+
binding
|
|
4049
|
+
}) ?? {
|
|
4050
|
+
configPatch: params.finalConfigPatch,
|
|
4051
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
4052
|
+
};
|
|
4053
|
+
const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0);
|
|
4054
|
+
const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
|
|
4055
|
+
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
|
|
4056
|
+
const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
4057
|
+
threadId: binding.threadId,
|
|
4058
|
+
cwd: params.cwd,
|
|
4059
|
+
authProfileId: resumeAuthProfileId,
|
|
4060
|
+
model: startModelSelection.model,
|
|
4061
|
+
modelProvider: startModelProvider,
|
|
4062
|
+
preserveNativeModel: false,
|
|
4063
|
+
appServer: params.appServer,
|
|
4064
|
+
dynamicTools: params.dynamicTools,
|
|
4065
|
+
developerInstructions: params.developerInstructions,
|
|
4066
|
+
config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
|
|
4067
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
4068
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
4069
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
4070
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
4071
|
+
hostSystemAgentActive,
|
|
4072
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
4073
|
+
shellEnvironment: params.shellEnvironment,
|
|
4074
|
+
disableLoginShell: params.disableLoginShell
|
|
4075
|
+
}));
|
|
4076
|
+
const liveThreadConfigFingerprint = incognito ? retainedThread.configFingerprint : fingerprintCodexThreadConfig({
|
|
4077
|
+
...resumeParams,
|
|
4078
|
+
model: binding.model ?? resumeParams.model ?? null,
|
|
4079
|
+
requestedModel: resumeParams.model ?? null,
|
|
4080
|
+
modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
|
|
4081
|
+
requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
|
|
4082
|
+
}, resumeAuthProfileId, dynamicToolsFingerprint);
|
|
4083
|
+
if (incognito && retainedThread.ephemeralPolicy !== resumeParams.developerInstructions) {
|
|
4084
|
+
preserveSubscription = true;
|
|
4085
|
+
throw new CodexIncognitoPolicyChangeError();
|
|
4086
|
+
}
|
|
4087
|
+
if (!incognito && retainedThread.configFingerprint !== liveThreadConfigFingerprint) {
|
|
4088
|
+
preserveSubscription = true;
|
|
4089
|
+
return {
|
|
4090
|
+
kind: "resume",
|
|
4091
|
+
prebuiltFinalConfigPatch
|
|
4092
|
+
};
|
|
4093
|
+
}
|
|
4094
|
+
await attestCodexThreadToolSurface({
|
|
4095
|
+
client: params.client,
|
|
4096
|
+
threadId: binding.threadId,
|
|
4097
|
+
appIds: pluginThreadConfig?.provisionalAppIds ?? [],
|
|
4098
|
+
signal: params.signal,
|
|
4099
|
+
threadConfig: resumeParams.config,
|
|
4100
|
+
restrictedToolSurface,
|
|
4101
|
+
lifecycleTiming,
|
|
4102
|
+
assertCurrent: assertWarmOwner
|
|
4103
|
+
});
|
|
4104
|
+
assertWarmOwner();
|
|
4033
4105
|
const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
|
|
4034
4106
|
const model = startModelSelection.model;
|
|
4035
|
-
if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
4107
|
+
if (!(incognito || await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
4036
4108
|
kind: "patch",
|
|
4037
4109
|
threadId: binding.threadId,
|
|
4038
4110
|
patch: {
|
|
@@ -4041,8 +4113,8 @@ async function tryReuseCodexLiveThread(options) {
|
|
|
4041
4113
|
nativeHookRelayGeneration,
|
|
4042
4114
|
environmentSelectionFingerprint
|
|
4043
4115
|
}
|
|
4044
|
-
}))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
|
|
4045
|
-
|
|
4116
|
+
}, assertWarmOwner)))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
|
|
4117
|
+
assertWarmOwner();
|
|
4046
4118
|
lifecycleTiming.mark("thread-ready");
|
|
4047
4119
|
lifecycleTiming.logSummary({
|
|
4048
4120
|
runId: params.params.runId,
|
|
@@ -4051,25 +4123,188 @@ async function tryReuseCodexLiveThread(options) {
|
|
|
4051
4123
|
threadId: binding.threadId,
|
|
4052
4124
|
action: "resumed"
|
|
4053
4125
|
});
|
|
4126
|
+
ownershipTransferred = true;
|
|
4054
4127
|
return {
|
|
4128
|
+
kind: "ready",
|
|
4055
4129
|
binding: {
|
|
4056
4130
|
...binding,
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4131
|
+
...!incognito ? {
|
|
4132
|
+
cwd: params.cwd,
|
|
4133
|
+
model,
|
|
4134
|
+
nativeHookRelayGeneration,
|
|
4135
|
+
environmentSelectionFingerprint
|
|
4136
|
+
} : {},
|
|
4061
4137
|
liveThreadConfigFingerprint,
|
|
4138
|
+
liveThreadEphemeralPolicy: retainedThread.ephemeralPolicy,
|
|
4062
4139
|
liveThreadOwnership: retainedThread,
|
|
4063
|
-
|
|
4140
|
+
...!incognito && retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
|
|
4064
4141
|
lifecycle: { action: "resumed" }
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
} finally {
|
|
4145
|
+
if (!ownershipTransferred) {
|
|
4146
|
+
let failure;
|
|
4147
|
+
try {
|
|
4148
|
+
if (preserveSubscription) {
|
|
4149
|
+
if (!await retainCodexAppServerBindingSubscription(params.client, binding.threadId, retainedThread)) failure = { cause: /* @__PURE__ */ new Error("Codex live thread ownership could not be returned to its session") };
|
|
4150
|
+
} else await retainedThread.release(binding.threadId);
|
|
4151
|
+
} catch (cause) {
|
|
4152
|
+
failure = { cause };
|
|
4153
|
+
}
|
|
4154
|
+
if (failure) await abandonCodexLiveThreadRelease({
|
|
4155
|
+
client: params.client,
|
|
4156
|
+
abandonClient: params.abandonClient,
|
|
4157
|
+
lifecycleTiming,
|
|
4158
|
+
threadId: binding.threadId
|
|
4159
|
+
}, failure.cause);
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
//#endregion
|
|
4164
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-adoption.ts
|
|
4165
|
+
/** Passive refusal must precede releasing or acquiring any native subscription. */
|
|
4166
|
+
async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context) {
|
|
4167
|
+
const { thread } = await context.lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
4168
|
+
threadId,
|
|
4169
|
+
includeTurns: false
|
|
4170
|
+
}, { signal: params.signal }));
|
|
4171
|
+
context.throwIfAborted();
|
|
4172
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
4173
|
+
if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
4174
|
+
if (thread.id !== threadId) throw new Error("Codex returned another thread during adoption status read");
|
|
4175
|
+
return thread;
|
|
4176
|
+
}
|
|
4177
|
+
/** All bound preparation follows attach's native-queue-before-binding-lease order. */
|
|
4178
|
+
async function withCodexThreadLifecycleBinding(params, run) {
|
|
4179
|
+
const identity = sessionBindingIdentity({
|
|
4180
|
+
sessionId: params.params.sessionId,
|
|
4181
|
+
sessionKey: params.params.sessionKey,
|
|
4182
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
4183
|
+
config: params.params.config
|
|
4184
|
+
});
|
|
4185
|
+
const snapshot = await params.bindingStore.read(identity);
|
|
4186
|
+
const runWithLease = () => params.bindingStore.withLease(identity, async () => {
|
|
4187
|
+
const binding = await params.bindingStore.read(identity);
|
|
4188
|
+
if (binding?.threadId !== snapshot?.threadId || binding?.clientId !== snapshot?.clientId) throw new CodexThreadBindingConflictError(binding?.threadId ?? snapshot?.threadId ?? params.params.sessionId, "acquiring thread lifecycle ownership");
|
|
4189
|
+
params.params.hostCapabilities.assertActive();
|
|
4190
|
+
params.signal?.throwIfAborted();
|
|
4191
|
+
return await run(identity, binding);
|
|
4192
|
+
});
|
|
4193
|
+
return snapshot?.pendingResumeConfiguration ? await withExclusiveCodexAppServerThread({
|
|
4194
|
+
bindingStore: params.bindingStore,
|
|
4195
|
+
identity,
|
|
4196
|
+
threadId: snapshot.threadId,
|
|
4197
|
+
run: runWithLease
|
|
4198
|
+
}) : snapshot ? await withCodexAppServerThreadMutation(snapshot.threadId, runWithLease) : await runWithLease();
|
|
4199
|
+
}
|
|
4200
|
+
/** Completes manual attachment only under the native queue and exact binding lease. */
|
|
4201
|
+
async function resumePendingCodexThread(params, context) {
|
|
4202
|
+
const { binding, contextEngineBinding, lifecycleTiming, restrictedToolSurface } = context;
|
|
4203
|
+
if (isIncognitoSessionKey(params.params.sessionKey) || context.transientRestriction || !restrictedToolSurface && binding.nativeToolPolicyRestricted === true || (contextEngineBinding ? !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding) : binding.contextEngine !== void 0) || shouldRotateCodexGpt56MultiAgentBinding({
|
|
4204
|
+
bindingModel: binding.model,
|
|
4205
|
+
requestedModel: params.params.modelId
|
|
4206
|
+
})) throw new Error(`Cannot configure resumed Codex thread ${binding.threadId} under a transient or incompatible session policy. The thread is preserved; retry from its normal session or use /new for the current policy.`);
|
|
4207
|
+
const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
4208
|
+
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
4209
|
+
const configuration = await preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint, async (assertCurrent) => {
|
|
4210
|
+
const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
|
|
4211
|
+
assertCurrent();
|
|
4212
|
+
if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
|
|
4213
|
+
client: params.client,
|
|
4214
|
+
abandonClient: params.abandonClient,
|
|
4215
|
+
lifecycleTiming,
|
|
4216
|
+
threadId: binding.threadId,
|
|
4217
|
+
assertCurrent
|
|
4218
|
+
});
|
|
4219
|
+
});
|
|
4220
|
+
try {
|
|
4221
|
+
const resumed = await resumeExistingCodexThread(params, {
|
|
4222
|
+
...context,
|
|
4223
|
+
prebuiltPluginThreadConfig,
|
|
4224
|
+
assertResumeConfiguration: configuration.assertConfigured,
|
|
4225
|
+
assertResumeOwnership: configuration.assertCurrent
|
|
4226
|
+
});
|
|
4227
|
+
if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
|
|
4228
|
+
return resumed;
|
|
4229
|
+
} finally {
|
|
4230
|
+
configuration.dispose();
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
/** Manual attachment is intent, never evidence that loaded native overrides took effect. */
|
|
4234
|
+
async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
|
|
4235
|
+
const fail = (reason) => /* @__PURE__ */ new Error(`Cannot configure resumed Codex thread ${binding.threadId}: ${reason}. The thread is preserved; continue it in native Codex or use /new for the current OpenClaw tools.`);
|
|
4236
|
+
const agentDir = resolveCodexThreadAgentDir(params);
|
|
4237
|
+
const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
|
|
4238
|
+
if (params.appServer.start.transport !== "stdio" || params.appServer.start.homeScope === "user" || path.resolve(localHome) !== resolveCodexAppServerHomeDir(agentDir) || binding.connectionScope === "supervision" || binding.preserveNativeModel === true) throw fail("configuration adoption requires an OpenClaw-owned local Codex home");
|
|
4239
|
+
if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw fail("the thread is claimed by active work; stop that run before resuming");
|
|
4240
|
+
const assertClient = captureCodexAppServerClientLifetime(params.client, "native-process");
|
|
4241
|
+
const assertCurrent = () => {
|
|
4242
|
+
params.params.hostCapabilities.assertActive();
|
|
4243
|
+
params.signal?.throwIfAborted();
|
|
4244
|
+
assertClient();
|
|
4245
|
+
if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
|
|
4246
|
+
};
|
|
4247
|
+
assertCurrent();
|
|
4248
|
+
const { thread } = await params.client.request("thread/read", {
|
|
4249
|
+
threadId: binding.threadId,
|
|
4250
|
+
includeTurns: false
|
|
4251
|
+
}, { signal: params.signal });
|
|
4252
|
+
assertCurrent();
|
|
4253
|
+
const statusType = thread.status?.type;
|
|
4254
|
+
if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
|
|
4255
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
4256
|
+
const observation = observeCodexThreadConfiguration(params, thread, assertCurrent);
|
|
4257
|
+
const dispose = observation.dispose;
|
|
4258
|
+
try {
|
|
4259
|
+
const rolloutPath = thread.path ?? binding.rolloutPath;
|
|
4260
|
+
const metadata = rolloutPath ? await readCodexSessionMeta(path.join(localHome, "sessions"), rolloutPath, binding.threadId) : void 0;
|
|
4261
|
+
if (!metadata) throw fail("its native tool catalog could not be read from the selected Codex home");
|
|
4262
|
+
const recordedTools = metadata.dynamic_tools ?? [];
|
|
4263
|
+
if (!Array.isArray(recordedTools) || codexDynamicToolsFingerprint(recordedTools) !== dynamicToolsFingerprint) throw fail("its immutable native tool catalog does not match the current OpenClaw tools");
|
|
4264
|
+
assertCurrent();
|
|
4265
|
+
await releaseSubscription(assertCurrent);
|
|
4266
|
+
assertCurrent();
|
|
4267
|
+
return {
|
|
4268
|
+
assertConfigured: observation.assertConfigured,
|
|
4269
|
+
assertCurrent,
|
|
4270
|
+
dispose
|
|
4067
4271
|
};
|
|
4068
4272
|
} catch (error) {
|
|
4069
|
-
|
|
4273
|
+
dispose();
|
|
4070
4274
|
throw error;
|
|
4071
4275
|
}
|
|
4072
4276
|
}
|
|
4277
|
+
/** Observe teardown before release; a successful resume alone can acknowledge ignored overrides. */
|
|
4278
|
+
async function prepareCodexThreadResume(params, binding, context) {
|
|
4279
|
+
const assertClient = captureCodexAppServerClientLifetime(params.client, binding.connectionScope === "supervision" ? "connection" : "native-process");
|
|
4280
|
+
const assertCurrent = () => {
|
|
4281
|
+
params.params.hostCapabilities.assertActive();
|
|
4282
|
+
params.signal?.throwIfAborted();
|
|
4283
|
+
assertClient();
|
|
4284
|
+
if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw new CodexAdoptedThreadActiveError();
|
|
4285
|
+
};
|
|
4286
|
+
assertCurrent();
|
|
4287
|
+
const thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context);
|
|
4288
|
+
assertCurrent();
|
|
4289
|
+
assertCodexSupervisionThreadLineage(binding, thread);
|
|
4290
|
+
return {
|
|
4291
|
+
...observeCodexThreadConfiguration(params, thread, assertCurrent),
|
|
4292
|
+
assertCurrent
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
function observeCodexThreadConfiguration(params, thread, assertCurrent) {
|
|
4296
|
+
if (thread.status?.type !== "idle" && thread.status?.type !== "notLoaded") throw new CodexAdoptedThreadActiveError();
|
|
4297
|
+
let unloaded = thread.status.type === "notLoaded";
|
|
4298
|
+
return {
|
|
4299
|
+
dispose: params.client.addNotificationHandler((notification) => {
|
|
4300
|
+
if (notification.method === "thread/status/changed" && isJsonObject(notification.params) && notification.params.threadId === thread.id && isJsonObject(notification.params.status) && notification.params.status.type === "notLoaded") unloaded = true;
|
|
4301
|
+
}),
|
|
4302
|
+
assertConfigured: () => {
|
|
4303
|
+
assertCurrent();
|
|
4304
|
+
if (!unloaded) throw new Error("Codex did not confirm unloading its previous configuration. The thread is preserved; stop competing native work and reconnect before retrying.");
|
|
4305
|
+
}
|
|
4306
|
+
};
|
|
4307
|
+
}
|
|
4073
4308
|
//#endregion
|
|
4074
4309
|
//#region extensions/codex/src/app-server/thread-supervision.ts
|
|
4075
4310
|
async function materializePendingSupervisionBranch(params) {
|
|
@@ -4094,6 +4329,32 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
4094
4329
|
});
|
|
4095
4330
|
let bindingCommitted = false;
|
|
4096
4331
|
let provisionalCleanupSafe = true;
|
|
4332
|
+
let cleanupExpected = pending;
|
|
4333
|
+
const trackPendingSupervisionArtifacts = async (cleanupThreadIds) => {
|
|
4334
|
+
const expected = pending;
|
|
4335
|
+
pending = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
4336
|
+
let updated;
|
|
4337
|
+
try {
|
|
4338
|
+
updated = await params.bindingStore.mutate(params.bindingIdentity, {
|
|
4339
|
+
kind: "patch-pending-supervision-branch",
|
|
4340
|
+
expected,
|
|
4341
|
+
pending
|
|
4342
|
+
});
|
|
4343
|
+
} catch (error) {
|
|
4344
|
+
try {
|
|
4345
|
+
const current = await params.bindingStore.read(params.bindingIdentity);
|
|
4346
|
+
if (matchesPendingSupervisionState(current, pending)) cleanupExpected = pending;
|
|
4347
|
+
else if (matchesPendingSupervisionState(current, expected)) cleanupExpected = expected;
|
|
4348
|
+
else throw new CodexThreadBindingConflictError(pending.sourceThreadId, "verifying supervised Codex cleanup tracking");
|
|
4349
|
+
} catch (verificationError) {
|
|
4350
|
+
provisionalCleanupSafe = false;
|
|
4351
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup tracking could not be verified: ${cleanupThreadIds.join(", ")}`, { cause: new AggregateError([error, verificationError], void 0, { cause: error }) });
|
|
4352
|
+
}
|
|
4353
|
+
throw error;
|
|
4354
|
+
}
|
|
4355
|
+
cleanupExpected = updated ? pending : void 0;
|
|
4356
|
+
if (!updated) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
4357
|
+
};
|
|
4097
4358
|
try {
|
|
4098
4359
|
const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
|
|
4099
4360
|
const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
|
|
@@ -4109,10 +4370,17 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
4109
4370
|
sourceThreadId: pending.sourceThreadId,
|
|
4110
4371
|
role: "model probe"
|
|
4111
4372
|
});
|
|
4112
|
-
|
|
4373
|
+
let probeResponse;
|
|
4374
|
+
try {
|
|
4375
|
+
params.throwIfAborted();
|
|
4376
|
+
probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
4377
|
+
if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
|
|
4378
|
+
} finally {
|
|
4379
|
+
await unsubscribeCodexAppServerLiveThread(params.client, probeThreadId, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS).catch((cause) => {
|
|
4380
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex model probe subscription could not be released: ${probeThreadId}`, { cause });
|
|
4381
|
+
});
|
|
4382
|
+
}
|
|
4113
4383
|
params.throwIfAborted();
|
|
4114
|
-
const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
4115
|
-
if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
|
|
4116
4384
|
const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
|
|
4117
4385
|
const nativeModelProvider = requireNativeSupervisionModelProvider({
|
|
4118
4386
|
responseModelProvider: probeResponse.modelProvider,
|
|
@@ -4158,7 +4426,7 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
4158
4426
|
otherThreadId: probeThreadId,
|
|
4159
4427
|
role: "canonical branch"
|
|
4160
4428
|
});
|
|
4161
|
-
|
|
4429
|
+
await trackPendingSupervisionArtifacts([finalThreadId]);
|
|
4162
4430
|
params.throwIfAborted();
|
|
4163
4431
|
assertExactSupervisionModelSelection(assertCodexThreadStartResponse(rawStartResponse), {
|
|
4164
4432
|
model: nativeModel,
|
|
@@ -4178,11 +4446,11 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
4178
4446
|
client: params.client,
|
|
4179
4447
|
threadId: finalThreadId,
|
|
4180
4448
|
ephemeral: startParams.ephemeral === true
|
|
4181
|
-
})
|
|
4449
|
+
})) {
|
|
4182
4450
|
provisionalCleanupSafe = false;
|
|
4183
4451
|
throw new CodexAppServerUnsafeSubscriptionError("Codex supervised plugin app attestation cleanup failed", { cause: error });
|
|
4184
4452
|
}
|
|
4185
|
-
|
|
4453
|
+
await trackPendingSupervisionArtifacts([]);
|
|
4186
4454
|
throw error;
|
|
4187
4455
|
}
|
|
4188
4456
|
if (history.responseItems.length > 0) {
|
|
@@ -4192,8 +4460,6 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
4192
4460
|
}, { signal: params.signal }));
|
|
4193
4461
|
params.throwIfAborted();
|
|
4194
4462
|
}
|
|
4195
|
-
if (!await archiveSupervisionArtifact(params.client, probeThreadId)) throw new Error(`Failed to archive temporary Codex model probe: ${probeThreadId}`);
|
|
4196
|
-
pending = await trackPendingSupervisionArtifacts(params, pending, [finalThreadId]);
|
|
4197
4463
|
const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
|
|
4198
4464
|
const bindingModelProvider = params.normalizeBindingModelProvider(params.attempt.authProfileId, nativeModelProvider);
|
|
4199
4465
|
let committed = false;
|
|
@@ -4255,31 +4521,28 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
4255
4521
|
};
|
|
4256
4522
|
} catch (error) {
|
|
4257
4523
|
if (bindingCommitted) throw error;
|
|
4258
|
-
if (error instanceof CodexThreadBindingConflictAfterCleanupError) throw error;
|
|
4259
4524
|
if (!provisionalCleanupSafe) {
|
|
4260
4525
|
await params.abandonClient();
|
|
4261
4526
|
throw error;
|
|
4262
4527
|
}
|
|
4263
4528
|
const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
|
|
4529
|
+
const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
|
|
4264
4530
|
let cleanupStateError;
|
|
4265
|
-
if (
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
} catch (stateError) {
|
|
4274
|
-
cleanupStateError = stateError;
|
|
4275
|
-
}
|
|
4531
|
+
if (cleanupExpected && !isDeepStrictEqual(cleanupExpected, nextPending)) try {
|
|
4532
|
+
await params.bindingStore.mutate(params.bindingIdentity, {
|
|
4533
|
+
kind: "patch-pending-supervision-branch",
|
|
4534
|
+
expected: cleanupExpected,
|
|
4535
|
+
pending: nextPending
|
|
4536
|
+
});
|
|
4537
|
+
} catch (stateError) {
|
|
4538
|
+
cleanupStateError = stateError;
|
|
4276
4539
|
}
|
|
4277
4540
|
const unsafeCleanup = cleanup.remaining.length > 0 || isCodexAppServerUnsafeSubscriptionError(error);
|
|
4278
4541
|
if (unsafeCleanup) await params.abandonClient();
|
|
4279
4542
|
if (cleanupStateError) {
|
|
4280
|
-
const cause = new AggregateError([error, cleanupStateError]);
|
|
4543
|
+
const cause = new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
|
|
4281
4544
|
if (unsafeCleanup) throw new CodexAppServerUnsafeSubscriptionError("Codex supervised branch cleanup state could not be recorded", { cause });
|
|
4282
|
-
throw
|
|
4545
|
+
throw cause;
|
|
4283
4546
|
}
|
|
4284
4547
|
if (cleanup.remaining.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup remains pending: ${cleanup.remaining.join(", ")}`, { cause: error });
|
|
4285
4548
|
throw error;
|
|
@@ -4307,7 +4570,7 @@ function buildPendingSupervisionProbeForkParams(params, pending) {
|
|
|
4307
4570
|
...params.appServer.serviceTier !== void 0 ? { serviceTier: params.appServer.serviceTier } : {},
|
|
4308
4571
|
config: runtimeConfig,
|
|
4309
4572
|
developerInstructions: params.developerInstructions ?? buildDeveloperInstructions(params.attempt, { dynamicTools: params.dynamicTools }),
|
|
4310
|
-
ephemeral:
|
|
4573
|
+
ephemeral: true,
|
|
4311
4574
|
threadSource: "appServer",
|
|
4312
4575
|
excludeTurns: true
|
|
4313
4576
|
};
|
|
@@ -4349,10 +4612,8 @@ function requireDistinctSupervisionThreadId(params) {
|
|
|
4349
4612
|
return threadId;
|
|
4350
4613
|
}
|
|
4351
4614
|
function readSupervisionResponseThreadId(value) {
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
if (!thread || typeof thread !== "object" || Array.isArray(thread)) return;
|
|
4355
|
-
return thread.id;
|
|
4615
|
+
const thread = isRecord(value) ? value.thread : void 0;
|
|
4616
|
+
return isRecord(thread) ? thread.id : void 0;
|
|
4356
4617
|
}
|
|
4357
4618
|
async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
4358
4619
|
if (!pending.cleanupThreadIds?.length) return pending;
|
|
@@ -4375,20 +4636,6 @@ async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
|
4375
4636
|
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
|
|
4376
4637
|
return next;
|
|
4377
4638
|
}
|
|
4378
|
-
async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
|
|
4379
|
-
const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
4380
|
-
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
4381
|
-
kind: "patch-pending-supervision-branch",
|
|
4382
|
-
expected: pending,
|
|
4383
|
-
pending: next
|
|
4384
|
-
})) {
|
|
4385
|
-
const cleanupFailed = [];
|
|
4386
|
-
for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
|
|
4387
|
-
if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
|
|
4388
|
-
throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
4389
|
-
}
|
|
4390
|
-
return next;
|
|
4391
|
-
}
|
|
4392
4639
|
function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
|
|
4393
4640
|
return {
|
|
4394
4641
|
sourceThreadId: pending.sourceThreadId,
|
|
@@ -4425,15 +4672,23 @@ async function archiveSupervisionArtifact(client, threadId) {
|
|
|
4425
4672
|
async function startOrResumeThread(params) {
|
|
4426
4673
|
const incognito = isIncognitoSessionKey(params.params.sessionKey);
|
|
4427
4674
|
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4675
|
+
return await withCodexThreadLifecycleBinding(params, async (bindingIdentity, currentBinding) => {
|
|
4676
|
+
let binding = currentBinding;
|
|
4677
|
+
if (hasCodexNativeToolCatalog(binding)) {
|
|
4678
|
+
const nativeCatalog = await loadCodexNativeToolCatalog({
|
|
4679
|
+
client: params.client,
|
|
4680
|
+
binding,
|
|
4681
|
+
appServer: params.appServer,
|
|
4682
|
+
agentDir: resolveCodexThreadAgentDir(params),
|
|
4683
|
+
assertCurrent: () => {
|
|
4684
|
+
params.signal?.throwIfAborted();
|
|
4685
|
+
params.params.hostCapabilities.assertActive();
|
|
4686
|
+
}
|
|
4687
|
+
});
|
|
4688
|
+
if (!isDeepStrictEqual(params.dynamicTools, nativeCatalog)) throw new Error("Canonical Codex declarations changed after tool preparation; retry the turn on its preserved native thread.");
|
|
4689
|
+
}
|
|
4690
|
+
const preflight = await prepareCodexThreadLifecyclePreflight(params);
|
|
4691
|
+
const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = preflight;
|
|
4437
4692
|
let replacementPredecessor;
|
|
4438
4693
|
const initialBoundThreadId = binding?.threadId;
|
|
4439
4694
|
const initialBoundClientId = binding?.clientId;
|
|
@@ -4445,28 +4700,15 @@ async function startOrResumeThread(params) {
|
|
|
4445
4700
|
config: params.params.config
|
|
4446
4701
|
});
|
|
4447
4702
|
const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
|
|
4448
|
-
const releaseRetainedThread =
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
});
|
|
4458
|
-
} finally {
|
|
4459
|
-
previousClient.release();
|
|
4460
|
-
}
|
|
4461
|
-
return;
|
|
4462
|
-
}
|
|
4463
|
-
await releaseCodexRetainedLiveThread({
|
|
4464
|
-
client: params.client,
|
|
4465
|
-
abandonClient: params.abandonClient,
|
|
4466
|
-
lifecycleTiming,
|
|
4467
|
-
threadId
|
|
4468
|
-
});
|
|
4469
|
-
};
|
|
4703
|
+
const releaseRetainedThread = (threadId, ownerClientId = initialBoundClientId, assertCurrent) => releaseCodexBoundLiveThread({
|
|
4704
|
+
client: params.client,
|
|
4705
|
+
clientId,
|
|
4706
|
+
ownerClientId,
|
|
4707
|
+
abandonClient: params.abandonClient,
|
|
4708
|
+
lifecycleTiming,
|
|
4709
|
+
threadId,
|
|
4710
|
+
assertCurrent
|
|
4711
|
+
});
|
|
4470
4712
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
4471
4713
|
if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
|
|
4472
4714
|
bindingStore: params.bindingStore,
|
|
@@ -4546,6 +4788,36 @@ async function startOrResumeThread(params) {
|
|
|
4546
4788
|
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
4547
4789
|
binding = void 0;
|
|
4548
4790
|
};
|
|
4791
|
+
const resolveRequestContext = () => {
|
|
4792
|
+
const startModelSelection = resolveCodexAppServerThreadModelSelection({
|
|
4793
|
+
provider: params.params.provider,
|
|
4794
|
+
model: params.runtimeModelId ?? params.params.modelId,
|
|
4795
|
+
binding,
|
|
4796
|
+
authProfileId: params.params.authProfileId,
|
|
4797
|
+
authProfileStore: params.params.authProfileStore,
|
|
4798
|
+
agentDir: params.params.agentDir,
|
|
4799
|
+
config: params.params.config
|
|
4800
|
+
});
|
|
4801
|
+
return {
|
|
4802
|
+
...preflight,
|
|
4803
|
+
bindingIdentity,
|
|
4804
|
+
startModelSelection,
|
|
4805
|
+
startModelProvider: startModelSelection.modelProvider,
|
|
4806
|
+
normalizeBindingModelProvider,
|
|
4807
|
+
throwIfAborted
|
|
4808
|
+
};
|
|
4809
|
+
};
|
|
4810
|
+
const transientDelegationRestriction = params.params.delegationCapability === "report_only";
|
|
4811
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
4812
|
+
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
4813
|
+
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
4814
|
+
if (binding?.pendingResumeConfiguration) return await resumePendingCodexThread(params, {
|
|
4815
|
+
...resolveRequestContext(),
|
|
4816
|
+
binding,
|
|
4817
|
+
clearCurrentBinding,
|
|
4818
|
+
releaseRetainedThread: (threadId, assertCurrent) => releaseRetainedThread(threadId, initialBoundClientId, assertCurrent),
|
|
4819
|
+
transientRestriction: transientDelegationRestriction || transientNativeToolRestriction || transientWebSearchRestriction
|
|
4820
|
+
});
|
|
4549
4821
|
if (binding?.threadId && !restrictedToolSurface && binding.nativeToolPolicyRestricted === true) await clearCurrentBinding("rotating a host-policy-restricted thread binding");
|
|
4550
4822
|
if (binding?.threadId && binding.nativeSkillIsolationFingerprint !== nativeSkillIsolationFingerprint) {
|
|
4551
4823
|
embeddedAgentLog.debug("codex app-server native skill isolation changed; starting a new thread", { threadId: binding.threadId });
|
|
@@ -4579,24 +4851,30 @@ async function startOrResumeThread(params) {
|
|
|
4579
4851
|
await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
|
|
4580
4852
|
binding = void 0;
|
|
4581
4853
|
}
|
|
4582
|
-
const
|
|
4583
|
-
provider: params.params.provider,
|
|
4584
|
-
model: params.runtimeModelId ?? params.params.modelId,
|
|
4585
|
-
binding,
|
|
4586
|
-
authProfileId: params.params.authProfileId,
|
|
4587
|
-
authProfileStore: params.params.authProfileStore,
|
|
4588
|
-
agentDir: params.params.agentDir,
|
|
4589
|
-
config: params.params.config
|
|
4590
|
-
});
|
|
4591
|
-
const startModelProvider = startModelSelection.modelProvider;
|
|
4592
|
-
const transientDelegationRestriction = params.params.delegationCapability === "report_only";
|
|
4854
|
+
const requestContext = resolveRequestContext();
|
|
4593
4855
|
let preserveExistingBinding = transientDelegationRestriction || !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
4594
4856
|
let rotatedContextEngineBinding = false;
|
|
4595
4857
|
let prebuiltPluginThreadConfig;
|
|
4858
|
+
const buildLoadedPluginThreadConfig = async (current) => {
|
|
4859
|
+
if (!params.pluginThreadConfig?.requiresCurrentPolicyCheck && !shouldRecheckRecoverablePluginBinding({
|
|
4860
|
+
binding: current,
|
|
4861
|
+
pluginThreadConfig: params.pluginThreadConfig
|
|
4862
|
+
})) return;
|
|
4863
|
+
try {
|
|
4864
|
+
prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: current.threadId }));
|
|
4865
|
+
} catch (error) {
|
|
4866
|
+
throwIfAborted();
|
|
4867
|
+
if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
|
|
4868
|
+
embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
|
|
4869
|
+
error,
|
|
4870
|
+
threadId: current.threadId
|
|
4871
|
+
});
|
|
4872
|
+
return;
|
|
4873
|
+
}
|
|
4874
|
+
throwIfAborted();
|
|
4875
|
+
return prebuiltPluginThreadConfig;
|
|
4876
|
+
};
|
|
4596
4877
|
const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
|
|
4597
|
-
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
4598
|
-
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
4599
|
-
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
4600
4878
|
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
4601
4879
|
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
4602
4880
|
if (binding?.threadId && (params.configuredMcpOwnershipVersion === 1 && (binding.configuredMcpOwnershipVersion !== 1 || binding.dynamicToolsFingerprint === void 0 || binding.mcpServersFingerprint !== void 0 || binding.userMcpServersFingerprint !== void 0) || params.configuredMcpOwnershipVersion !== 1 && binding.configuredMcpOwnershipVersion === 1) && binding?.threadId) {
|
|
@@ -4674,28 +4952,13 @@ async function startOrResumeThread(params) {
|
|
|
4674
4952
|
binding = void 0;
|
|
4675
4953
|
}
|
|
4676
4954
|
if (binding?.threadId) {
|
|
4677
|
-
|
|
4955
|
+
if (isCodexPluginThreadBindingStale({
|
|
4678
4956
|
codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
|
|
4679
4957
|
bindingFingerprint: binding.pluginAppsFingerprint,
|
|
4680
4958
|
bindingInputFingerprint: binding.pluginAppsInputFingerprint,
|
|
4681
4959
|
currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
|
|
4682
4960
|
hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
|
|
4683
|
-
})
|
|
4684
|
-
if (!pluginBindingStale && (params.pluginThreadConfig?.requiresCurrentPolicyCheck || shouldRecheckRecoverablePluginBinding({
|
|
4685
|
-
binding,
|
|
4686
|
-
pluginThreadConfig: params.pluginThreadConfig
|
|
4687
|
-
}))) try {
|
|
4688
|
-
const bindingThreadId = binding.threadId;
|
|
4689
|
-
prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: bindingThreadId }));
|
|
4690
|
-
pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
|
|
4691
|
-
} catch (error) {
|
|
4692
|
-
if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
|
|
4693
|
-
embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
|
|
4694
|
-
error,
|
|
4695
|
-
threadId: binding.threadId
|
|
4696
|
-
});
|
|
4697
|
-
}
|
|
4698
|
-
if (pluginBindingStale) {
|
|
4961
|
+
})) {
|
|
4699
4962
|
embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
|
|
4700
4963
|
await clearCurrentBinding("rotating a stale thread binding");
|
|
4701
4964
|
binding = void 0;
|
|
@@ -4708,120 +4971,55 @@ async function startOrResumeThread(params) {
|
|
|
4708
4971
|
binding = void 0;
|
|
4709
4972
|
}
|
|
4710
4973
|
}
|
|
4711
|
-
if (binding?.threadId)
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
4718
|
-
else {
|
|
4719
|
-
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
4720
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
4721
|
-
}
|
|
4722
|
-
} else if (incognito) {
|
|
4723
|
-
if (binding.clientId && binding.clientId === clientId) {
|
|
4724
|
-
params.buildFinalConfigPatch?.({
|
|
4725
|
-
action: "resume",
|
|
4726
|
-
binding
|
|
4974
|
+
if (binding?.threadId) {
|
|
4975
|
+
if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
|
|
4976
|
+
assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
|
|
4977
|
+
preserveExistingBinding = shouldStartTransientNoToolThread({
|
|
4978
|
+
previous: binding.dynamicToolsFingerprint,
|
|
4979
|
+
nextHasDynamicTools: params.dynamicTools.length > 0
|
|
4727
4980
|
});
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4981
|
+
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
4982
|
+
else {
|
|
4983
|
+
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
4984
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
4985
|
+
}
|
|
4986
|
+
} else {
|
|
4987
|
+
const warmReuse = await tryReuseCodexLiveThread({
|
|
4988
|
+
...requestContext,
|
|
4989
|
+
params,
|
|
4990
|
+
binding,
|
|
4991
|
+
clientId,
|
|
4992
|
+
buildLoadedPluginThreadConfig
|
|
4736
4993
|
});
|
|
4737
|
-
return
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4994
|
+
if (warmReuse.kind === "ready") return warmReuse.binding;
|
|
4995
|
+
if (incognito || warmReuse.kind === "rotate") {
|
|
4996
|
+
throwIfAborted();
|
|
4997
|
+
await clearCurrentBinding(incognito ? "rotating an unavailable ephemeral thread binding" : "rotating a stale plugin app binding");
|
|
4998
|
+
} else {
|
|
4999
|
+
const configuration = await prepareCodexThreadResume(params, binding, requestContext);
|
|
5000
|
+
try {
|
|
5001
|
+
await releaseRetainedThread(binding.threadId, binding.clientId, configuration.assertCurrent);
|
|
5002
|
+
configuration.assertCurrent();
|
|
5003
|
+
const resumed = await resumeExistingCodexThread(params, {
|
|
5004
|
+
...requestContext,
|
|
5005
|
+
binding,
|
|
5006
|
+
clearCurrentBinding,
|
|
5007
|
+
prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
|
|
5008
|
+
prebuiltPluginThreadConfig,
|
|
5009
|
+
buildLoadedPluginThreadConfig,
|
|
5010
|
+
assertResumeOwnership: configuration.assertCurrent,
|
|
5011
|
+
assertResumeConfiguration: configuration.assertConfigured
|
|
5012
|
+
});
|
|
5013
|
+
if (resumed) return resumed;
|
|
5014
|
+
} finally {
|
|
5015
|
+
configuration.dispose();
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
4741
5018
|
}
|
|
4742
|
-
await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
|
|
4743
|
-
binding = void 0;
|
|
4744
|
-
} else {
|
|
4745
|
-
const warmReuse = await tryReuseCodexLiveThread({
|
|
4746
|
-
params,
|
|
4747
|
-
binding,
|
|
4748
|
-
bindingIdentity,
|
|
4749
|
-
clientId,
|
|
4750
|
-
dynamicToolsFingerprint,
|
|
4751
|
-
environmentSelectionFingerprint,
|
|
4752
|
-
hostSystemAgentActive,
|
|
4753
|
-
lifecycleTiming,
|
|
4754
|
-
nativeSkillIsolation,
|
|
4755
|
-
releaseConsumedThread: (threadId, cause) => releaseCodexConsumedLiveThread({
|
|
4756
|
-
client: params.client,
|
|
4757
|
-
abandonClient: params.abandonClient,
|
|
4758
|
-
lifecycleTiming,
|
|
4759
|
-
threadId,
|
|
4760
|
-
cause
|
|
4761
|
-
}),
|
|
4762
|
-
ringZeroActive,
|
|
4763
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
4764
|
-
startModelProvider,
|
|
4765
|
-
startModelSelection,
|
|
4766
|
-
throwIfAborted,
|
|
4767
|
-
userMcpServersConfigPatch
|
|
4768
|
-
});
|
|
4769
|
-
if (warmReuse.binding) return warmReuse.binding;
|
|
4770
|
-
await releaseRetainedThread(binding.threadId);
|
|
4771
|
-
const resumed = await resumeExistingCodexThread(params, {
|
|
4772
|
-
binding,
|
|
4773
|
-
bindingIdentity,
|
|
4774
|
-
startModelSelection,
|
|
4775
|
-
startModelProvider,
|
|
4776
|
-
userMcpServersConfigPatch,
|
|
4777
|
-
dynamicToolsFingerprint,
|
|
4778
|
-
dynamicToolsContainDeferred,
|
|
4779
|
-
webSearchThreadConfigFingerprint,
|
|
4780
|
-
nativeSkillIsolationFingerprint,
|
|
4781
|
-
userMcpServersFingerprint,
|
|
4782
|
-
ringZeroConfigFingerprint,
|
|
4783
|
-
ringZeroClientInstanceId,
|
|
4784
|
-
networkProxyConfigFingerprint,
|
|
4785
|
-
contextEngineBinding,
|
|
4786
|
-
environmentSelectionFingerprint,
|
|
4787
|
-
hostSystemAgentActive,
|
|
4788
|
-
ringZeroActive,
|
|
4789
|
-
restrictedToolSurface,
|
|
4790
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
4791
|
-
nativeSkillIsolation,
|
|
4792
|
-
lifecycleTiming,
|
|
4793
|
-
normalizeBindingModelProvider,
|
|
4794
|
-
throwIfAborted,
|
|
4795
|
-
clearCurrentBinding,
|
|
4796
|
-
prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
|
|
4797
|
-
prebuiltPluginThreadConfig
|
|
4798
|
-
});
|
|
4799
|
-
if (resumed) return resumed;
|
|
4800
5019
|
}
|
|
4801
5020
|
if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
|
|
4802
5021
|
const started = await startFreshCodexThread(params, {
|
|
4803
|
-
|
|
4804
|
-
startModelSelection,
|
|
4805
|
-
startModelProvider,
|
|
4806
|
-
userMcpServersConfigPatch,
|
|
4807
|
-
dynamicToolsFingerprint,
|
|
4808
|
-
dynamicToolsContainDeferred,
|
|
4809
|
-
webSearchThreadConfigFingerprint,
|
|
4810
|
-
nativeSkillIsolationFingerprint,
|
|
4811
|
-
userMcpServersFingerprint,
|
|
4812
|
-
ringZeroConfigFingerprint,
|
|
4813
|
-
ringZeroClientInstanceId,
|
|
4814
|
-
networkProxyConfigFingerprint,
|
|
4815
|
-
contextEngineBinding,
|
|
4816
|
-
environmentSelectionFingerprint,
|
|
4817
|
-
hostSystemAgentActive,
|
|
4818
|
-
ringZeroActive,
|
|
4819
|
-
restrictedToolSurface,
|
|
4820
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
4821
|
-
nativeSkillIsolation,
|
|
4822
|
-
lifecycleTiming,
|
|
4823
|
-
normalizeBindingModelProvider,
|
|
4824
|
-
throwIfAborted,
|
|
5022
|
+
...requestContext,
|
|
4825
5023
|
prebuiltPluginThreadConfig,
|
|
4826
5024
|
preserveExistingBinding,
|
|
4827
5025
|
rotatedContextEngineBinding,
|
|
@@ -4832,6 +5030,54 @@ async function startOrResumeThread(params) {
|
|
|
4832
5030
|
});
|
|
4833
5031
|
}
|
|
4834
5032
|
//#endregion
|
|
5033
|
+
//#region extensions/codex/src/app-server/reasoning-effort.ts
|
|
5034
|
+
const CODEX_REASONING_EFFORTS = [
|
|
5035
|
+
"minimal",
|
|
5036
|
+
"low",
|
|
5037
|
+
"medium",
|
|
5038
|
+
"high",
|
|
5039
|
+
"xhigh",
|
|
5040
|
+
"max"
|
|
5041
|
+
];
|
|
5042
|
+
const LEGACY_PRO_REASONING_EFFORTS = [
|
|
5043
|
+
"medium",
|
|
5044
|
+
"high",
|
|
5045
|
+
"xhigh"
|
|
5046
|
+
];
|
|
5047
|
+
const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
|
|
5048
|
+
const MODERN_GPT_5_MODEL_ID_RE = /^gpt-5\.(?:[3-9]|[1-9]\d)(?:$|-)/u;
|
|
5049
|
+
/** Read reasoning metadata after the Codex app-server route has been selected. */
|
|
5050
|
+
function readCodexSupportedReasoningEfforts(compat) {
|
|
5051
|
+
if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
|
|
5052
|
+
const efforts = compat.supportedReasoningEfforts;
|
|
5053
|
+
if (!Array.isArray(efforts)) return;
|
|
5054
|
+
return efforts.filter((effort) => typeof effort === "string");
|
|
5055
|
+
}
|
|
5056
|
+
function resolveSupportedReasoningEffort(params) {
|
|
5057
|
+
const declared = new Set(params.supportedReasoningEfforts.map((effort) => effort.trim().toLowerCase()));
|
|
5058
|
+
const supported = CODEX_REASONING_EFFORTS.filter((effort) => declared.has(effort));
|
|
5059
|
+
if (supported.includes(params.requested)) return params.requested;
|
|
5060
|
+
const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
|
|
5061
|
+
return supported.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? supported.at(-1);
|
|
5062
|
+
}
|
|
5063
|
+
function resolveCodexAppServerReasoningEffort(params) {
|
|
5064
|
+
if (params.thinkLevel === "ultra") return "ultra";
|
|
5065
|
+
if (params.thinkLevel === "off") return params.supportedReasoningEfforts?.includes("none") ? "none" : null;
|
|
5066
|
+
if (params.thinkLevel === "adaptive") return null;
|
|
5067
|
+
if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
5068
|
+
requested: params.thinkLevel,
|
|
5069
|
+
supportedReasoningEfforts: params.supportedReasoningEfforts
|
|
5070
|
+
}) ?? null;
|
|
5071
|
+
const modelId = params.modelId.trim().toLowerCase();
|
|
5072
|
+
if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
|
|
5073
|
+
requested: params.thinkLevel,
|
|
5074
|
+
supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
|
|
5075
|
+
}) ?? null;
|
|
5076
|
+
if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
|
|
5077
|
+
if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
|
|
5078
|
+
return null;
|
|
5079
|
+
}
|
|
5080
|
+
//#endregion
|
|
4835
5081
|
//#region extensions/codex/src/app-server/user-input.ts
|
|
4836
5082
|
/** Builds ordered Codex user input for both new turns and same-turn steering. */
|
|
4837
5083
|
function buildCodexUserInput(text, images) {
|
|
@@ -4884,12 +5130,29 @@ function buildTurnStartParams(params, options) {
|
|
|
4884
5130
|
agentDir: params.agentDir,
|
|
4885
5131
|
config: params.config
|
|
4886
5132
|
});
|
|
5133
|
+
const collaborationMode = modelSelection ? buildTurnCollaborationMode(params, {
|
|
5134
|
+
model: modelSelection.model,
|
|
5135
|
+
turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
|
|
5136
|
+
skillsCollaborationInstructions: options.skillsCollaborationInstructions,
|
|
5137
|
+
memoryCollaborationInstructions: options.memoryCollaborationInstructions
|
|
5138
|
+
}) : void 0;
|
|
4887
5139
|
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
4888
5140
|
const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
5141
|
+
let additionalContext = buildCodexTemporalAdditionalContext(params, { sessionStatusAvailable: options.sessionStatusAvailable === true });
|
|
5142
|
+
if (currentSenderContext) additionalContext = {
|
|
5143
|
+
...additionalContext,
|
|
5144
|
+
openclaw_current_sender: {
|
|
5145
|
+
kind: "untrusted",
|
|
5146
|
+
value: currentSenderContext
|
|
5147
|
+
}
|
|
5148
|
+
};
|
|
5149
|
+
if (params.permissionChange?.notice) additionalContext = {
|
|
5150
|
+
...additionalContext,
|
|
5151
|
+
openclaw_permission_change: {
|
|
5152
|
+
kind: "application",
|
|
5153
|
+
value: params.permissionChange.notice
|
|
5154
|
+
}
|
|
5155
|
+
};
|
|
4893
5156
|
return {
|
|
4894
5157
|
threadId: options.threadId,
|
|
4895
5158
|
input: [...buildCodexUserInput(options.promptText ?? params.prompt, params.images), ...options.explicitSkillInputs ?? []],
|
|
@@ -4904,23 +5167,33 @@ function buildTurnStartParams(params, options) {
|
|
|
4904
5167
|
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
4905
5168
|
} : {},
|
|
4906
5169
|
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : options.clearInheritedServiceTier ? { serviceTier: null } : {},
|
|
4907
|
-
...
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
skillsCollaborationInstructions: options.skillsCollaborationInstructions,
|
|
4913
|
-
memoryCollaborationInstructions: options.memoryCollaborationInstructions
|
|
4914
|
-
}) } : {}
|
|
5170
|
+
...collaborationMode ? {
|
|
5171
|
+
effort: collaborationMode.settings.reasoning_effort,
|
|
5172
|
+
collaborationMode
|
|
5173
|
+
} : {},
|
|
5174
|
+
...options.environmentSelection ? { environments: options.environmentSelection } : {}
|
|
4915
5175
|
};
|
|
4916
5176
|
}
|
|
5177
|
+
function buildCodexTemporalAdditionalContext(params, options) {
|
|
5178
|
+
return { openclaw_temporal_context: {
|
|
5179
|
+
kind: "application",
|
|
5180
|
+
value: buildTemporalContextText({
|
|
5181
|
+
configuredTimezone: params.config?.agents?.defaults?.userTimezone,
|
|
5182
|
+
sessionStatusAvailable: options.sessionStatusAvailable
|
|
5183
|
+
})
|
|
5184
|
+
} };
|
|
5185
|
+
}
|
|
4917
5186
|
function buildTurnCollaborationMode(params, options = {}) {
|
|
4918
5187
|
const model = options.model ?? params.modelId;
|
|
4919
5188
|
return {
|
|
4920
5189
|
mode: "default",
|
|
4921
5190
|
settings: {
|
|
4922
5191
|
model,
|
|
4923
|
-
reasoning_effort:
|
|
5192
|
+
reasoning_effort: resolveCodexAppServerReasoningEffort({
|
|
5193
|
+
thinkLevel: params.thinkLevel,
|
|
5194
|
+
modelId: model,
|
|
5195
|
+
supportedReasoningEfforts: readCodexSupportedReasoningEfforts(params.model?.compat)
|
|
5196
|
+
}),
|
|
4924
5197
|
developer_instructions: buildTurnScopedCollaborationInstructions(params, options)
|
|
4925
5198
|
}
|
|
4926
5199
|
};
|
|
@@ -4958,4 +5231,4 @@ function joinPresentSections(...sections) {
|
|
|
4958
5231
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
4959
5232
|
}
|
|
4960
5233
|
//#endregion
|
|
4961
|
-
export {
|
|
5234
|
+
export { resolveCodexNativeSkillIsolation as $, readCodexMcpToolConnectorId as A, refreshCodexPluginRuntimeState as At, emitCodexNativePreToolUseFailureDiagnostic as B, buildDeveloperInstructions as C, buildCodexPluginThreadConfig as Ct, resolveCodexAppServerThreadModelSelection as D, mergeCodexThreadConfigs as Dt, resolveCodexAppServerRequestModelSelection as E, buildDisabledAppsConfigPatch as Et, assertCodexNativeHookRelayAllowed as F, attestCodexRestrictedToolSurfaceMcpServersDisabled as G, resolveCodexNativeHookRelayTtlMs as H, buildCodexNativeHookRelayConfig as I, createCodexManagedThreadStore as J, CODEX_MANAGED_THREAD_MAX_ENTRIES as K, buildCodexNativeHookRelayDisabledConfig as L, isCodexAppServerProfilerEnabled as M, isOpenAiCuratedMarketplaceName as Mt, CODEX_NATIVE_HOOK_RELAY_EVENTS as N, pluginReadParams as Nt, resolveCodexBindingModelProviderFallback as O, shouldBuildCodexPluginThreadConfig as Ot, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as P, resolveRecoverableCodexPluginConfigKeys as Pt, applyCodexNativeSkillIsolation as Q, buildCodexNativeHookRelayId as R, resolveCodexWebSearchPlan as S, buildCodexPluginAppsConfigPatchFromPolicyContext as St, resolveCodexAppServerModelProvider as T, buildCodexPluginThreadConfigTimeoutFallback as Tt, scheduleCodexNativeHookRelayUnregister as U, resolveCodexNativeHookRelayEvents as V, attestCodexPluginThreadApps as W, loadCodexNativeToolCatalog as X, hasCodexNativeToolCatalog as Y, parseCodexNativeToolCatalog as Z, buildCodexRingZeroThreadConfigPatch as _, neutralizeCodexExplicitMentionSigils as _t, readCodexSupportedReasoningEfforts as a, filterCodexDynamicTools as at, readCodexInheritedMcpServerNames as b, resolveCodexContextEngineProjectionReserveTokens as bt, assertScheduledCodexAppAuthorityRuntime as c, isMessageOnlyCodexSourceReply as ct, buildScheduledCodexAppServerConnectionIdentity as d, resolveCodexDynamicToolsLoading as dt, areCodexDynamicToolFingerprintsCompatible as et, captureScheduledCodexAppAuthority as f, resolveCodexDynamicToolsLoadingForRuntime as ft, assertCodexManagedRequirementsDoNotOverrideToolPolicy as g, fitCodexProjectedContextForTurnStart as gt, resolveScheduledCodexAppCreatorCaptureDecision as h, buildCodexContinuityCalibration as ht, buildCodexUserInput as i, fingerprintUserMcpServersConfigPatch as it, readCodexMcpToolUiVisibility as j, isOpenAiCuratedMarketplace as jt, buildCodexProjectDocThreadConfig as k, ensureCodexPluginActivation as kt, buildLegacyScheduledCodexAppRecoveryPrompt as l, isSystemAgentOnlyCodexDynamicToolAllowlist as lt, readCurrentCodexScheduledAppPolicy as m, isContextEngineBindingCompatible as mt, buildTurnCollaborationMode as n, codexLegacyDynamicToolsFingerprint as nt, resolveCodexAppServerReasoningEffort as o, filterCodexDynamicToolsForDisabledNativeSurface as ot, intersectCodexPluginThreadConfigWithScheduledAuthority as p, buildContextEngineBinding as pt, CODEX_MANAGED_THREAD_NAMESPACE as q, buildTurnStartParams as r, fingerprintJsonObject as rt, startOrResumeThread as s, isForcedPrivateQaCodexRuntime as st, buildCodexTemporalAdditionalContext as t, codexDynamicToolsFingerprint as tt, buildScheduledCodexAppAuthorityInputFingerprint as u, normalizeCodexDynamicToolName as ut, buildCodexRuntimeThreadConfig as v, projectContextEngineAssemblyForCodex as vt, CODEX_NATIVE_PERSONALITY_NONE as w, buildCodexPluginThreadConfigInputFingerprint as wt, buildCodexNativeWebSearchThreadConfig as x, resolveCodexContinuityProjectionMaxChars as xt, buildCodexThreadConfiguration as y, resolveCodexContextEngineProjectionMaxChars as yt, createCodexNativeHookRelay as z };
|