@openclaw/codex 2026.8.2 → 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 +3 -3
- package/dist/{attempt-client-cleanup-CpVfwctT.js → attempt-client-cleanup-DYHcRQAa.js} +6 -43
- package/dist/attempt-context-DmQUNWwT.js +634 -0
- package/dist/{binding-connection-CF7BmrDk.js → binding-connection-E6uDsPlx.js} +2 -2
- package/dist/{bounded-turn-DRC3hcjm.js → bounded-turn-wzJrwKKe.js} +8 -9
- package/dist/{capabilities-D6m_TdVy.js → capabilities-dIjNVCYH.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters--UToCokS.js → command-formatters-CLHhEB1Q.js} +3 -3
- package/dist/{command-handlers-CLFx2Ypa.js → command-handlers-CTGb2csu.js} +54 -55
- package/dist/{compact-5xqW1oCh.js → compact-_t0HQrUS.js} +33 -25
- package/dist/{config-CcYL77O6.js → config-BhmfC1s6.js} +12 -8
- package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
- package/dist/doctor-contract-api.js +31 -4
- package/dist/{dynamic-tools-BmeXjwNZ.js → dynamic-tools-BU9tmlYm.js} +448 -457
- package/dist/{effective-mcp-catalog-XXQKgK1U.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-B2r5vhWH.js → event-projector-usage-C5mcrGjp.js} +9 -137
- package/dist/{harness-BgeB7LnD.js → harness-DXVZ5II5.js} +13 -13
- package/dist/harness.js +1 -1
- package/dist/index.js +323 -313
- package/dist/{isolated-completion-CxTus3vZ.js → isolated-completion-kg9kv4iH.js} +4 -4
- package/dist/{launch-args-sXOAg0SX.js → launch-args-RXQwn8zV.js} +7 -1
- package/dist/{managed-binary-ZkGodW19.js → managed-binary-4-Vjz5GH.js} +14 -9
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{model-catalog-DULBcniB.js → model-catalog-CUM68Hkl.js} +11 -11
- package/dist/{models-DaWfETQk.js → models-sYnEc0gd.js} +3 -3
- package/dist/{native-subagent-monitor-vXYUZH37.js → native-subagent-monitor-CnH147jH.js} +3 -3
- package/dist/{node-exec-server.runtime-DirKVZ5h.js → node-exec-server.runtime-CstNHCmC.js} +2 -2
- package/dist/{plugin-approval-roundtrip-CCWQGlAI.js → plugin-approval-roundtrip-CNe-Xrjx.js} +29 -3
- package/dist/{plugin-marketplace-discovery-BXjIFEiw.js → plugin-marketplace-discovery-CfFlKqDl.js} +6 -6
- package/dist/{plugin-metadata-cache-DQQm4gWT.js → plugin-metadata-cache-BFrPhb2c.js} +5 -5
- package/dist/provider-capabilities-DW5p4k9w.js +101 -0
- package/dist/{rate-limits-BqHEHXQJ.js → rate-limits-CawQJfUS.js} +1 -1
- package/dist/{request-BWxweEdK.js → request-DYAI5FQE.js} +18 -6
- package/dist/{run-attempt-BVLE4Gbi.js → run-attempt-D1hJO4fX.js} +483 -1865
- package/dist/{runtime-artifact-D9pYp4zL.js → runtime-artifact-C3jVimFn.js} +19 -12
- package/dist/{session-binding-D0zinCP1.js → session-binding-BHuTCRZQ.js} +8 -10
- package/dist/{session-catalog-eMhVm0Ql.js → session-catalog-yyvTWFOw.js} +138 -116
- package/dist/{session-cli-B2lyjJA0.js → session-cli-CmFEXMAM.js} +1 -1
- package/dist/session-initialization-BeWzjGiR.js +74 -0
- package/dist/{session-permission-policy-ScnAyG90.js → session-permission-policy-DeWVTCMj.js} +2 -2
- package/dist/{session-retirement-CtUtUTr9.js → session-retirement-BujDjXVF.js} +16 -9
- package/dist/{session-upstream-marker-gqrYIRHk.js → session-upstream-marker-C351-5Qw.js} +18 -2
- package/dist/{settled-turn-context-Bk3IUwod.js → settled-turn-context-D3SNWtUn.js} +6 -28
- package/dist/{settled-turn-finalizer-BXLN9PfJ.js → settled-turn-finalizer-BVJx4yoc.js} +5 -5
- package/dist/{shared-client-B1tT5U6r.js → shared-client-DxcfF9KX.js} +2748 -101
- package/dist/{side-question-CZ-mCw-t.js → side-question-BTZAII4l.js} +97 -63
- package/dist/{thread-lifecycle-CGGKP720.js → thread-lifecycle-DAcEJ_Om.js} +2039 -1487
- package/dist/{thread-ownership-RkaoZCmQ.js → thread-ownership-zGokd8i1.js} +10 -9
- package/dist/thread-policy-g4KbJPzp.js +54 -0
- package/dist/{thread-resume-BFt1-ZDa.js → thread-resume-C7pyVKeW.js} +15 -7
- package/dist/{transcript-mirror-1ep4XjzF.js → transcript-mirror-DTUxFWa_.js} +47 -10
- package/dist/{transport-stdio-NT_Dpd9V.js → transport-stdio-CyPldczm.js} +3 -1
- package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
- package/dist/{usage-DUhd58gI.js → usage-Bj-rrzkR.js} +3 -3
- package/dist/{web-search-provider.runtime-BEmlE7ov.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
- package/openclaw.plugin.json +0 -30
- package/package.json +5 -5
- package/dist/protocol-validators-Bg5rpNxk.js +0 -2542
- package/dist/session-catalog-home-id-Dt2GiG5o.js +0 -112
- package/dist/upstream-session-fork-Dc8BFExw.js +0 -312
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { E 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
|
|
5
|
-
import {
|
|
6
|
-
import { c as
|
|
7
|
-
import {
|
|
8
|
-
import { i as
|
|
9
|
-
import { f as projectBoundedCodexThreadHistory } from "./transcript-mirror-1ep4XjzF.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";
|
|
10
9
|
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
|
|
11
10
|
import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
12
11
|
import fs from "node:fs/promises";
|
|
13
12
|
import path from "node:path";
|
|
14
13
|
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
15
14
|
import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
|
16
|
-
import { SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildHarnessVisibleReplyGuidance, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-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";
|
|
17
17
|
import * as crypto$1 from "node:crypto";
|
|
18
18
|
import crypto, { createHash } from "node:crypto";
|
|
19
19
|
import os from "node:os";
|
|
@@ -26,13 +26,12 @@ import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-util
|
|
|
26
26
|
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
27
27
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
28
28
|
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
29
|
-
import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
30
29
|
import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
31
|
-
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
32
|
-
import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
|
|
33
30
|
import { buildCodexUserMcpServersThreadConfigPatchForRun } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
34
31
|
import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
|
|
35
32
|
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-flags";
|
|
33
|
+
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
34
|
+
import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
|
|
36
35
|
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
37
36
|
/**
|
|
38
37
|
* Reads Codex plugin marketplace state and app inventory to decide which
|
|
@@ -281,25 +280,20 @@ function resolveOwnedApps(params) {
|
|
|
281
280
|
accessible: info.isAccessible,
|
|
282
281
|
enabled: info.isEnabled,
|
|
283
282
|
needsAuth: !info.isAccessible
|
|
284
|
-
},
|
|
283
|
+
}, resolveOwnedAppApprovalOverrideKeys(info));
|
|
285
284
|
}).toSorted((left, right) => left.id.localeCompare(right.id));
|
|
286
285
|
}
|
|
287
|
-
/** Returns
|
|
288
|
-
function
|
|
286
|
+
/** Returns current tool keys whose overrides could bypass the requested reviewer. */
|
|
287
|
+
function resolveOwnedAppApprovalOverrideKeys(app) {
|
|
288
|
+
if (!app.toolSummaries) return {};
|
|
289
289
|
const appName = app.name.trim();
|
|
290
290
|
const appNameLower = appName.toLowerCase();
|
|
291
|
-
const
|
|
292
|
-
const writableToolConfigKeys = new Set(tools.filter((tool) => !tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
|
|
293
|
-
appName,
|
|
294
|
-
appNameLower,
|
|
295
|
-
tool
|
|
296
|
-
})));
|
|
297
|
-
const keys = tools.filter((tool) => tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
|
|
291
|
+
const keys = app.toolSummaries.filter((tool) => !tool.isReadOnly).flatMap((tool) => resolveAppToolConfigKeys({
|
|
298
292
|
appName,
|
|
299
293
|
appNameLower,
|
|
300
294
|
tool
|
|
301
|
-
}))
|
|
302
|
-
return
|
|
295
|
+
}));
|
|
296
|
+
return { approvalOverrideToolConfigKeys: Array.from(new Set(keys)).toSorted() };
|
|
303
297
|
}
|
|
304
298
|
function resolveAppToolConfigKeys(params) {
|
|
305
299
|
const keys = [params.tool.name];
|
|
@@ -566,7 +560,7 @@ function toCodexPluginOwnedAccountApp(app) {
|
|
|
566
560
|
accessible: app.isAccessible,
|
|
567
561
|
enabled: app.isEnabled,
|
|
568
562
|
needsAuth: !app.isAccessible,
|
|
569
|
-
...
|
|
563
|
+
...resolveOwnedAppApprovalOverrideKeys(app)
|
|
570
564
|
};
|
|
571
565
|
}
|
|
572
566
|
function resolveCodexThreadConfigAppsForRecord(params) {
|
|
@@ -623,7 +617,7 @@ function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
|
|
|
623
617
|
* Builds Codex thread config patches that expose only policy-approved apps
|
|
624
618
|
* for native Codex turns.
|
|
625
619
|
*/
|
|
626
|
-
const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION =
|
|
620
|
+
const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 4;
|
|
627
621
|
const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
|
|
628
622
|
/** Returns true when plugin config exists and thread config may need app patches. */
|
|
629
623
|
function shouldBuildCodexPluginThreadConfig(pluginConfig) {
|
|
@@ -996,10 +990,11 @@ function readPersistedAppToolApprovalOverrideNames(config, app) {
|
|
|
996
990
|
const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
|
|
997
991
|
const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
|
|
998
992
|
if (!isJsonObject(tools)) return [];
|
|
999
|
-
|
|
993
|
+
const keys = app.approvalOverrideToolConfigKeys;
|
|
994
|
+
return Object.entries(tools).flatMap(([name, value]) => (!keys || keys.includes(name)) && hasPersistedToolApprovalOverride(value) ? [name] : []).toSorted();
|
|
1000
995
|
}
|
|
1001
996
|
function hasPersistedToolApprovalOverride(value) {
|
|
1002
|
-
return isJsonObject(value) && value.approval_mode !== void 0;
|
|
997
|
+
return isJsonObject(value) && value.approval_mode !== void 0 && value.approval_mode !== null;
|
|
1003
998
|
}
|
|
1004
999
|
function quoteConfigKeyPathSegment(segment) {
|
|
1005
1000
|
return `"${segment.replace(/["\\]/g, (char) => `\\${char}`)}"`;
|
|
@@ -1043,11 +1038,11 @@ function mergeJsonObjects(left, right) {
|
|
|
1043
1038
|
return merged;
|
|
1044
1039
|
}
|
|
1045
1040
|
function fingerprintJson(value) {
|
|
1046
|
-
return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
1041
|
+
return crypto.createHash("sha256").update(stableStringify$1(value)).digest("hex");
|
|
1047
1042
|
}
|
|
1048
|
-
function stableStringify(value) {
|
|
1049
|
-
if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
1050
|
-
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(",")}}`;
|
|
1051
1046
|
return JSON.stringify(value);
|
|
1052
1047
|
}
|
|
1053
1048
|
//#endregion
|
|
@@ -1828,6 +1823,65 @@ function applyCodexNativeSkillIsolation(config, isolation) {
|
|
|
1828
1823
|
};
|
|
1829
1824
|
}
|
|
1830
1825
|
//#endregion
|
|
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
|
+
}
|
|
1884
|
+
//#endregion
|
|
1831
1885
|
//#region extensions/codex/src/app-server/managed-thread-store.ts
|
|
1832
1886
|
const CODEX_MANAGED_THREAD_NAMESPACE = "app-server-managed-threads";
|
|
1833
1887
|
const CODEX_MANAGED_THREAD_MAX_ENTRIES = 2e4;
|
|
@@ -1890,11 +1944,59 @@ function createCodexManagedThreadStore(state) {
|
|
|
1890
1944
|
};
|
|
1891
1945
|
}
|
|
1892
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
|
|
1893
1983
|
//#region extensions/codex/src/app-server/plugin-thread-attestation.ts
|
|
1894
1984
|
/**
|
|
1895
1985
|
* Confirms admitted plugin and account apps against their actual Codex thread before
|
|
1896
1986
|
* OpenClaw commits a binding or starts a turn.
|
|
1897
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
|
+
}
|
|
1898
2000
|
var CodexPluginThreadAppAttestationError = class extends Error {
|
|
1899
2001
|
constructor(message, options) {
|
|
1900
2002
|
super(message, options);
|
|
@@ -1945,211 +2047,505 @@ async function discardUnattestedCodexPluginThread(params) {
|
|
|
1945
2047
|
}
|
|
1946
2048
|
}
|
|
1947
2049
|
//#endregion
|
|
1948
|
-
//#region extensions/codex/src/app-server/
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
userMcpServersFingerprint: context.userMcpServersFingerprint,
|
|
1965
|
-
mcpServersFingerprint: input.nextMcpServersFingerprint,
|
|
1966
|
-
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
1967
|
-
ringZeroConfigFingerprint: context.ringZeroConfigFingerprint,
|
|
1968
|
-
ringZeroClientInstanceId: context.ringZeroClientInstanceId,
|
|
1969
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
1970
|
-
networkProxyConfigFingerprint: context.networkProxyConfigFingerprint,
|
|
1971
|
-
nativeHookRelayGeneration: input.finalConfigPatch.nativeHookRelayGeneration,
|
|
1972
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
1973
|
-
pluginAppsFingerprint: input.pluginThreadConfig?.fingerprint,
|
|
1974
|
-
pluginAppsInputFingerprint: input.pluginThreadConfig?.inputFingerprint,
|
|
1975
|
-
pluginAppPolicyContext: input.pluginThreadConfig?.policyContext,
|
|
1976
|
-
contextEngine: context.contextEngineBinding,
|
|
1977
|
-
environmentSelectionFingerprint: context.environmentSelectionFingerprint,
|
|
1978
|
-
...!context.preserveExistingBinding ? { liveThreadConfigFingerprint: fingerprintCodexThreadConfig({
|
|
1979
|
-
...startParams,
|
|
1980
|
-
model: response.model ?? startParams.model ?? null,
|
|
1981
|
-
requestedModel: startParams.model ?? null,
|
|
1982
|
-
modelProvider: input.bindingModelProvider ?? null,
|
|
1983
|
-
requestedModelProvider: startParams.modelProvider ?? input.bindingModelProvider ?? null
|
|
1984
|
-
}, params.params.authProfileId, context.dynamicToolsFingerprint) } : {},
|
|
1985
|
-
lifecycle: {
|
|
1986
|
-
action: "started",
|
|
1987
|
-
...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();
|
|
1988
2066
|
}
|
|
1989
|
-
}
|
|
1990
|
-
|
|
2067
|
+
},
|
|
2068
|
+
clear() {
|
|
2069
|
+
for (const entry of pending) clearTimeout(entry.timeout);
|
|
2070
|
+
pending.clear();
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
1991
2073
|
//#endregion
|
|
1992
|
-
//#region extensions/codex/src/app-server/
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
"
|
|
2000
|
-
"
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
"medium",
|
|
2004
|
-
"high",
|
|
2005
|
-
"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"
|
|
2006
2085
|
];
|
|
2007
|
-
const
|
|
2008
|
-
const
|
|
2009
|
-
/**
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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;
|
|
2015
2129
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
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();
|
|
2023
2147
|
}
|
|
2024
|
-
/**
|
|
2025
|
-
function
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
2029
|
-
requested: params.thinkLevel,
|
|
2030
|
-
supportedReasoningEfforts: params.supportedReasoningEfforts
|
|
2031
|
-
}) ?? null;
|
|
2032
|
-
const modelId = params.modelId.trim().toLowerCase();
|
|
2033
|
-
if (LEGACY_PRO_MODEL_ID_RE.test(modelId)) return resolveSupportedReasoningEffort({
|
|
2034
|
-
requested: params.thinkLevel,
|
|
2035
|
-
supportedReasoningEfforts: LEGACY_PRO_REASONING_EFFORTS
|
|
2036
|
-
}) ?? null;
|
|
2037
|
-
if (params.thinkLevel === "minimal" && MODERN_GPT_5_MODEL_ID_RE.test(modelId)) return "low";
|
|
2038
|
-
if (params.thinkLevel === "minimal" || params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
|
|
2039
|
-
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);
|
|
2040
2152
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
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
|
+
});
|
|
2051
2168
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
currentModel: params.model,
|
|
2064
|
-
bindingModel: params.binding.model,
|
|
2065
|
-
bindingModelProvider: params.binding.modelProvider
|
|
2066
|
-
}) : void 0;
|
|
2067
|
-
return resolveCodexAppServerRequestModelSelection({
|
|
2068
|
-
model: params.model,
|
|
2069
|
-
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
2070
|
-
authProfileId,
|
|
2071
|
-
authProfileStore: params.authProfileStore,
|
|
2072
|
-
agentDir: params.agentDir,
|
|
2073
|
-
config: params.config
|
|
2074
|
-
});
|
|
2075
|
-
}
|
|
2076
|
-
function resolveCodexAppServerRequestModelSelection(params) {
|
|
2077
|
-
const model = params.model.trim();
|
|
2078
|
-
const modelProvider = params.modelProvider?.trim();
|
|
2079
|
-
if (modelProvider) return {
|
|
2080
|
-
model,
|
|
2081
|
-
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();
|
|
2082
2180
|
};
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
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
|
+
}
|
|
2091
2240
|
});
|
|
2241
|
+
const unregister = () => {
|
|
2242
|
+
foregroundClosed = true;
|
|
2243
|
+
rejectPendingAdmissions("native hook relay foreground closed");
|
|
2244
|
+
relay.unregister();
|
|
2245
|
+
};
|
|
2092
2246
|
return {
|
|
2093
|
-
|
|
2094
|
-
|
|
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
|
+
}
|
|
2095
2278
|
};
|
|
2096
2279
|
}
|
|
2097
|
-
function
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
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;
|
|
2101
2283
|
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
2107
|
-
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;
|
|
2108
2288
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
});
|
|
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));
|
|
2115
2294
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
const
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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)}`;
|
|
2122
2304
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
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;
|
|
2148
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;
|
|
2149
2357
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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
|
|
2153
2549
|
}).join("\n");
|
|
2154
2550
|
const delegationGuidanceAvailable = params.disableTools !== true && params.delegationCapability !== "report_only" && !isMessageOnlyCodexSourceReply(params);
|
|
2155
2551
|
const nativeDelegationAvailable = delegationGuidanceAvailable && !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) && !shouldDisableCodexToolSearchForModel(params.modelId);
|
|
@@ -2176,7 +2572,12 @@ function buildDeveloperInstructions(params, options = {}) {
|
|
|
2176
2572
|
}).join("\n") : void 0,
|
|
2177
2573
|
buildHarnessVisibleReplyGuidance({
|
|
2178
2574
|
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
|
2179
|
-
messageToolAvailable
|
|
2575
|
+
messageToolAvailable,
|
|
2576
|
+
uiPresentation: params.disableTools !== true && params.promptMode !== "minimal" && params.promptMode !== "none" ? {
|
|
2577
|
+
showWidgetToolName,
|
|
2578
|
+
dashboardToolName,
|
|
2579
|
+
portalToolName
|
|
2580
|
+
} : void 0
|
|
2180
2581
|
}),
|
|
2181
2582
|
buildCredentialSafetyPrompt(secretsToolName),
|
|
2182
2583
|
nativeCommandGuidance,
|
|
@@ -2372,8 +2773,31 @@ const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
|
2372
2773
|
"project",
|
|
2373
2774
|
"sessionFlags"
|
|
2374
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
|
+
}
|
|
2375
2800
|
function buildThreadStartParams(params, options) {
|
|
2376
|
-
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
2377
2801
|
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
2378
2802
|
provider: params.provider,
|
|
2379
2803
|
authProfileId: params.authProfileId,
|
|
@@ -2392,29 +2816,11 @@ function buildThreadStartParams(params, options) {
|
|
|
2392
2816
|
return {
|
|
2393
2817
|
model: modelSelection.model,
|
|
2394
2818
|
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
2395
|
-
|
|
2396
|
-
...options.
|
|
2397
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
2398
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2399
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2400
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2819
|
+
...buildCodexThreadConfiguration(params, options),
|
|
2820
|
+
...(options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) ? { baseInstructions: "" } : {},
|
|
2401
2821
|
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2402
2822
|
serviceName: "OpenClaw",
|
|
2403
|
-
...ringZeroActive ? { baseInstructions: "" } : {},
|
|
2404
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2405
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2406
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2407
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2408
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
2409
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2410
|
-
appServer: options.appServer,
|
|
2411
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
2412
|
-
restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
|
|
2413
|
-
shellEnvironment: options.shellEnvironment,
|
|
2414
|
-
disableLoginShell: options.disableLoginShell
|
|
2415
|
-
}),
|
|
2416
2823
|
...resolveCodexThreadEnvironmentSelection(options),
|
|
2417
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
2418
2824
|
dynamicTools: [...options.dynamicTools],
|
|
2419
2825
|
experimentalRawEvents: true,
|
|
2420
2826
|
...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
|
|
@@ -2437,8 +2843,6 @@ function buildThreadResumeParams(params, options) {
|
|
|
2437
2843
|
});
|
|
2438
2844
|
return {
|
|
2439
2845
|
threadId: options.threadId,
|
|
2440
|
-
...options.cwd ? { cwd: options.cwd } : {},
|
|
2441
|
-
...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
|
|
2442
2846
|
excludeTurns: true,
|
|
2443
2847
|
initialTurnsPage: {
|
|
2444
2848
|
limit: 1,
|
|
@@ -2449,24 +2853,8 @@ function buildThreadResumeParams(params, options) {
|
|
|
2449
2853
|
model: modelSelection.model,
|
|
2450
2854
|
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
|
|
2451
2855
|
} : {},
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2455
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2456
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2457
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2458
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2459
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2460
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2461
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
2462
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2463
|
-
appServer: options.appServer,
|
|
2464
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
2465
|
-
restrictedToolSurfaceInheritedMcpServerNames: options.restrictedToolSurfaceInheritedMcpServerNames,
|
|
2466
|
-
shellEnvironment: options.shellEnvironment,
|
|
2467
|
-
disableLoginShell: options.disableLoginShell
|
|
2468
|
-
}),
|
|
2469
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
2856
|
+
...buildCodexThreadConfiguration(params, options),
|
|
2857
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
2470
2858
|
};
|
|
2471
2859
|
}
|
|
2472
2860
|
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
@@ -2516,7 +2904,7 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
2516
2904
|
}).threadConfig;
|
|
2517
2905
|
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
2518
2906
|
return applyCodexManagedShellEnvironment({
|
|
2519
|
-
...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,
|
|
2520
2908
|
...params.bootstrapContextMode === "lightweight" ? CODEX_NO_PROJECT_DOCS_CONFIG : {}
|
|
2521
2909
|
}, options.shellEnvironment, options.disableLoginShell);
|
|
2522
2910
|
}
|
|
@@ -2527,10 +2915,14 @@ function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isH
|
|
|
2527
2915
|
...CODEX_NO_PROJECT_DOCS_CONFIG
|
|
2528
2916
|
};
|
|
2529
2917
|
}
|
|
2530
|
-
function buildRestrictedToolConfigPatch(inheritedMcpServerNames) {
|
|
2918
|
+
function buildRestrictedToolConfigPatch(inheritedMcpServerNames, scheduledAppAuthorityActive = false) {
|
|
2531
2919
|
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
2532
2920
|
return {
|
|
2533
2921
|
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
2922
|
+
...scheduledAppAuthorityActive ? {
|
|
2923
|
+
"features.apps": true,
|
|
2924
|
+
"orchestrator.mcp.enabled": true
|
|
2925
|
+
} : {},
|
|
2534
2926
|
...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
|
|
2535
2927
|
};
|
|
2536
2928
|
}
|
|
@@ -2552,58 +2944,53 @@ async function readCodexInheritedMcpServerNames(client, cwd, signal) {
|
|
|
2552
2944
|
return Object.keys(configuredServers).toSorted();
|
|
2553
2945
|
}
|
|
2554
2946
|
async function assertCodexManagedRequirementsDoNotOverrideToolPolicy(client, options, signal) {
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
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;
|
|
2559
2953
|
if (options.restrictedToolSurface) for (const key of [
|
|
2560
2954
|
"hooks",
|
|
2561
2955
|
"managedHooks",
|
|
2562
2956
|
"managed_hooks"
|
|
2563
2957
|
]) {
|
|
2564
|
-
const hooks =
|
|
2958
|
+
const hooks = requirements[key];
|
|
2565
2959
|
if (hooks === void 0 || hooks === null) continue;
|
|
2566
2960
|
if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
|
|
2567
|
-
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");
|
|
2568
2962
|
}
|
|
2569
2963
|
const additionalDeniedFeatures = new Set(options.additionalDeniedFeatures);
|
|
2570
2964
|
for (const key of ["featureRequirements", "feature_requirements"]) {
|
|
2571
|
-
const
|
|
2572
|
-
if (
|
|
2573
|
-
if (!isJsonObject(
|
|
2574
|
-
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)) {
|
|
2575
2969
|
if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
2576
2970
|
const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
|
|
2577
2971
|
const deniedByToolPolicy = options.restrictedToolSurface && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature) || additionalDeniedFeatures.has(canonicalFeature);
|
|
2972
|
+
if (canonicalFeature === "hooks" && managedHooksAllowed) continue;
|
|
2578
2973
|
if (enabled && deniedByToolPolicy) throw new Error(`Codex tool policy cannot override required feature ${feature}`);
|
|
2579
2974
|
}
|
|
2580
2975
|
}
|
|
2581
2976
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
const
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
if (observedDisabledServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
|
|
2600
|
-
observedDisabledServerNames.add(status.name);
|
|
2601
|
-
if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
|
|
2602
|
-
if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
|
|
2603
|
-
if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
|
|
2604
|
-
}
|
|
2605
|
-
for (const expectedName of expectedDisabledServerNames) if (!observedDisabledServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing server ${expectedName}`);
|
|
2606
|
-
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");
|
|
2984
|
+
}
|
|
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;
|
|
2607
2994
|
}
|
|
2608
2995
|
function hasNonEmptyJsonValue(value) {
|
|
2609
2996
|
if (value === null || value === false || value === "") return false;
|
|
@@ -2624,1179 +3011,1298 @@ function resolveCodexThreadEnvironmentSelection(options) {
|
|
|
2624
3011
|
return {};
|
|
2625
3012
|
}
|
|
2626
3013
|
//#endregion
|
|
2627
|
-
//#region extensions/codex/src/app-server/
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
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
|
+
};
|
|
2635
3047
|
}
|
|
2636
|
-
function
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
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
|
+
};
|
|
2640
3103
|
}
|
|
2641
|
-
async function
|
|
2642
|
-
const
|
|
2643
|
-
|
|
2644
|
-
let
|
|
2645
|
-
let
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
binding: resumeBinding
|
|
2652
|
-
}) ?? {
|
|
2653
|
-
configPatch: params.finalConfigPatch,
|
|
2654
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2655
|
-
};
|
|
2656
|
-
const pluginThreadConfig = context.prebuiltPluginThreadConfig ?? (params.pluginThreadConfig?.requiresCurrentPolicyCheck ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0);
|
|
2657
|
-
const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
|
|
2658
|
-
const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
|
|
2659
|
-
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2660
|
-
threadId: resumeBinding.threadId,
|
|
2661
|
-
cwd: params.cwd,
|
|
2662
|
-
authProfileId,
|
|
2663
|
-
model: startModelSelection.model,
|
|
2664
|
-
modelProvider: startModelProvider,
|
|
2665
|
-
preserveNativeModel: resumeBinding.preserveNativeModel === true,
|
|
2666
|
-
appServer: params.appServer,
|
|
2667
|
-
dynamicTools: params.dynamicTools,
|
|
2668
|
-
developerInstructions: params.developerInstructions,
|
|
2669
|
-
config: resumeConfig,
|
|
2670
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2671
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2672
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2673
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2674
|
-
hostSystemAgentActive,
|
|
2675
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
2676
|
-
shellEnvironment: params.shellEnvironment,
|
|
2677
|
-
disableLoginShell: params.disableLoginShell
|
|
2678
|
-
}));
|
|
2679
|
-
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2680
|
-
throwIfAborted();
|
|
2681
|
-
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
2682
|
-
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
2683
|
-
client: params.client,
|
|
2684
|
-
abandonClient,
|
|
2685
|
-
request: resumeParams,
|
|
2686
|
-
signal: params.signal,
|
|
2687
|
-
assertCurrent: context.assertResumeOwnership,
|
|
2688
|
-
isPrewriteOwnershipError: (error) => error instanceof CodexAdoptedThreadActiveError
|
|
2689
|
-
}));
|
|
2690
|
-
resumeResponseAccepted = true;
|
|
2691
|
-
assertCodexThreadAcceptsDirectInput(response.thread);
|
|
2692
|
-
context.assertResumeConfiguration?.();
|
|
2693
|
-
checkingLoadedPluginThreadConfig = true;
|
|
2694
|
-
const loadedPluginThreadConfig = await context.buildLoadedPluginThreadConfig?.(resumeBinding);
|
|
2695
|
-
if (loadedPluginThreadConfig && loadedPluginThreadConfig.fingerprint !== (pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint)) {
|
|
2696
|
-
checkingLoadedPluginThreadConfig = false;
|
|
2697
|
-
throw new Error("Codex thread app policy changed; a fresh thread configuration is required");
|
|
2698
|
-
}
|
|
2699
|
-
await attestCodexPluginThreadApps({
|
|
2700
|
-
client: params.client,
|
|
2701
|
-
threadId: response.thread.id,
|
|
2702
|
-
appIds: loadedPluginThreadConfig?.provisionalAppIds ?? pluginThreadConfig?.provisionalAppIds ?? [],
|
|
2703
|
-
signal: params.signal
|
|
2704
|
-
});
|
|
2705
|
-
checkingLoadedPluginThreadConfig = false;
|
|
2706
|
-
if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
|
|
2707
|
-
await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
|
|
2708
|
-
} catch (error) {
|
|
2709
|
-
context.assertResumeOwnership?.();
|
|
2710
|
-
await abandonClient();
|
|
2711
|
-
throw new CodexRestrictedToolSurfaceAttestationError(error);
|
|
2712
|
-
}
|
|
2713
|
-
throwIfAborted();
|
|
2714
|
-
const boundAuthProfileId = authProfileId;
|
|
2715
|
-
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2716
|
-
const resumePatch = {
|
|
2717
|
-
clientId: resolveCodexAppServerClientInstanceId(params.client),
|
|
2718
|
-
pendingResumeConfiguration: void 0,
|
|
2719
|
-
cwd: params.cwd,
|
|
2720
|
-
rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
|
|
2721
|
-
authProfileId: boundAuthProfileId,
|
|
2722
|
-
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
2723
|
-
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
2724
|
-
modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
2725
|
-
dynamicToolsFingerprint,
|
|
2726
|
-
dynamicToolsContainDeferred,
|
|
2727
|
-
webSearchThreadConfigFingerprint,
|
|
2728
|
-
nativeSkillIsolationFingerprint,
|
|
2729
|
-
userMcpServersFingerprint,
|
|
2730
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2731
|
-
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2732
|
-
ringZeroConfigFingerprint,
|
|
2733
|
-
ringZeroClientInstanceId,
|
|
2734
|
-
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
2735
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2736
|
-
networkProxyConfigFingerprint,
|
|
2737
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
2738
|
-
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2739
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
|
|
2740
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
|
|
2741
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
|
|
2742
|
-
contextEngine: contextEngineBinding,
|
|
2743
|
-
environmentSelectionFingerprint
|
|
2744
|
-
};
|
|
2745
|
-
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2746
|
-
kind: "patch",
|
|
2747
|
-
threadId: resumeBinding.threadId,
|
|
2748
|
-
patch: resumePatch
|
|
2749
|
-
}, context.assertResumeConfiguration))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2750
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2751
|
-
sessionId: params.params.sessionId,
|
|
2752
|
-
sessionKey: params.params.sessionKey,
|
|
2753
|
-
threadId: response.thread.id,
|
|
2754
|
-
engineId: contextEngineBinding.engineId,
|
|
2755
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2756
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2757
|
-
action: "resumed"
|
|
2758
|
-
});
|
|
2759
|
-
lifecycleTiming.mark("thread-ready");
|
|
2760
|
-
lifecycleTiming.logSummary({
|
|
2761
|
-
runId: params.params.runId,
|
|
2762
|
-
sessionId: params.params.sessionId,
|
|
2763
|
-
sessionKey: params.params.sessionKey,
|
|
2764
|
-
threadId: response.thread.id,
|
|
2765
|
-
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 } : {}
|
|
2766
3114
|
});
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
};
|
|
2784
|
-
} catch (error) {
|
|
2785
|
-
resumeReservation?.release();
|
|
2786
|
-
if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
|
|
2787
|
-
if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
|
|
2788
|
-
if (!resumeBinding.pendingResumeConfiguration) await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
|
|
2789
|
-
throw error;
|
|
2790
|
-
}
|
|
2791
|
-
if (resumeResponseAccepted) {
|
|
2792
|
-
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
2793
|
-
threadId: resumeBinding.threadId,
|
|
2794
|
-
timeoutMs: 5e3,
|
|
2795
|
-
assertCurrent: context.assertResumeOwnership
|
|
2796
|
-
}).catch(() => false)) {
|
|
2797
|
-
try {
|
|
2798
|
-
await abandonClient();
|
|
2799
|
-
} catch (abandonError) {
|
|
2800
|
-
throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume client could not be retired", { cause: abandonError });
|
|
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);
|
|
2801
3131
|
}
|
|
2802
|
-
throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
2803
3132
|
}
|
|
2804
3133
|
}
|
|
2805
|
-
if (
|
|
2806
|
-
|
|
2807
|
-
|
|
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);
|
|
2808
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
|
+
};
|
|
2809
3153
|
}
|
|
2810
|
-
|
|
2811
|
-
const
|
|
2812
|
-
const
|
|
2813
|
-
const
|
|
2814
|
-
const
|
|
2815
|
-
|
|
2816
|
-
|
|
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
|
|
2817
3167
|
};
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
hostSystemAgentActive,
|
|
2833
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
2834
|
-
shellEnvironment: params.shellEnvironment,
|
|
2835
|
-
disableLoginShell: params.disableLoginShell
|
|
2836
|
-
}));
|
|
2837
|
-
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2838
|
-
const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2839
|
-
try {
|
|
2840
|
-
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2841
|
-
} catch (error) {
|
|
2842
|
-
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2843
|
-
throw error;
|
|
2844
|
-
}
|
|
2845
|
-
});
|
|
2846
|
-
const response = assertCodexThreadStartResponse(threadStartResponse);
|
|
2847
|
-
const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
|
|
2848
|
-
if (provisionalAppIds?.length) try {
|
|
2849
|
-
await lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
|
|
2850
|
-
client: params.client,
|
|
2851
|
-
threadId: response.thread.id,
|
|
2852
|
-
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,
|
|
2853
3182
|
signal: params.signal
|
|
2854
|
-
})
|
|
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
|
+
});
|
|
2855
3215
|
} catch (error) {
|
|
2856
|
-
if (!
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
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
|
|
2863
3236
|
}
|
|
2864
|
-
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
var CodexScheduledAppAuthorityCaptureTimeoutError = class extends Error {
|
|
3240
|
+
constructor() {
|
|
3241
|
+
super("Codex scheduled app authority capture deadline elapsed");
|
|
3242
|
+
this.name = "CodexScheduledAppAuthorityCaptureTimeoutError";
|
|
2865
3243
|
}
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
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.`);
|
|
2872
3285
|
}
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
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
|
+
};
|
|
2887
3297
|
}
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
const
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
modelProvider: bindingModelProvider,
|
|
2907
|
-
dynamicToolsFingerprint,
|
|
2908
|
-
dynamicToolsContainDeferred,
|
|
2909
|
-
webSearchThreadConfigFingerprint,
|
|
2910
|
-
nativeSkillIsolationFingerprint,
|
|
2911
|
-
userMcpServersFingerprint,
|
|
2912
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2913
|
-
configuredMcpOwnershipVersion: params.configuredMcpOwnershipVersion,
|
|
2914
|
-
ringZeroConfigFingerprint,
|
|
2915
|
-
ringZeroClientInstanceId,
|
|
2916
|
-
nativeToolPolicyRestricted: restrictedToolSurface ? true : void 0,
|
|
2917
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2918
|
-
networkProxyConfigFingerprint,
|
|
2919
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2920
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2921
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2922
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2923
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2924
|
-
contextEngine: contextEngineBinding,
|
|
2925
|
-
environmentSelectionFingerprint
|
|
2926
|
-
};
|
|
2927
|
-
const cleanupUncommittedSuccessor = async (cause) => {
|
|
2928
|
-
if (!await discardUnattestedCodexPluginThread({
|
|
2929
|
-
client: params.client,
|
|
2930
|
-
threadId: response.thread.id,
|
|
2931
|
-
ephemeral: startParams.ephemeral === true
|
|
2932
|
-
})) {
|
|
2933
|
-
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2934
|
-
throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
|
|
2935
|
-
}
|
|
2936
|
-
};
|
|
2937
|
-
const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
|
|
2938
|
-
await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
|
|
2939
|
-
sourceHomeId: managedSourceHomeId,
|
|
2940
|
-
threadId: response.thread.id,
|
|
2941
|
-
...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
|
+
}];
|
|
2942
3316
|
}));
|
|
2943
|
-
let committed;
|
|
2944
|
-
try {
|
|
2945
|
-
committed = await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, replacementPredecessor ? {
|
|
2946
|
-
kind: "replace-thread",
|
|
2947
|
-
expectedThreadId: replacementPredecessor.threadId,
|
|
2948
|
-
binding: nextBinding
|
|
2949
|
-
} : {
|
|
2950
|
-
kind: "set",
|
|
2951
|
-
if: { kind: "absent" },
|
|
2952
|
-
binding: nextBinding
|
|
2953
|
-
}));
|
|
2954
|
-
} catch (error) {
|
|
2955
|
-
if (replacementPredecessor) await cleanupUncommittedSuccessor(error);
|
|
2956
|
-
throw error;
|
|
2957
|
-
}
|
|
2958
|
-
if (!committed) {
|
|
2959
|
-
if (replacementPredecessor) await cleanupUncommittedSuccessor();
|
|
2960
|
-
throw new CodexThreadBindingConflictError(replacementPredecessor?.threadId ?? response.thread.id, "committing a fresh thread");
|
|
2961
|
-
}
|
|
2962
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2963
|
-
sessionId: params.params.sessionId,
|
|
2964
|
-
sessionKey: params.params.sessionKey,
|
|
2965
|
-
threadId: response.thread.id,
|
|
2966
|
-
engineId: contextEngineBinding.engineId,
|
|
2967
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2968
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2969
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2970
|
-
});
|
|
2971
3317
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
});
|
|
2980
|
-
return
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
pluginThreadConfig,
|
|
2988
|
-
response,
|
|
2989
|
-
rolloutPath,
|
|
2990
|
-
startModelProvider: requestModelProvider ?? startModelProvider,
|
|
2991
|
-
startParams,
|
|
2992
|
-
modelProvider
|
|
2993
|
-
});
|
|
2994
|
-
}
|
|
2995
|
-
//#endregion
|
|
2996
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
|
|
2997
|
-
/** Preserves the caller's abort reason across thread ownership transitions. */
|
|
2998
|
-
function throwIfCodexThreadLifecycleAborted(signal) {
|
|
2999
|
-
if (!signal?.aborted) return;
|
|
3000
|
-
const reason = signal.reason;
|
|
3001
|
-
if (reason instanceof Error) throw reason;
|
|
3002
|
-
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
3003
|
-
error.name = "AbortError";
|
|
3004
|
-
throw error;
|
|
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
|
|
3332
|
+
};
|
|
3005
3333
|
}
|
|
3006
|
-
/**
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
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;
|
|
3346
|
+
}
|
|
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");
|
|
3014
3364
|
}
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3365
|
+
//#endregion
|
|
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);
|
|
3019
3373
|
}
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
return await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => releaseCodexAppServerLiveThread(options.client, options.threadId, options.assertCurrent));
|
|
3024
|
-
} catch (error) {
|
|
3025
|
-
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
3026
|
-
return await abandonCodexLiveThreadRelease(options, error);
|
|
3027
|
-
}
|
|
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}`;
|
|
3028
3377
|
}
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
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() {}
|
|
3038
3389
|
};
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
};
|
|
3051
|
-
const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0);
|
|
3052
|
-
const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
|
|
3053
|
-
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
|
|
3054
|
-
const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
3055
|
-
threadId: binding.threadId,
|
|
3056
|
-
cwd: params.cwd,
|
|
3057
|
-
authProfileId: resumeAuthProfileId,
|
|
3058
|
-
model: startModelSelection.model,
|
|
3059
|
-
modelProvider: startModelProvider,
|
|
3060
|
-
preserveNativeModel: false,
|
|
3061
|
-
appServer: params.appServer,
|
|
3062
|
-
dynamicTools: params.dynamicTools,
|
|
3063
|
-
developerInstructions: params.developerInstructions,
|
|
3064
|
-
config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
|
|
3065
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
3066
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3067
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
3068
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
3069
|
-
hostSystemAgentActive,
|
|
3070
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3071
|
-
shellEnvironment: params.shellEnvironment,
|
|
3072
|
-
disableLoginShell: params.disableLoginShell
|
|
3073
|
-
}));
|
|
3074
|
-
const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
|
|
3075
|
-
...resumeParams,
|
|
3076
|
-
model: binding.model ?? resumeParams.model ?? null,
|
|
3077
|
-
requestedModel: resumeParams.model ?? null,
|
|
3078
|
-
modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
|
|
3079
|
-
requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
|
|
3080
|
-
}, resumeAuthProfileId, dynamicToolsFingerprint);
|
|
3081
|
-
if (retainedThread.configFingerprint !== liveThreadConfigFingerprint) return {
|
|
3082
|
-
kind: "resume",
|
|
3083
|
-
prebuiltFinalConfigPatch
|
|
3084
|
-
};
|
|
3085
|
-
await attestCodexPluginThreadApps({
|
|
3086
|
-
client: params.client,
|
|
3087
|
-
threadId: binding.threadId,
|
|
3088
|
-
appIds: pluginThreadConfig?.provisionalAppIds ?? [],
|
|
3089
|
-
signal: params.signal
|
|
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)
|
|
3090
3401
|
});
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3402
|
+
};
|
|
3403
|
+
const snapshot = () => ({
|
|
3404
|
+
totalMs: toMs(now() - startedAt),
|
|
3405
|
+
spans: spans.slice()
|
|
3406
|
+
});
|
|
3407
|
+
return {
|
|
3408
|
+
async measure(name, run) {
|
|
3409
|
+
const spanStartedAt = now();
|
|
3410
|
+
try {
|
|
3411
|
+
return await run();
|
|
3412
|
+
} finally {
|
|
3413
|
+
record(name, spanStartedAt);
|
|
3102
3414
|
}
|
|
3103
|
-
},
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
threadId: binding.threadId,
|
|
3111
|
-
action: "resumed"
|
|
3112
|
-
});
|
|
3113
|
-
ownershipTransferred = true;
|
|
3114
|
-
return {
|
|
3115
|
-
kind: "ready",
|
|
3116
|
-
binding: {
|
|
3117
|
-
...binding,
|
|
3118
|
-
cwd: params.cwd,
|
|
3119
|
-
model,
|
|
3120
|
-
nativeHookRelayGeneration,
|
|
3121
|
-
environmentSelectionFingerprint,
|
|
3122
|
-
liveThreadConfigFingerprint,
|
|
3123
|
-
liveThreadOwnership: retainedThread,
|
|
3124
|
-
...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
|
|
3125
|
-
lifecycle: { action: "resumed" }
|
|
3415
|
+
},
|
|
3416
|
+
measureSync(name, run) {
|
|
3417
|
+
const spanStartedAt = now();
|
|
3418
|
+
try {
|
|
3419
|
+
return run();
|
|
3420
|
+
} finally {
|
|
3421
|
+
record(name, spanStartedAt);
|
|
3126
3422
|
}
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
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);
|
|
3138
3451
|
}
|
|
3139
|
-
}
|
|
3452
|
+
};
|
|
3140
3453
|
}
|
|
3141
3454
|
//#endregion
|
|
3142
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
threadId,
|
|
3147
|
-
includeTurns: false
|
|
3148
|
-
}, { signal: params.signal }));
|
|
3149
|
-
context.throwIfAborted();
|
|
3150
|
-
assertCodexThreadAcceptsDirectInput(thread);
|
|
3151
|
-
if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
3152
|
-
}
|
|
3153
|
-
/** Preserve attach's native-queue-before-binding-lease order when consuming pending intent. */
|
|
3154
|
-
async function withCodexThreadLifecycleBinding(params, run) {
|
|
3155
|
-
const identity = sessionBindingIdentity({
|
|
3156
|
-
sessionId: params.params.sessionId,
|
|
3455
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
|
|
3456
|
+
function resolveCodexThreadAgentDir(params) {
|
|
3457
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
3458
|
+
config: params.params.config,
|
|
3157
3459
|
sessionKey: params.params.sessionKey,
|
|
3158
|
-
agentId: params.agentId ?? params.params.agentId
|
|
3159
|
-
|
|
3160
|
-
});
|
|
3161
|
-
const snapshot = await params.bindingStore.read(identity);
|
|
3162
|
-
const pendingThreadId = snapshot?.pendingResumeConfiguration ? snapshot.threadId : void 0;
|
|
3163
|
-
const runWithLease = () => params.bindingStore.withLease(identity, async () => {
|
|
3164
|
-
const binding = await params.bindingStore.read(identity);
|
|
3165
|
-
if (pendingThreadId && (binding?.threadId !== pendingThreadId || !binding.pendingResumeConfiguration)) throw new CodexThreadBindingConflictError(pendingThreadId, "acquiring a pending resume configuration");
|
|
3166
|
-
if (!pendingThreadId && binding?.pendingResumeConfiguration) throw new CodexThreadBindingConflictError(binding.threadId, "acquiring a pending resume configuration");
|
|
3167
|
-
return await run(identity, binding);
|
|
3168
|
-
});
|
|
3169
|
-
return pendingThreadId ? await withExclusiveCodexAppServerThread({
|
|
3170
|
-
bindingStore: params.bindingStore,
|
|
3171
|
-
identity,
|
|
3172
|
-
threadId: pendingThreadId,
|
|
3173
|
-
run: runWithLease
|
|
3174
|
-
}) : await runWithLease();
|
|
3460
|
+
agentId: params.agentId ?? params.params.agentId
|
|
3461
|
+
}).sessionAgentId;
|
|
3462
|
+
return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
|
|
3175
3463
|
}
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
})) 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.`);
|
|
3183
|
-
const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
3184
|
-
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
3185
|
-
const configuration = await preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint, async (assertCurrent) => {
|
|
3186
|
-
const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
|
|
3187
|
-
assertCurrent();
|
|
3188
|
-
if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
|
|
3189
|
-
client: params.client,
|
|
3190
|
-
abandonClient: params.abandonClient,
|
|
3191
|
-
lifecycleTiming,
|
|
3192
|
-
threadId: binding.threadId,
|
|
3193
|
-
assertCurrent
|
|
3194
|
-
});
|
|
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
|
|
3195
3470
|
});
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
assertResumeConfiguration: configuration.assertConfigured,
|
|
3201
|
-
assertResumeOwnership: configuration.assertCurrent
|
|
3202
|
-
});
|
|
3203
|
-
if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
|
|
3204
|
-
return resumed;
|
|
3205
|
-
} finally {
|
|
3206
|
-
configuration.dispose();
|
|
3207
|
-
}
|
|
3208
|
-
}
|
|
3209
|
-
/** Manual attachment is intent, never evidence that loaded native overrides took effect. */
|
|
3210
|
-
async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
|
|
3211
|
-
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.`);
|
|
3212
|
-
const agentDir = resolveCodexThreadAgentDir(params);
|
|
3213
|
-
const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
|
|
3214
|
-
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");
|
|
3215
|
-
if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw fail("the thread is claimed by active work; stop that run before resuming");
|
|
3216
|
-
const assertCurrent = captureExclusiveSharedCodexAppServerClient(params.client);
|
|
3217
|
-
const { thread } = await params.client.request("thread/read", {
|
|
3218
|
-
threadId: binding.threadId,
|
|
3219
|
-
includeTurns: false
|
|
3220
|
-
}, { signal: params.signal });
|
|
3221
|
-
assertCurrent();
|
|
3222
|
-
const statusType = thread.status?.type;
|
|
3223
|
-
if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
|
|
3224
|
-
assertCodexThreadAcceptsDirectInput(thread);
|
|
3225
|
-
let unloaded = statusType === "notLoaded";
|
|
3226
|
-
const dispose = params.client.addNotificationHandler((notification) => {
|
|
3227
|
-
if (notification.method === "thread/status/changed" && isJsonObject(notification.params) && notification.params.threadId === binding.threadId && isJsonObject(notification.params.status) && notification.params.status.type === "notLoaded") unloaded = true;
|
|
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)
|
|
3228
3475
|
});
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
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
|
+
})
|
|
3498
|
+
});
|
|
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;
|
|
3533
|
+
return {
|
|
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
|
|
3553
|
+
};
|
|
3250
3554
|
}
|
|
3251
3555
|
//#endregion
|
|
3252
|
-
//#region extensions/codex/src/app-server/
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
pending.add(entry);
|
|
3258
|
-
},
|
|
3259
|
-
delete(entry) {
|
|
3260
|
-
return pending.delete(entry);
|
|
3261
|
-
},
|
|
3262
|
-
flush() {
|
|
3263
|
-
while (pending.size > 0) {
|
|
3264
|
-
const entry = pending.values().next().value;
|
|
3265
|
-
if (!entry) return;
|
|
3266
|
-
clearTimeout(entry.timeout);
|
|
3267
|
-
entry.unregister();
|
|
3268
|
-
}
|
|
3269
|
-
},
|
|
3270
|
-
clear() {
|
|
3271
|
-
for (const entry of pending) clearTimeout(entry.timeout);
|
|
3272
|
-
pending.clear();
|
|
3273
|
-
}
|
|
3274
|
-
};
|
|
3275
|
-
//#endregion
|
|
3276
|
-
//#region extensions/codex/src/app-server/native-hook-relay.ts
|
|
3277
|
-
/**
|
|
3278
|
-
* Bridges Codex native hook callbacks into OpenClaw's native hook relay so
|
|
3279
|
-
* app-server tool events can still run OpenClaw policy and diagnostics.
|
|
3280
|
-
*/
|
|
3281
|
-
/** Codex hook events that can be registered through OpenClaw's native relay. */
|
|
3282
|
-
const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
|
|
3283
|
-
"pre_tool_use",
|
|
3284
|
-
"post_tool_use",
|
|
3285
|
-
"permission_request",
|
|
3286
|
-
"before_agent_finalize"
|
|
3287
|
-
];
|
|
3288
|
-
const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
|
|
3289
|
-
const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 18e5;
|
|
3290
|
-
/** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
|
|
3291
|
-
const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 3e5;
|
|
3292
|
-
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
|
|
3293
|
-
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
|
|
3294
|
-
const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
|
|
3295
|
-
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
|
|
3296
|
-
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
|
|
3297
|
-
const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
|
|
3298
|
-
const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
|
|
3299
|
-
const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
|
|
3300
|
-
exec: [
|
|
3301
|
-
"Bash",
|
|
3302
|
-
"exec",
|
|
3303
|
-
"exec_command"
|
|
3304
|
-
],
|
|
3305
|
-
apply_patch: [
|
|
3306
|
-
"apply_patch",
|
|
3307
|
-
"Write",
|
|
3308
|
-
"Edit"
|
|
3309
|
-
],
|
|
3310
|
-
spawn_agent: ["spawn_agent", "Agent"]
|
|
3311
|
-
};
|
|
3312
|
-
/** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
|
|
3313
|
-
async function assertCodexNativeHookRelayAllowed(client, signal) {
|
|
3314
|
-
let attestation = nativeHookPolicyByClient.get(client);
|
|
3315
|
-
if (!attestation) {
|
|
3316
|
-
attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
|
|
3317
|
-
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
|
|
3318
|
-
const requirements = response.requirements;
|
|
3319
|
-
if (requirements === null) return;
|
|
3320
|
-
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
|
|
3321
|
-
const managedOnly = requirements.allowManagedHooksOnly;
|
|
3322
|
-
if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
|
|
3323
|
-
if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
|
|
3324
|
-
});
|
|
3325
|
-
nativeHookPolicyByClient.set(client, attestation);
|
|
3326
|
-
attestation.catch(() => {
|
|
3327
|
-
if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
|
|
3328
|
-
});
|
|
3329
|
-
}
|
|
3330
|
-
await attestation;
|
|
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;
|
|
3331
3561
|
}
|
|
3332
|
-
/**
|
|
3333
|
-
function
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
params.
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3562
|
+
/** Materializes the public lifecycle result after a fresh thread is durably committed. */
|
|
3563
|
+
function buildStartedCodexThreadBinding(input) {
|
|
3564
|
+
const { context, params, response, startParams } = input;
|
|
3565
|
+
return {
|
|
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
|
+
}
|
|
3346
3603
|
};
|
|
3347
|
-
nativeHookRelayUnregisterQueue.add(pending);
|
|
3348
|
-
timeout.unref();
|
|
3349
3604
|
}
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
toolName: params.failure.toolName,
|
|
3364
|
-
toolCallId: params.failure.toolCallId,
|
|
3365
|
-
durationMs: params.failure.durationMs,
|
|
3366
|
-
errorCategory: "before_tool_call",
|
|
3367
|
-
terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
|
|
3368
|
-
...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
|
|
3369
|
-
});
|
|
3370
|
-
}
|
|
3371
|
-
/** Registers an OpenClaw native hook relay for a Codex app-server turn. */
|
|
3372
|
-
function createCodexNativeHookRelay(params) {
|
|
3373
|
-
if (params.options?.enabled === false) return;
|
|
3374
|
-
const directChildClaims = /* @__PURE__ */ new Map();
|
|
3375
|
-
const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
|
|
3376
|
-
let foregroundClosed = false;
|
|
3377
|
-
let successfulYieldRetentionAuthorized = false;
|
|
3378
|
-
const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
|
|
3379
|
-
const rejectPendingAdmissions = (reason) => {
|
|
3380
|
-
for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
|
|
3381
|
-
pendingDirectChildAdmissions.clear();
|
|
3605
|
+
//#endregion
|
|
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();
|
|
3382
3618
|
};
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
})
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
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
|
|
3680
|
+
});
|
|
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 } : {}
|
|
3433
3759
|
}
|
|
3434
|
-
}
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
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
|
+
}
|
|
3778
|
+
}
|
|
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;
|
|
3439
3821
|
}
|
|
3440
3822
|
});
|
|
3441
|
-
const
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3823
|
+
const response = assertCodexThreadStartResponse(threadStartResponse);
|
|
3824
|
+
const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
|
|
3825
|
+
const assertCurrent = () => {
|
|
3826
|
+
throwIfAborted();
|
|
3827
|
+
params.params.hostCapabilities.assertActive();
|
|
3445
3828
|
};
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
const threadId = threadIdInput.trim();
|
|
3455
|
-
const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
|
|
3456
|
-
if (!pending) return;
|
|
3457
|
-
pendingDirectChildAdmissions.delete(threadId);
|
|
3458
|
-
pending.reject(new Error(reason));
|
|
3459
|
-
},
|
|
3460
|
-
claimDirectChild: (threadIdInput) => {
|
|
3461
|
-
const threadId = threadIdInput.trim();
|
|
3462
|
-
if (!threadId) return () => void 0;
|
|
3463
|
-
if (directChildClaims.get(threadId)) return () => void 0;
|
|
3464
|
-
const claim = Symbol(threadId);
|
|
3465
|
-
directChildClaims.set(threadId, claim);
|
|
3466
|
-
const pending = pendingDirectChildAdmissions.get(threadId);
|
|
3467
|
-
pendingDirectChildAdmissions.delete(threadId);
|
|
3468
|
-
pending?.resolve(claim);
|
|
3469
|
-
let released = false;
|
|
3470
|
-
return () => {
|
|
3471
|
-
if (released) return;
|
|
3472
|
-
released = true;
|
|
3473
|
-
if (directChildClaims.get(threadId) !== claim) return;
|
|
3474
|
-
directChildClaims.delete(threadId);
|
|
3475
|
-
if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
|
|
3476
|
-
};
|
|
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 });
|
|
3477
3837
|
}
|
|
3838
|
+
throw cause;
|
|
3478
3839
|
};
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
const
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
|
|
3534
|
-
const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
|
|
3535
|
-
const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
|
|
3536
|
-
config[`hooks.${codexEvent}`] = [{
|
|
3537
|
-
...matcher ? { matcher } : {},
|
|
3538
|
-
hooks: [{
|
|
3539
|
-
type: "command",
|
|
3540
|
-
command,
|
|
3541
|
-
timeout,
|
|
3542
|
-
async: false,
|
|
3543
|
-
statusMessage: "OpenClaw native hook relay"
|
|
3544
|
-
}]
|
|
3545
|
-
}];
|
|
3546
|
-
const state = {
|
|
3547
|
-
enabled: true,
|
|
3548
|
-
trusted_hash: codexCommandHookTrustedHash({
|
|
3549
|
-
event,
|
|
3550
|
-
command,
|
|
3551
|
-
matcher,
|
|
3552
|
-
timeout,
|
|
3553
|
-
statusMessage: "OpenClaw native hook relay"
|
|
3554
|
-
})
|
|
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
|
|
3862
|
+
});
|
|
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
|
|
3555
3894
|
};
|
|
3556
|
-
|
|
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
|
+
});
|
|
3557
3925
|
}
|
|
3558
|
-
|
|
3559
|
-
|
|
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"
|
|
3933
|
+
});
|
|
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
|
|
3947
|
+
});
|
|
3560
3948
|
}
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3949
|
+
//#endregion
|
|
3950
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
|
|
3951
|
+
/** Preserves the caller's abort reason across thread ownership transitions. */
|
|
3952
|
+
function throwIfCodexThreadLifecycleAborted(signal) {
|
|
3953
|
+
if (!signal?.aborted) return;
|
|
3954
|
+
const reason = signal.reason;
|
|
3955
|
+
if (reason instanceof Error) throw reason;
|
|
3956
|
+
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
3957
|
+
error.name = "AbortError";
|
|
3958
|
+
throw error;
|
|
3570
3959
|
}
|
|
3571
|
-
|
|
3572
|
-
|
|
3960
|
+
/** Releases consumed subscription ownership or retires an unsafe client. */
|
|
3961
|
+
async function releaseCodexConsumedLiveThread(options) {
|
|
3962
|
+
if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
|
|
3963
|
+
threadId: options.threadId,
|
|
3964
|
+
timeoutMs: 5e3,
|
|
3965
|
+
assertCurrent: options.assertCurrent
|
|
3966
|
+
}))) return;
|
|
3967
|
+
return await abandonCodexLiveThreadRelease(options, options.cause);
|
|
3968
|
+
}
|
|
3969
|
+
async function abandonCodexLiveThreadRelease(options, cause) {
|
|
3970
|
+
options.assertCurrent?.();
|
|
3971
|
+
await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
|
|
3972
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, cause !== void 0 ? { cause } : void 0);
|
|
3573
3973
|
}
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3974
|
+
/** Releases through the retained owner, preserving its guarded callback and rollback. */
|
|
3975
|
+
async function releaseCodexRetainedLiveThread(options) {
|
|
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
|
+
}
|
|
3578
3982
|
}
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
const
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
const
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
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();
|
|
3590
4003
|
}
|
|
3591
|
-
const sortedNames = Array.from(nativeNames).toSorted();
|
|
3592
|
-
if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
|
|
3593
|
-
return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
|
|
3594
4004
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
4005
|
+
/** Reuses one safely owned, fully matching subscription on its original client. */
|
|
4006
|
+
async function tryReuseCodexLiveThread(options) {
|
|
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" };
|
|
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
|
+
}
|
|
3606
4038
|
};
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
4039
|
+
let ownershipTransferred = false;
|
|
4040
|
+
let preserveSubscription = false;
|
|
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();
|
|
4105
|
+
const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
|
|
4106
|
+
const model = startModelSelection.model;
|
|
4107
|
+
if (!(incognito || await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
4108
|
+
kind: "patch",
|
|
4109
|
+
threadId: binding.threadId,
|
|
4110
|
+
patch: {
|
|
4111
|
+
cwd: params.cwd,
|
|
4112
|
+
model,
|
|
4113
|
+
nativeHookRelayGeneration,
|
|
4114
|
+
environmentSelectionFingerprint
|
|
4115
|
+
}
|
|
4116
|
+
}, assertWarmOwner)))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
|
|
4117
|
+
assertWarmOwner();
|
|
4118
|
+
lifecycleTiming.mark("thread-ready");
|
|
4119
|
+
lifecycleTiming.logSummary({
|
|
4120
|
+
runId: params.params.runId,
|
|
4121
|
+
sessionId: params.params.sessionId,
|
|
4122
|
+
sessionKey: params.params.sessionKey,
|
|
4123
|
+
threadId: binding.threadId,
|
|
4124
|
+
action: "resumed"
|
|
4125
|
+
});
|
|
4126
|
+
ownershipTransferred = true;
|
|
4127
|
+
return {
|
|
4128
|
+
kind: "ready",
|
|
4129
|
+
binding: {
|
|
4130
|
+
...binding,
|
|
4131
|
+
...!incognito ? {
|
|
4132
|
+
cwd: params.cwd,
|
|
4133
|
+
model,
|
|
4134
|
+
nativeHookRelayGeneration,
|
|
4135
|
+
environmentSelectionFingerprint
|
|
4136
|
+
} : {},
|
|
4137
|
+
liveThreadConfigFingerprint,
|
|
4138
|
+
liveThreadEphemeralPolicy: retainedThread.ephemeralPolicy,
|
|
4139
|
+
liveThreadOwnership: retainedThread,
|
|
4140
|
+
...!incognito && retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
|
|
4141
|
+
lifecycle: { action: "resumed" }
|
|
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
|
+
}
|
|
3615
4162
|
}
|
|
3616
4163
|
//#endregion
|
|
3617
|
-
//#region extensions/codex/src/app-server/
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
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;
|
|
3622
4176
|
}
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
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();
|
|
3631
4199
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
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
|
|
4271
|
+
};
|
|
4272
|
+
} catch (error) {
|
|
4273
|
+
dispose();
|
|
4274
|
+
throw error;
|
|
4275
|
+
}
|
|
3635
4276
|
}
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
},
|
|
3645
|
-
mark() {},
|
|
3646
|
-
logSummary() {}
|
|
3647
|
-
};
|
|
3648
|
-
const now = options.now ?? Date.now;
|
|
3649
|
-
const startedAt = now();
|
|
3650
|
-
let didLog = false;
|
|
3651
|
-
const spans = [];
|
|
3652
|
-
const toMs = (value) => Math.max(0, Math.round(value));
|
|
3653
|
-
const record = (name, spanStartedAt) => {
|
|
3654
|
-
const currentAt = now();
|
|
3655
|
-
spans.push({
|
|
3656
|
-
name,
|
|
3657
|
-
durationMs: toMs(currentAt - spanStartedAt),
|
|
3658
|
-
elapsedMs: toMs(currentAt - startedAt)
|
|
3659
|
-
});
|
|
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();
|
|
3660
4285
|
};
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
4286
|
+
assertCurrent();
|
|
4287
|
+
const thread = await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, context);
|
|
4288
|
+
assertCurrent();
|
|
4289
|
+
assertCodexSupervisionThreadLineage(binding, thread);
|
|
3665
4290
|
return {
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
try {
|
|
3669
|
-
return await run();
|
|
3670
|
-
} finally {
|
|
3671
|
-
record(name, spanStartedAt);
|
|
3672
|
-
}
|
|
3673
|
-
},
|
|
3674
|
-
measureSync(name, run) {
|
|
3675
|
-
const spanStartedAt = now();
|
|
3676
|
-
try {
|
|
3677
|
-
return run();
|
|
3678
|
-
} finally {
|
|
3679
|
-
record(name, spanStartedAt);
|
|
3680
|
-
}
|
|
3681
|
-
},
|
|
3682
|
-
mark(name) {
|
|
3683
|
-
record(name, now());
|
|
3684
|
-
},
|
|
3685
|
-
logSummary(params) {
|
|
3686
|
-
if (didLog) return;
|
|
3687
|
-
const summary = snapshot();
|
|
3688
|
-
const shouldWarn = shouldWarnCodexThreadLifecycleTimingSummary(summary, options);
|
|
3689
|
-
if (!shouldWarn && !log.isEnabled?.("trace")) return;
|
|
3690
|
-
didLog = true;
|
|
3691
|
-
const message = formatCodexThreadLifecycleTimingSummary({
|
|
3692
|
-
runId: params.runId,
|
|
3693
|
-
sessionId: params.sessionId,
|
|
3694
|
-
sessionKey: params.sessionKey,
|
|
3695
|
-
action: params.action,
|
|
3696
|
-
summary
|
|
3697
|
-
});
|
|
3698
|
-
const meta = {
|
|
3699
|
-
runId: params.runId,
|
|
3700
|
-
sessionId: params.sessionId,
|
|
3701
|
-
sessionKey: params.sessionKey,
|
|
3702
|
-
action: params.action,
|
|
3703
|
-
threadId: params.threadId,
|
|
3704
|
-
totalMs: summary.totalMs,
|
|
3705
|
-
spans: summary.spans
|
|
3706
|
-
};
|
|
3707
|
-
if (shouldWarn) log.warn(message, meta);
|
|
3708
|
-
else log.trace(message, meta);
|
|
3709
|
-
}
|
|
4291
|
+
...observeCodexThreadConfiguration(params, thread, assertCurrent),
|
|
4292
|
+
assertCurrent
|
|
3710
4293
|
};
|
|
3711
4294
|
}
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
await assertCodexModelBackedReviewerEffectiveConfig({
|
|
3716
|
-
client: params.client,
|
|
3717
|
-
approvalsReviewer: params.appServer.approvalsReviewer,
|
|
3718
|
-
cwd: params.cwd,
|
|
3719
|
-
signal: params.signal
|
|
3720
|
-
});
|
|
3721
|
-
if (params.nativeHookRelayRequired) await assertCodexNativeHookRelayAllowed(params.client, params.signal);
|
|
3722
|
-
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
3723
|
-
...params.timing,
|
|
3724
|
-
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
3725
|
-
});
|
|
3726
|
-
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
3727
|
-
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
3728
|
-
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
3729
|
-
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
3730
|
-
config: params.params.config,
|
|
3731
|
-
disableTools: params.params.disableTools,
|
|
3732
|
-
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
3733
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3734
|
-
webSearchAllowed: params.webSearchAllowed
|
|
3735
|
-
})).threadConfig);
|
|
3736
|
-
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
3737
|
-
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
3738
|
-
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRun({
|
|
3739
|
-
run: params.params,
|
|
3740
|
-
cwd: params.cwd,
|
|
3741
|
-
agentId: params.agentId ?? params.params.agentId,
|
|
3742
|
-
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
3743
|
-
warn: (message) => embeddedAgentLog.warn(message),
|
|
3744
|
-
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
3745
|
-
serverName,
|
|
3746
|
-
error: formatErrorMessage(error)
|
|
3747
|
-
})
|
|
3748
|
-
});
|
|
3749
|
-
const nativeSkillIsolation = await lifecycleTiming.measure("native-skill-isolation", () => resolveCodexNativeSkillIsolation({
|
|
3750
|
-
client: params.client,
|
|
3751
|
-
codexHome: params.appServer.start.env?.CODEX_HOME,
|
|
3752
|
-
cwd: params.cwd,
|
|
3753
|
-
home: params.appServer.start.env?.HOME,
|
|
3754
|
-
signal: params.signal,
|
|
3755
|
-
userProfile: params.appServer.start.env?.USERPROFILE
|
|
3756
|
-
}));
|
|
3757
|
-
const nativeSkillIsolationFingerprint = nativeSkillIsolation ? fingerprintJsonObject({
|
|
3758
|
-
version: 1,
|
|
3759
|
-
disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
|
|
3760
|
-
}) : void 0;
|
|
3761
|
-
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
3762
|
-
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
3763
|
-
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
3764
|
-
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
3765
|
-
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
3766
|
-
const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params.params);
|
|
3767
|
-
const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.params.pluginHarnessToolPolicyRestricted === true;
|
|
3768
|
-
const imageGenerationDenied = params.params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") === true;
|
|
3769
|
-
if (restrictedToolSurface && params.nativeCodeModeEnabled !== false) throw new Error("Codex restricted tool surfaces require native code mode to be disabled");
|
|
3770
|
-
const restrictedToolSurfaceInheritedMcpServerNames = restrictedToolSurface ? await lifecycleTiming.measure("restricted-tool-surface-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
3771
|
-
if (restrictedToolSurface || imageGenerationDenied) await lifecycleTiming.measure("tool-policy-config-requirements-read", () => assertCodexManagedRequirementsDoNotOverrideToolPolicy(params.client, {
|
|
3772
|
-
restrictedToolSurface,
|
|
3773
|
-
additionalDeniedFeatures: imageGenerationDenied ? ["image_generation"] : void 0
|
|
3774
|
-
}, params.signal));
|
|
3775
|
-
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
3776
|
-
version: 1,
|
|
3777
|
-
baseInstructions: "",
|
|
3778
|
-
config: buildCodexRingZeroThreadConfigPatch(params.params, true, restrictedToolSurfaceInheritedMcpServerNames)
|
|
3779
|
-
}) : void 0;
|
|
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";
|
|
3780
4298
|
return {
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
lifecycleTiming,
|
|
3789
|
-
nativeSkillIsolation,
|
|
3790
|
-
nativeSkillIsolationFingerprint,
|
|
3791
|
-
networkProxyConfigFingerprint,
|
|
3792
|
-
ringZeroActive,
|
|
3793
|
-
ringZeroClientInstanceId: ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0,
|
|
3794
|
-
ringZeroConfigFingerprint,
|
|
3795
|
-
restrictedToolSurface,
|
|
3796
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3797
|
-
userMcpServersConfigPatch,
|
|
3798
|
-
userMcpServersFingerprint,
|
|
3799
|
-
webSearchThreadConfigFingerprint
|
|
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
|
+
}
|
|
3800
4306
|
};
|
|
3801
4307
|
}
|
|
3802
4308
|
//#endregion
|
|
@@ -4168,6 +4674,19 @@ async function startOrResumeThread(params) {
|
|
|
4168
4674
|
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
4169
4675
|
return await withCodexThreadLifecycleBinding(params, async (bindingIdentity, currentBinding) => {
|
|
4170
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
|
+
}
|
|
4171
4690
|
const preflight = await prepareCodexThreadLifecyclePreflight(params);
|
|
4172
4691
|
const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = preflight;
|
|
4173
4692
|
let replacementPredecessor;
|
|
@@ -4181,35 +4700,15 @@ async function startOrResumeThread(params) {
|
|
|
4181
4700
|
config: params.params.config
|
|
4182
4701
|
});
|
|
4183
4702
|
const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
|
|
4184
|
-
const releaseRetainedThread =
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
lifecycleTiming,
|
|
4194
|
-
threadId,
|
|
4195
|
-
assertCurrent: assertCurrent ? () => {
|
|
4196
|
-
assertCurrent();
|
|
4197
|
-
assertPrevious?.();
|
|
4198
|
-
} : void 0
|
|
4199
|
-
});
|
|
4200
|
-
} finally {
|
|
4201
|
-
previousClient.release();
|
|
4202
|
-
}
|
|
4203
|
-
}
|
|
4204
|
-
if (isCodexAppServerLiveThreadClaimed(params.client, threadId)) throw new Error(`Codex thread ${threadId} is claimed by active work; stop it first.`);
|
|
4205
|
-
return await releaseCodexRetainedLiveThread({
|
|
4206
|
-
client: params.client,
|
|
4207
|
-
abandonClient: params.abandonClient,
|
|
4208
|
-
lifecycleTiming,
|
|
4209
|
-
threadId,
|
|
4210
|
-
assertCurrent
|
|
4211
|
-
});
|
|
4212
|
-
};
|
|
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
|
+
});
|
|
4213
4712
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
4214
4713
|
if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
|
|
4215
4714
|
bindingStore: params.bindingStore,
|
|
@@ -4484,28 +4983,6 @@ async function startOrResumeThread(params) {
|
|
|
4484
4983
|
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
4485
4984
|
await clearCurrentBinding("rotating a stale thread binding");
|
|
4486
4985
|
}
|
|
4487
|
-
} else if (incognito) {
|
|
4488
|
-
if (binding.clientId && binding.clientId === clientId && ((await buildLoadedPluginThreadConfig(binding))?.fingerprint ?? binding.pluginAppsFingerprint) === binding.pluginAppsFingerprint) {
|
|
4489
|
-
params.buildFinalConfigPatch?.({
|
|
4490
|
-
action: "resume",
|
|
4491
|
-
binding
|
|
4492
|
-
});
|
|
4493
|
-
throwIfAborted();
|
|
4494
|
-
lifecycleTiming.mark("thread-ready");
|
|
4495
|
-
lifecycleTiming.logSummary({
|
|
4496
|
-
runId: params.params.runId,
|
|
4497
|
-
sessionId: params.params.sessionId,
|
|
4498
|
-
sessionKey: params.params.sessionKey,
|
|
4499
|
-
threadId: binding.threadId,
|
|
4500
|
-
action: "resumed"
|
|
4501
|
-
});
|
|
4502
|
-
return {
|
|
4503
|
-
...binding,
|
|
4504
|
-
lifecycle: { action: "resumed" }
|
|
4505
|
-
};
|
|
4506
|
-
}
|
|
4507
|
-
await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
|
|
4508
|
-
binding = void 0;
|
|
4509
4986
|
} else {
|
|
4510
4987
|
const warmReuse = await tryReuseCodexLiveThread({
|
|
4511
4988
|
...requestContext,
|
|
@@ -4515,21 +4992,28 @@ async function startOrResumeThread(params) {
|
|
|
4515
4992
|
buildLoadedPluginThreadConfig
|
|
4516
4993
|
});
|
|
4517
4994
|
if (warmReuse.kind === "ready") return warmReuse.binding;
|
|
4518
|
-
if (warmReuse.kind === "rotate") {
|
|
4995
|
+
if (incognito || warmReuse.kind === "rotate") {
|
|
4519
4996
|
throwIfAborted();
|
|
4520
|
-
await clearCurrentBinding("rotating a stale plugin app binding");
|
|
4997
|
+
await clearCurrentBinding(incognito ? "rotating an unavailable ephemeral thread binding" : "rotating a stale plugin app binding");
|
|
4521
4998
|
} else {
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
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
|
+
}
|
|
4533
5017
|
}
|
|
4534
5018
|
}
|
|
4535
5019
|
}
|
|
@@ -4546,6 +5030,54 @@ async function startOrResumeThread(params) {
|
|
|
4546
5030
|
});
|
|
4547
5031
|
}
|
|
4548
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
|
|
4549
5081
|
//#region extensions/codex/src/app-server/user-input.ts
|
|
4550
5082
|
/** Builds ordered Codex user input for both new turns and same-turn steering. */
|
|
4551
5083
|
function buildCodexUserInput(text, images) {
|
|
@@ -4598,12 +5130,22 @@ function buildTurnStartParams(params, options) {
|
|
|
4598
5130
|
agentDir: params.agentDir,
|
|
4599
5131
|
config: params.config
|
|
4600
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;
|
|
4601
5139
|
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
4602
5140
|
const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
|
|
4603
|
-
let additionalContext =
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
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
|
+
};
|
|
4607
5149
|
if (params.permissionChange?.notice) additionalContext = {
|
|
4608
5150
|
...additionalContext,
|
|
4609
5151
|
openclaw_permission_change: {
|
|
@@ -4625,23 +5167,33 @@ function buildTurnStartParams(params, options) {
|
|
|
4625
5167
|
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
4626
5168
|
} : {},
|
|
4627
5169
|
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : options.clearInheritedServiceTier ? { serviceTier: null } : {},
|
|
4628
|
-
...
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
skillsCollaborationInstructions: options.skillsCollaborationInstructions,
|
|
4634
|
-
memoryCollaborationInstructions: options.memoryCollaborationInstructions
|
|
4635
|
-
}) } : {}
|
|
5170
|
+
...collaborationMode ? {
|
|
5171
|
+
effort: collaborationMode.settings.reasoning_effort,
|
|
5172
|
+
collaborationMode
|
|
5173
|
+
} : {},
|
|
5174
|
+
...options.environmentSelection ? { environments: options.environmentSelection } : {}
|
|
4636
5175
|
};
|
|
4637
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
|
+
}
|
|
4638
5186
|
function buildTurnCollaborationMode(params, options = {}) {
|
|
4639
5187
|
const model = options.model ?? params.modelId;
|
|
4640
5188
|
return {
|
|
4641
5189
|
mode: "default",
|
|
4642
5190
|
settings: {
|
|
4643
5191
|
model,
|
|
4644
|
-
reasoning_effort:
|
|
5192
|
+
reasoning_effort: resolveCodexAppServerReasoningEffort({
|
|
5193
|
+
thinkLevel: params.thinkLevel,
|
|
5194
|
+
modelId: model,
|
|
5195
|
+
supportedReasoningEfforts: readCodexSupportedReasoningEfforts(params.model?.compat)
|
|
5196
|
+
}),
|
|
4645
5197
|
developer_instructions: buildTurnScopedCollaborationInstructions(params, options)
|
|
4646
5198
|
}
|
|
4647
5199
|
};
|
|
@@ -4679,4 +5231,4 @@ function joinPresentSections(...sections) {
|
|
|
4679
5231
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
4680
5232
|
}
|
|
4681
5233
|
//#endregion
|
|
4682
|
-
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 };
|