@openclaw/codex 2026.8.1-beta.3 → 2026.8.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 +4 -5
- package/dist/{attempt-client-cleanup-URjUlCHg.js → attempt-client-cleanup-CVPvp9dL.js} +47 -5
- package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
- package/dist/{binding-connection-DAvZwp96.js → binding-connection-CCQFs8lf.js} +2 -2
- package/dist/{bounded-turn-4BjrZEwW.js → bounded-turn-rq5tiSXk.js} +13 -18
- package/dist/{capabilities-CH3K57v4.js → capabilities-CGm_94eL.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-D0nZ3qtd.js → command-formatters-C54Yg3eA.js} +2 -2
- package/dist/{command-handlers-DC86lgT4.js → command-handlers-BvOAHTrA.js} +89 -62
- package/dist/{compact-COqgBRny.js → compact-Hd7SAc0N.js} +8 -9
- package/dist/{config-CpNeo5AE.js → config-Cxw3Cl3k.js} +346 -286
- package/dist/doctor-contract-api.js +115 -14
- package/dist/{dynamic-tools-fJZhl4CE.js → dynamic-tools-BFoUcRdx.js} +147 -903
- package/dist/{effective-mcp-catalog-4YKn51aG.js → effective-mcp-catalog-BTJHnDSx.js} +2 -2
- package/dist/{event-projector-assistant-message-BwxV4d6N.js → event-projector-assistant-message-C5-Ds8mA.js} +18 -1
- package/dist/{event-projector-usage-CLwNEJqW.js → event-projector-usage-D3PHFFXh.js} +3 -3
- package/dist/{harness-DoKQcF8n.js → harness-DVfUQBlQ.js} +42 -52
- package/dist/harness.js +1 -1
- package/dist/index.js +278 -295
- package/dist/{isolated-completion-DPMtlH9r.js → isolated-completion-Cbbx_5Ed.js} +4 -4
- package/dist/launch-args-sXOAg0SX.js +84 -0
- package/dist/{managed-binary-By4FZC6i.js → managed-binary-ZkGodW19.js} +43 -7
- package/dist/media-understanding-provider.js +1 -1
- package/dist/model-catalog-o6Cgjoss.js +101 -0
- package/dist/{models-CZw1wClT.js → models-DgFWSuxA.js} +25 -28
- package/dist/{native-subagent-monitor-GGq3RnOo.js → native-subagent-monitor-CwFyoylz.js} +207 -30
- package/dist/{node-exec-server.runtime-Bsu1LdFZ.js → node-exec-server.runtime-DXrqpw1c.js} +8 -5
- package/dist/{plugin-approval-roundtrip-C75Yiiwc.js → plugin-approval-roundtrip-D80UZiJ_.js} +1 -1
- package/dist/{plugin-marketplace-discovery-CSWDkMrP.js → plugin-marketplace-discovery-mdmScYOj.js} +30 -39
- package/dist/{plugin-metadata-cache-BXH5NF-4.js → plugin-metadata-cache-CulOABwu.js} +162 -135
- package/dist/{protocol-validators-VAVyEnDp.js → protocol-validators-Bg5rpNxk.js} +148 -5
- package/dist/{rate-limits-BI1dMHDG.js → rate-limits-hFtzt2Up.js} +1 -1
- package/dist/{sandbox-guard-C1qEeZ5C.js → request-CCm4LaED.js} +183 -2
- package/dist/{run-attempt-BV3HzuCf.js → run-attempt-BwNQDH2u.js} +599 -361
- package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-DeBvleJw.js} +1 -1
- package/dist/{session-binding-BTrLf9Qm.js → session-binding-q2WSTVmC.js} +195 -122
- package/dist/{session-catalog-ezP_UcRM.js → session-catalog-C2Tae4rI.js} +229 -122
- package/dist/session-catalog-home-id-DAKUrm3C.js +112 -0
- package/dist/{session-cli-B1hKvfkI.js → session-cli-BuDjm5Ou.js} +1 -1
- package/dist/session-permission-policy-gLm40Vtq.js +160 -0
- package/dist/session-retirement-CdkI0bvb.js +90 -0
- package/dist/{session-upstream-marker-D_2npHlA.js → session-upstream-marker-Bcy33kam.js} +2 -3
- package/dist/{settled-turn-finalizer-Dx1onIqg.js → settled-turn-finalizer-B8JJcxCi.js} +7 -6
- package/dist/{shared-client-Dw-rzii0.js → shared-client-kNkljY-R.js} +1720 -786
- package/dist/{side-question-DbXkJZzg.js → side-question-GwD73tX6.js} +69 -39
- package/dist/{thread-lifecycle-DKdrnTMX.js → thread-lifecycle-Ss-uwnC4.js} +852 -359
- package/dist/{thread-ownership-i2NBwDUL.js → thread-ownership-CeYBqg_4.js} +18 -8
- package/dist/{plugin-app-cache-key-DMNQ8Zi8.js → thread-resume-DF54hagX.js} +52 -6
- package/dist/{transcript-mirror-BU0_LKAL.js → transcript-mirror-CkEQIna-.js} +112 -13
- package/dist/transport-stdio-C7-s9TLG.js +708 -0
- package/dist/{upstream-session-fork-BDVJCLAD.js → upstream-session-fork-BACGlOFa.js} +78 -86
- package/dist/{usage-9dKJCgcy.js → usage-C2l8Mils.js} +3 -3
- package/dist/{web-search-provider.runtime-DCdx4DGu.js → web-search-provider.runtime-B2XgRdFv.js} +3 -3
- package/openclaw.plugin.json +19 -1
- package/package.json +8 -8
- package/dist/app-server-policy-bg5QPSVP.js +0 -25
- package/dist/command-rpc-xUIIonsO.js +0 -90
- package/dist/incognito-session-ET_ufDH-.js +0 -2
- package/dist/model-catalog-CmGoqVKB.js +0 -48
- package/dist/request-DSK0W6YI.js +0 -157
- package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
- package/dist/session-retirement-CE4iMiLh.js +0 -36
- package/dist/thread-resume-CdJk2Ehf.js +0 -30
- package/dist/transport-stdio-DhoGy33Z.js +0 -99
|
@@ -1,33 +1,38 @@
|
|
|
1
|
-
import {
|
|
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 { a as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { n as codexCatalogHomeId } from "./session-catalog-home-id-
|
|
9
|
-
import {
|
|
10
|
-
import { u as projectBoundedCodexThreadHistory } from "./transcript-mirror-BU0_LKAL.js";
|
|
1
|
+
import { E as resolveCodexPluginsPolicy, F as resolveCodexAppServerLocalHomeDir, M as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, P as resolveCodexAppServerHomeDir, f as assertCodexModelBackedReviewerEffectiveConfig, j as CODEX_PLUGINS_MARKETPLACE_NAME, n as codexSandboxPolicyForTurn } from "./config-Cxw3Cl3k.js";
|
|
2
|
+
import { $ as CodexAppServerRpcError, D as CodexThreadStartRequestError, E as CodexThreadBindingConflictError, H as getCodexAppServerClientInstanceId, Ht as isJsonObject, J as isCodexAppServerOverloadError, Q as resolveCodexAppServerClientInstanceId, St as releaseCodexAppServerLiveThread, T as CodexRestrictedToolSurfaceAttestationError, Vt as flattenCodexDynamicToolFunctions, _ as retainSharedCodexAppServerClientByInstanceId, bt as isCodexAppServerLiveThreadClaimed, dt as resolveCodexToolAbortTerminalReason, gt as consumeCodexAppServerLiveThread, n as captureExclusiveSharedCodexAppServerClient, w as CodexAdoptedThreadActiveError, wt as unsubscribeCodexAppServerLiveThread } from "./shared-client-kNkljY-R.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-q2WSTVmC.js";
|
|
4
|
+
import { a as assertCodexThreadForkResponse, r as assertCodexThreadAcceptsDirectInput, s as assertCodexThreadStartResponse, t as CodexThreadDirectInputError } from "./protocol-validators-Bg5rpNxk.js";
|
|
5
|
+
import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, f as unsubscribeCodexThreadBestEffort, n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-CVPvp9dL.js";
|
|
6
|
+
import { c as serializeCodexAppInventoryError, n as buildCodexAppServerConnectionFingerprint, s as defaultCodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-DF54hagX.js";
|
|
7
|
+
import { c as withExclusiveCodexAppServerThread, l as isIncognitoSessionKey } from "./thread-ownership-CeYBqg_4.js";
|
|
8
|
+
import { i as readCodexSessionMeta, n as codexCatalogHomeId } from "./session-catalog-home-id-DAKUrm3C.js";
|
|
9
|
+
import { d as projectBoundedCodexThreadHistory } from "./transcript-mirror-CkEQIna-.js";
|
|
11
10
|
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
|
|
12
11
|
import { asOptionalRecord, isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
12
|
import fs from "node:fs/promises";
|
|
14
13
|
import path from "node:path";
|
|
15
|
-
import {
|
|
14
|
+
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
15
|
+
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";
|
|
16
17
|
import * as crypto$1 from "node:crypto";
|
|
17
18
|
import crypto, { createHash } from "node:crypto";
|
|
18
19
|
import os from "node:os";
|
|
19
|
-
import {
|
|
20
|
+
import { isDeepStrictEqual } from "node:util";
|
|
21
|
+
import { resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
|
|
22
|
+
import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
20
23
|
import { z } from "zod";
|
|
21
24
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
22
25
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
26
|
+
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
27
|
+
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
23
28
|
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
24
29
|
import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
25
30
|
import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
26
31
|
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
27
32
|
import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
33
|
+
import { buildCodexUserMcpServersThreadConfigPatchForRun } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
34
|
+
import { registerRetainedNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
|
|
35
|
+
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-flags";
|
|
31
36
|
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
32
37
|
/**
|
|
33
38
|
* Reads Codex plugin marketplace state and app inventory to decide which
|
|
@@ -270,15 +275,39 @@ function resolveOwnedApps(params) {
|
|
|
270
275
|
enabled: false,
|
|
271
276
|
needsAuth: true
|
|
272
277
|
};
|
|
273
|
-
return {
|
|
278
|
+
return Object.assign({
|
|
274
279
|
id: app.id,
|
|
275
280
|
name: app.name,
|
|
276
281
|
accessible: info.isAccessible,
|
|
277
282
|
enabled: info.isEnabled,
|
|
278
283
|
needsAuth: !info.isAccessible
|
|
279
|
-
};
|
|
284
|
+
}, resolveOwnedAppReadOnlyToolConfigKeys(info));
|
|
280
285
|
}).toSorted((left, right) => left.id.localeCompare(right.id));
|
|
281
286
|
}
|
|
287
|
+
/** Returns the config keys that Codex metadata proves belong to read-only tools. */
|
|
288
|
+
function resolveOwnedAppReadOnlyToolConfigKeys(app) {
|
|
289
|
+
const appName = app.name.trim();
|
|
290
|
+
const appNameLower = appName.toLowerCase();
|
|
291
|
+
const tools = app.toolSummaries ?? [];
|
|
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({
|
|
298
|
+
appName,
|
|
299
|
+
appNameLower,
|
|
300
|
+
tool
|
|
301
|
+
})).filter((key) => !writableToolConfigKeys.has(key));
|
|
302
|
+
return keys.length > 0 ? { readOnlyToolConfigKeys: Array.from(new Set(keys)).toSorted() } : {};
|
|
303
|
+
}
|
|
304
|
+
function resolveAppToolConfigKeys(params) {
|
|
305
|
+
const keys = [params.tool.name];
|
|
306
|
+
if (params.tool.title) keys.push(params.tool.title);
|
|
307
|
+
if (params.appName) keys.push(`${params.appName}_${params.tool.name}`);
|
|
308
|
+
if (params.appNameLower && params.appNameLower !== params.appName) keys.push(`${params.appNameLower}_${params.tool.name}`);
|
|
309
|
+
return keys;
|
|
310
|
+
}
|
|
282
311
|
function findPluginSummary(marketplace, pluginName) {
|
|
283
312
|
const exact = marketplace.plugins.find((plugin) => plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}`);
|
|
284
313
|
if (exact) return exact;
|
|
@@ -457,12 +486,21 @@ function activationFailure(identity, reason, diagnostic, extraDiagnostics = [])
|
|
|
457
486
|
}
|
|
458
487
|
//#endregion
|
|
459
488
|
//#region extensions/codex/src/app-server/plugin-thread-app-admission.ts
|
|
489
|
+
function resolveCodexPluginThreadAppCacheKey(params) {
|
|
490
|
+
return params.threadId ? `${params.appCacheKey}:thread:${encodeURIComponent(params.threadId)}` : params.appCacheKey;
|
|
491
|
+
}
|
|
492
|
+
function createCodexPluginThreadAppInventoryRequest(params) {
|
|
493
|
+
return async (method, requestParams) => await params.request(method, (method === "app/installed" || method === "app/read") && params.threadId ? {
|
|
494
|
+
...requestParams,
|
|
495
|
+
threadId: params.threadId
|
|
496
|
+
} : requestParams);
|
|
497
|
+
}
|
|
460
498
|
async function refreshCodexPluginAppInventory(params, appCache, options = {}) {
|
|
461
499
|
if (!params.appCacheKey) return;
|
|
462
|
-
const request =
|
|
500
|
+
const request = createCodexPluginThreadAppInventoryRequest(params);
|
|
463
501
|
try {
|
|
464
502
|
return await appCache.refreshNow({
|
|
465
|
-
key: params
|
|
503
|
+
key: resolveCodexPluginThreadAppCacheKey(params),
|
|
466
504
|
request,
|
|
467
505
|
nowMs: params.nowMs,
|
|
468
506
|
forceRefetch: options.forceRefetch,
|
|
@@ -499,9 +537,9 @@ function normalizeCodexPluginOwnerName(name) {
|
|
|
499
537
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
500
538
|
}
|
|
501
539
|
async function readCodexThreadAdmissibleAccountApps(params, appCache) {
|
|
502
|
-
const request =
|
|
540
|
+
const request = createCodexPluginThreadAppInventoryRequest(params);
|
|
503
541
|
const cachedInventory = appCache.read({
|
|
504
|
-
key: params
|
|
542
|
+
key: resolveCodexPluginThreadAppCacheKey(params),
|
|
505
543
|
request,
|
|
506
544
|
nowMs: params.nowMs,
|
|
507
545
|
suppressRefresh: true
|
|
@@ -527,7 +565,8 @@ function toCodexPluginOwnedAccountApp(app) {
|
|
|
527
565
|
name: app.name,
|
|
528
566
|
accessible: app.isAccessible,
|
|
529
567
|
enabled: app.isEnabled,
|
|
530
|
-
needsAuth: !app.isAccessible
|
|
568
|
+
needsAuth: !app.isAccessible,
|
|
569
|
+
...resolveOwnedAppReadOnlyToolConfigKeys(app)
|
|
531
570
|
};
|
|
532
571
|
}
|
|
533
572
|
function resolveCodexThreadConfigAppsForRecord(params) {
|
|
@@ -616,6 +655,11 @@ function buildCodexPluginThreadConfigTimeoutFallback(params) {
|
|
|
616
655
|
/** Builds the Codex apps config patch and policy context for a native thread. */
|
|
617
656
|
async function buildCodexPluginThreadConfig(params) {
|
|
618
657
|
const appCache = params.appCache ?? defaultCodexAppInventoryCache;
|
|
658
|
+
const threadAppCacheKey = resolveCodexPluginThreadAppCacheKey(params);
|
|
659
|
+
const threadRequest = (method, requestParams) => params.request(method, (method === "app/installed" || method === "app/read") && params.threadId && isJsonObject(requestParams) ? {
|
|
660
|
+
...requestParams,
|
|
661
|
+
threadId: params.threadId
|
|
662
|
+
} : requestParams);
|
|
619
663
|
let inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
620
664
|
pluginConfig: params.pluginConfig,
|
|
621
665
|
appCacheKey: params.appCacheKey
|
|
@@ -629,9 +673,9 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
629
673
|
let inventory = policy.pluginPolicies.length > 0 ? await readCodexPluginInventory({
|
|
630
674
|
pluginConfig: params.pluginConfig,
|
|
631
675
|
policy,
|
|
632
|
-
request:
|
|
676
|
+
request: threadRequest,
|
|
633
677
|
appCache,
|
|
634
|
-
appCacheKey:
|
|
678
|
+
appCacheKey: threadAppCacheKey,
|
|
635
679
|
configCwd: params.configCwd,
|
|
636
680
|
metadataCache: params.metadataCache,
|
|
637
681
|
nowMs: params.nowMs,
|
|
@@ -647,9 +691,9 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
647
691
|
inventory = await readCodexPluginInventory({
|
|
648
692
|
pluginConfig: params.pluginConfig,
|
|
649
693
|
policy,
|
|
650
|
-
request:
|
|
694
|
+
request: threadRequest,
|
|
651
695
|
appCache,
|
|
652
|
-
appCacheKey:
|
|
696
|
+
appCacheKey: threadAppCacheKey,
|
|
653
697
|
configCwd: params.configCwd,
|
|
654
698
|
metadataCache: params.metadataCache,
|
|
655
699
|
nowMs: params.nowMs
|
|
@@ -665,9 +709,9 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
665
709
|
if (!record.activationRequired) continue;
|
|
666
710
|
const activation = await ensureCodexPluginActivation({
|
|
667
711
|
identity: record.policy,
|
|
668
|
-
request:
|
|
712
|
+
request: threadRequest,
|
|
669
713
|
appCache,
|
|
670
|
-
appCacheKey:
|
|
714
|
+
appCacheKey: threadAppCacheKey,
|
|
671
715
|
configCwd: params.configCwd,
|
|
672
716
|
metadataCache: params.metadataCache,
|
|
673
717
|
deferAppInventoryRefresh: true,
|
|
@@ -691,9 +735,9 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
691
735
|
inventory = await readCodexPluginInventory({
|
|
692
736
|
pluginConfig: params.pluginConfig,
|
|
693
737
|
policy,
|
|
694
|
-
request:
|
|
738
|
+
request: threadRequest,
|
|
695
739
|
appCache,
|
|
696
|
-
appCacheKey:
|
|
740
|
+
appCacheKey: threadAppCacheKey,
|
|
697
741
|
configCwd: params.configCwd,
|
|
698
742
|
metadataCache: params.metadataCache,
|
|
699
743
|
nowMs: params.nowMs
|
|
@@ -712,9 +756,9 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
712
756
|
inventory = await readCodexPluginInventory({
|
|
713
757
|
pluginConfig: params.pluginConfig,
|
|
714
758
|
policy,
|
|
715
|
-
request:
|
|
759
|
+
request: threadRequest,
|
|
716
760
|
appCache,
|
|
717
|
-
appCacheKey:
|
|
761
|
+
appCacheKey: threadAppCacheKey,
|
|
718
762
|
configCwd: params.configCwd,
|
|
719
763
|
metadataCache: params.metadataCache,
|
|
720
764
|
nowMs: params.nowMs
|
|
@@ -952,7 +996,7 @@ function readPersistedAppToolApprovalOverrideNames(config, app) {
|
|
|
952
996
|
const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
|
|
953
997
|
const tools = isJsonObject(appConfig) ? appConfig.tools : void 0;
|
|
954
998
|
if (!isJsonObject(tools)) return [];
|
|
955
|
-
return Object.entries(tools).filter(([, value]) => hasPersistedToolApprovalOverride(value)).map(([toolName]) => toolName).toSorted();
|
|
999
|
+
return Object.entries(tools).filter(([toolName]) => !app.readOnlyToolConfigKeys?.includes(toolName)).filter(([, value]) => hasPersistedToolApprovalOverride(value)).map(([toolName]) => toolName).toSorted();
|
|
956
1000
|
}
|
|
957
1001
|
function hasPersistedToolApprovalOverride(value) {
|
|
958
1002
|
return isJsonObject(value) && value.approval_mode !== void 0;
|
|
@@ -1780,33 +1824,6 @@ function applyCodexNativeSkillIsolation(config, isolation) {
|
|
|
1780
1824
|
};
|
|
1781
1825
|
}
|
|
1782
1826
|
//#endregion
|
|
1783
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
|
|
1784
|
-
var CodexThreadStartRequestError = class extends Error {
|
|
1785
|
-
constructor(cause) {
|
|
1786
|
-
super(`thread/start: ${formatErrorMessage(cause)}`, { cause });
|
|
1787
|
-
this.name = "CodexThreadStartRequestError";
|
|
1788
|
-
}
|
|
1789
|
-
};
|
|
1790
|
-
var CodexThreadBindingConflictError = class extends Error {
|
|
1791
|
-
constructor(threadId, operation) {
|
|
1792
|
-
super(`Codex thread binding changed while ${operation}: ${threadId}`);
|
|
1793
|
-
this.name = "CodexThreadBindingConflictError";
|
|
1794
|
-
}
|
|
1795
|
-
};
|
|
1796
|
-
var CodexRestrictedToolSurfaceAttestationError = class extends Error {
|
|
1797
|
-
constructor(cause) {
|
|
1798
|
-
super("Codex restricted-tool-surface MCP attestation failed", { cause });
|
|
1799
|
-
this.name = "CodexRestrictedToolSurfaceAttestationError";
|
|
1800
|
-
}
|
|
1801
|
-
};
|
|
1802
|
-
var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
|
|
1803
|
-
var CodexAdoptedThreadActiveError = class extends Error {
|
|
1804
|
-
constructor() {
|
|
1805
|
-
super("Codex session became active in another runner; wait for it to finish before continuing");
|
|
1806
|
-
this.name = "CodexAdoptedThreadActiveError";
|
|
1807
|
-
}
|
|
1808
|
-
};
|
|
1809
|
-
//#endregion
|
|
1810
1827
|
//#region extensions/codex/src/app-server/managed-thread-store.ts
|
|
1811
1828
|
const CODEX_MANAGED_THREAD_NAMESPACE = "app-server-managed-threads";
|
|
1812
1829
|
const CODEX_MANAGED_THREAD_MAX_ENTRIES = 2e4;
|
|
@@ -1981,11 +1998,6 @@ const LEGACY_PRO_REASONING_EFFORTS = [
|
|
|
1981
1998
|
];
|
|
1982
1999
|
const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
|
|
1983
2000
|
const MODERN_GPT_5_MODEL_ID_RE = /^gpt-5\.(?:[3-9]|[1-9]\d)(?:$|-)/u;
|
|
1984
|
-
function normalizeCodexReasoningEfforts(efforts) {
|
|
1985
|
-
if (!efforts) return [];
|
|
1986
|
-
const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
|
|
1987
|
-
return CODEX_REASONING_EFFORTS.filter((effort) => supported.has(effort));
|
|
1988
|
-
}
|
|
1989
2001
|
/** Read reasoning metadata after the Codex app-server route has been selected. */
|
|
1990
2002
|
function readCodexSupportedReasoningEfforts(compat) {
|
|
1991
2003
|
if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
|
|
@@ -1994,7 +2006,8 @@ function readCodexSupportedReasoningEfforts(compat) {
|
|
|
1994
2006
|
return efforts.filter((effort) => typeof effort === "string");
|
|
1995
2007
|
}
|
|
1996
2008
|
function resolveSupportedReasoningEffort(params) {
|
|
1997
|
-
const
|
|
2009
|
+
const declared = new Set(params.supportedReasoningEfforts.map((effort) => effort.trim().toLowerCase()));
|
|
2010
|
+
const supported = CODEX_REASONING_EFFORTS.filter((effort) => declared.has(effort));
|
|
1998
2011
|
if (supported.includes(params.requested)) return params.requested;
|
|
1999
2012
|
const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
|
|
2000
2013
|
const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
|
|
@@ -2002,7 +2015,8 @@ function resolveSupportedReasoningEffort(params) {
|
|
|
2002
2015
|
}
|
|
2003
2016
|
/** Resolve a turn effort from the selected model's provider-owned metadata. */
|
|
2004
2017
|
function resolveCodexAppServerReasoningEffort(params) {
|
|
2005
|
-
if (params.thinkLevel === "off"
|
|
2018
|
+
if (params.thinkLevel === "off") return params.supportedReasoningEfforts?.includes("none") ? "none" : null;
|
|
2019
|
+
if (params.thinkLevel === "adaptive") return null;
|
|
2006
2020
|
if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
2007
2021
|
requested: params.thinkLevel,
|
|
2008
2022
|
supportedReasoningEfforts: params.supportedReasoningEfforts
|
|
@@ -2102,6 +2116,7 @@ function buildCodexProjectDocThreadConfig(config) {
|
|
|
2102
2116
|
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
2103
2117
|
function buildDeveloperInstructions(params, options = {}) {
|
|
2104
2118
|
const deferredToolNames = /* @__PURE__ */ new Set();
|
|
2119
|
+
let secretsToolName;
|
|
2105
2120
|
let hasSkillWorkshop = false;
|
|
2106
2121
|
let hasSessionsSpawn = false;
|
|
2107
2122
|
let hasSessionsYield = false;
|
|
@@ -2115,6 +2130,7 @@ function buildDeveloperInstructions(params, options = {}) {
|
|
|
2115
2130
|
for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
|
|
2116
2131
|
const name = tool.name.trim();
|
|
2117
2132
|
if (tool.deferLoading === true && name) deferredToolNames.add(name);
|
|
2133
|
+
if (name === "secrets" && params.disableTools !== true) secretsToolName ??= spec.type === "namespace" ? `${spec.name}.${name}` : name;
|
|
2118
2134
|
hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
|
|
2119
2135
|
hasSessionsSpawn ||= name === "sessions_spawn";
|
|
2120
2136
|
hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
|
|
@@ -2154,7 +2170,7 @@ function buildDeveloperInstructions(params, options = {}) {
|
|
|
2154
2170
|
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
|
2155
2171
|
messageToolAvailable
|
|
2156
2172
|
}),
|
|
2157
|
-
|
|
2173
|
+
buildCredentialSafetyPrompt(secretsToolName),
|
|
2158
2174
|
nativeCommandGuidance,
|
|
2159
2175
|
params.extraSystemPrompt
|
|
2160
2176
|
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
@@ -2261,7 +2277,8 @@ function resolveCodexWebSearchPlan(params) {
|
|
|
2261
2277
|
const CODEX_CODE_MODE_THREAD_CONFIG = {
|
|
2262
2278
|
"features.code_mode": true,
|
|
2263
2279
|
"features.code_mode_only": false,
|
|
2264
|
-
"features.apply_patch_streaming_events": true
|
|
2280
|
+
"features.apply_patch_streaming_events": true,
|
|
2281
|
+
suppress_unstable_features_warning: true
|
|
2265
2282
|
};
|
|
2266
2283
|
const CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG = { "features.goals": false };
|
|
2267
2284
|
const CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG = { "tools.update_plan.enabled": false };
|
|
@@ -2451,33 +2468,24 @@ function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
|
2451
2468
|
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
2452
2469
|
};
|
|
2453
2470
|
if (options.nativeCodeModeEnabled === false) {
|
|
2454
|
-
const disabledConfig = mergeCodexThreadConfigs(configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG)
|
|
2455
|
-
...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG,
|
|
2456
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
2457
|
-
...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG
|
|
2458
|
-
};
|
|
2471
|
+
const disabledConfig = expectDefined(mergeCodexThreadConfigs(configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG), "Codex disabled code mode config");
|
|
2459
2472
|
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
2460
2473
|
return disabledConfig;
|
|
2461
2474
|
}
|
|
2462
|
-
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true })
|
|
2463
|
-
|
|
2464
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
2465
|
-
...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG,
|
|
2466
|
-
"features.code_mode_only": true
|
|
2467
|
-
}, options.directOnlyToolNamespaces);
|
|
2468
|
-
return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG) ?? {
|
|
2469
|
-
...codeModeConfig,
|
|
2470
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
2471
|
-
...CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG
|
|
2472
|
-
}, options.directOnlyToolNamespaces);
|
|
2475
|
+
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(expectDefined(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true }), "Codex code mode only config"), options.directOnlyToolNamespaces);
|
|
2476
|
+
return ensureDirectOnlyToolNamespaces(expectDefined(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG), "Codex code mode config"), options.directOnlyToolNamespaces);
|
|
2473
2477
|
}
|
|
2474
2478
|
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
2475
2479
|
if (!requiredNamespaces?.length) return config;
|
|
2476
|
-
const
|
|
2477
|
-
const
|
|
2480
|
+
const feature = expectDefined(config["features.code_mode"], "Codex code mode config");
|
|
2481
|
+
const configured = isJsonObject(feature) ? feature : { enabled: feature };
|
|
2482
|
+
const namespaces = Array.isArray(configured.direct_only_tool_namespaces) ? configured.direct_only_tool_namespaces.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2478
2483
|
return {
|
|
2479
2484
|
...config,
|
|
2480
|
-
"code_mode
|
|
2485
|
+
"features.code_mode": {
|
|
2486
|
+
...configured,
|
|
2487
|
+
direct_only_tool_namespaces: [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
2488
|
+
}
|
|
2481
2489
|
};
|
|
2482
2490
|
}
|
|
2483
2491
|
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
@@ -2491,10 +2499,6 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
2491
2499
|
const configMcpServers = config?.mcp_servers;
|
|
2492
2500
|
if (restrictedToolSurface && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex restricted tool surface received invalid thread mcp_servers config");
|
|
2493
2501
|
const restrictedToolSurfaceMcpServerNames = [...options.restrictedToolSurfaceInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
2494
|
-
const restrictedRunConfig = restrictedToolSurface && isJsonObject(configMcpServers) ? {
|
|
2495
|
-
...config,
|
|
2496
|
-
mcp_servers: {}
|
|
2497
|
-
} : config;
|
|
2498
2502
|
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
2499
2503
|
config: params.config,
|
|
2500
2504
|
disableTools: params.disableTools,
|
|
@@ -2502,7 +2506,7 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
2502
2506
|
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2503
2507
|
webSearchAllowed: options.webSearchAllowed
|
|
2504
2508
|
}).threadConfig;
|
|
2505
|
-
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(
|
|
2509
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
2506
2510
|
return applyCodexManagedShellEnvironment({
|
|
2507
2511
|
...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,
|
|
2508
2512
|
...params.bootstrapContextMode === "lightweight" ? CODEX_NO_PROJECT_DOCS_CONFIG : {}
|
|
@@ -2613,14 +2617,13 @@ function resolveCodexThreadEnvironmentSelection(options) {
|
|
|
2613
2617
|
}
|
|
2614
2618
|
//#endregion
|
|
2615
2619
|
//#region extensions/codex/src/app-server/thread-lifecycle-io.ts
|
|
2616
|
-
function
|
|
2617
|
-
const agentId =
|
|
2620
|
+
function resolveCodexThreadAgentDir(params) {
|
|
2621
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
2618
2622
|
config: params.params.config,
|
|
2619
2623
|
sessionKey: params.params.sessionKey,
|
|
2620
2624
|
agentId: params.agentId ?? params.params.agentId
|
|
2621
2625
|
}).sessionAgentId;
|
|
2622
|
-
|
|
2623
|
-
return codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir));
|
|
2626
|
+
return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
|
|
2624
2627
|
}
|
|
2625
2628
|
function resolveCodexThreadRolloutPath(thread) {
|
|
2626
2629
|
const rolloutPath = thread.path?.trim();
|
|
@@ -2630,6 +2633,8 @@ function resolveCodexThreadRolloutPath(thread) {
|
|
|
2630
2633
|
async function resumeExistingCodexThread(params, context) {
|
|
2631
2634
|
const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, nativeSkillIsolationFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, nativeSkillIsolation, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
|
|
2632
2635
|
let resumeReservation;
|
|
2636
|
+
let resumeResponseAccepted = false;
|
|
2637
|
+
const abandonClient = params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client));
|
|
2633
2638
|
try {
|
|
2634
2639
|
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
2635
2640
|
const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
|
|
@@ -2663,25 +2668,29 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2663
2668
|
}));
|
|
2664
2669
|
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2665
2670
|
throwIfAborted();
|
|
2666
|
-
if (resumeBinding.preserveNativeModel === true) {
|
|
2667
|
-
const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
2668
|
-
threadId: resumeBinding.threadId,
|
|
2669
|
-
includeTurns: false
|
|
2670
|
-
}, { signal: params.signal }));
|
|
2671
|
-
throwIfAborted();
|
|
2672
|
-
if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
2673
|
-
}
|
|
2674
2671
|
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
2675
2672
|
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
2676
2673
|
client: params.client,
|
|
2677
|
-
abandonClient
|
|
2674
|
+
abandonClient,
|
|
2678
2675
|
request: resumeParams,
|
|
2679
|
-
signal: params.signal
|
|
2676
|
+
signal: params.signal,
|
|
2677
|
+
assertCurrent: context.assertResumeOwnership,
|
|
2678
|
+
isPrewriteOwnershipError: (error) => error instanceof CodexAdoptedThreadActiveError
|
|
2680
2679
|
}));
|
|
2680
|
+
resumeResponseAccepted = true;
|
|
2681
|
+
assertCodexThreadAcceptsDirectInput(response.thread);
|
|
2682
|
+
context.assertResumeConfiguration?.();
|
|
2683
|
+
if (resumeBinding.pendingResumeConfiguration) await attestCodexPluginThreadApps({
|
|
2684
|
+
client: params.client,
|
|
2685
|
+
threadId: response.thread.id,
|
|
2686
|
+
appIds: context.prebuiltPluginThreadConfig?.provisionalAppIds ?? [],
|
|
2687
|
+
signal: params.signal
|
|
2688
|
+
});
|
|
2681
2689
|
if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
|
|
2682
2690
|
await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
|
|
2683
2691
|
} catch (error) {
|
|
2684
|
-
|
|
2692
|
+
context.assertResumeOwnership?.();
|
|
2693
|
+
await abandonClient();
|
|
2685
2694
|
throw new CodexRestrictedToolSurfaceAttestationError(error);
|
|
2686
2695
|
}
|
|
2687
2696
|
throwIfAborted();
|
|
@@ -2689,6 +2698,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2689
2698
|
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2690
2699
|
const resumePatch = {
|
|
2691
2700
|
clientId: resolveCodexAppServerClientInstanceId(params.client),
|
|
2701
|
+
pendingResumeConfiguration: void 0,
|
|
2692
2702
|
cwd: params.cwd,
|
|
2693
2703
|
rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
|
|
2694
2704
|
authProfileId: boundAuthProfileId,
|
|
@@ -2719,7 +2729,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2719
2729
|
kind: "patch",
|
|
2720
2730
|
threadId: resumeBinding.threadId,
|
|
2721
2731
|
patch: resumePatch
|
|
2722
|
-
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2732
|
+
}, context.assertResumeConfiguration))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2723
2733
|
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2724
2734
|
sessionId: params.params.sessionId,
|
|
2725
2735
|
sessionKey: params.params.sessionKey,
|
|
@@ -2756,19 +2766,26 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2756
2766
|
};
|
|
2757
2767
|
} catch (error) {
|
|
2758
2768
|
resumeReservation?.release();
|
|
2759
|
-
if (
|
|
2769
|
+
if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
|
|
2760
2770
|
if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
|
|
2761
|
-
await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
|
|
2771
|
+
if (!resumeBinding.pendingResumeConfiguration) await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
|
|
2762
2772
|
throw error;
|
|
2763
2773
|
}
|
|
2764
|
-
if (
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2774
|
+
if (resumeResponseAccepted) {
|
|
2775
|
+
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
2776
|
+
threadId: resumeBinding.threadId,
|
|
2777
|
+
timeoutMs: 5e3,
|
|
2778
|
+
assertCurrent: context.assertResumeOwnership
|
|
2779
|
+
}).catch(() => false)) {
|
|
2780
|
+
try {
|
|
2781
|
+
await abandonClient();
|
|
2782
|
+
} catch (abandonError) {
|
|
2783
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume client could not be retired", { cause: abandonError });
|
|
2784
|
+
}
|
|
2785
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
if (resumeBinding.pendingResumeConfiguration || error instanceof CodexThreadDirectInputError || params.signal?.aborted) throw error;
|
|
2772
2789
|
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
2773
2790
|
await clearCurrentBinding("rotating a stale thread binding");
|
|
2774
2791
|
}
|
|
@@ -2899,7 +2916,7 @@ async function startFreshCodexThread(params, context) {
|
|
|
2899
2916
|
throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
|
|
2900
2917
|
}
|
|
2901
2918
|
};
|
|
2902
|
-
const managedSourceHomeId =
|
|
2919
|
+
const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
|
|
2903
2920
|
await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
|
|
2904
2921
|
sourceHomeId: managedSourceHomeId,
|
|
2905
2922
|
threadId: response.thread.id,
|
|
@@ -2958,6 +2975,603 @@ async function startFreshCodexThread(params, context) {
|
|
|
2958
2975
|
});
|
|
2959
2976
|
}
|
|
2960
2977
|
//#endregion
|
|
2978
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
|
|
2979
|
+
/** Preserves the caller's abort reason across thread ownership transitions. */
|
|
2980
|
+
function throwIfCodexThreadLifecycleAborted(signal) {
|
|
2981
|
+
if (!signal?.aborted) return;
|
|
2982
|
+
const reason = signal.reason;
|
|
2983
|
+
if (reason instanceof Error) throw reason;
|
|
2984
|
+
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
2985
|
+
error.name = "AbortError";
|
|
2986
|
+
throw error;
|
|
2987
|
+
}
|
|
2988
|
+
/** Releases consumed subscription ownership or retires an unsafe client. */
|
|
2989
|
+
async function releaseCodexConsumedLiveThread(options) {
|
|
2990
|
+
if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
|
|
2991
|
+
threadId: options.threadId,
|
|
2992
|
+
timeoutMs: 5e3,
|
|
2993
|
+
assertCurrent: options.assertCurrent
|
|
2994
|
+
}))) return;
|
|
2995
|
+
return await abandonCodexLiveThreadRelease(options, options.cause);
|
|
2996
|
+
}
|
|
2997
|
+
async function abandonCodexLiveThreadRelease(options, cause) {
|
|
2998
|
+
options.assertCurrent?.();
|
|
2999
|
+
await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
|
|
3000
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, cause !== void 0 ? { cause } : void 0);
|
|
3001
|
+
}
|
|
3002
|
+
/** Releases through the retained owner, preserving its guarded callback and rollback. */
|
|
3003
|
+
async function releaseCodexRetainedLiveThread(options) {
|
|
3004
|
+
try {
|
|
3005
|
+
return await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => releaseCodexAppServerLiveThread(options.client, options.threadId, options.assertCurrent));
|
|
3006
|
+
} catch (error) {
|
|
3007
|
+
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
3008
|
+
return await abandonCodexLiveThreadRelease(options, error);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
/** Reuses one safely owned, fully matching subscription on its original client. */
|
|
3012
|
+
async function tryReuseCodexLiveThread(options) {
|
|
3013
|
+
const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, releaseConsumedThread, ringZeroActive, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
|
|
3014
|
+
if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive) return {};
|
|
3015
|
+
const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
|
|
3016
|
+
action: "resume",
|
|
3017
|
+
binding
|
|
3018
|
+
}) ?? {
|
|
3019
|
+
configPatch: params.finalConfigPatch,
|
|
3020
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
3021
|
+
};
|
|
3022
|
+
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0;
|
|
3023
|
+
const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
|
|
3024
|
+
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
|
|
3025
|
+
const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
3026
|
+
threadId: binding.threadId,
|
|
3027
|
+
cwd: params.cwd,
|
|
3028
|
+
authProfileId: resumeAuthProfileId,
|
|
3029
|
+
model: startModelSelection.model,
|
|
3030
|
+
modelProvider: startModelProvider,
|
|
3031
|
+
preserveNativeModel: false,
|
|
3032
|
+
appServer: params.appServer,
|
|
3033
|
+
dynamicTools: params.dynamicTools,
|
|
3034
|
+
developerInstructions: params.developerInstructions,
|
|
3035
|
+
config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
|
|
3036
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
3037
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3038
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
3039
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
3040
|
+
hostSystemAgentActive,
|
|
3041
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3042
|
+
shellEnvironment: params.shellEnvironment,
|
|
3043
|
+
disableLoginShell: params.disableLoginShell
|
|
3044
|
+
}));
|
|
3045
|
+
const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
|
|
3046
|
+
...resumeParams,
|
|
3047
|
+
model: binding.model ?? resumeParams.model ?? null,
|
|
3048
|
+
requestedModel: resumeParams.model ?? null,
|
|
3049
|
+
modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
|
|
3050
|
+
requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
|
|
3051
|
+
}, resumeAuthProfileId, dynamicToolsFingerprint);
|
|
3052
|
+
const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint);
|
|
3053
|
+
if (!retainedThread) {
|
|
3054
|
+
const incompatibleOwnership = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
|
|
3055
|
+
if (incompatibleOwnership) await incompatibleOwnership.release(binding.threadId);
|
|
3056
|
+
return { prebuiltFinalConfigPatch };
|
|
3057
|
+
}
|
|
3058
|
+
try {
|
|
3059
|
+
const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
|
|
3060
|
+
const model = startModelSelection.model;
|
|
3061
|
+
if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
3062
|
+
kind: "patch",
|
|
3063
|
+
threadId: binding.threadId,
|
|
3064
|
+
patch: {
|
|
3065
|
+
cwd: params.cwd,
|
|
3066
|
+
model,
|
|
3067
|
+
nativeHookRelayGeneration,
|
|
3068
|
+
environmentSelectionFingerprint
|
|
3069
|
+
}
|
|
3070
|
+
}))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
|
|
3071
|
+
throwIfAborted();
|
|
3072
|
+
lifecycleTiming.mark("thread-ready");
|
|
3073
|
+
lifecycleTiming.logSummary({
|
|
3074
|
+
runId: params.params.runId,
|
|
3075
|
+
sessionId: params.params.sessionId,
|
|
3076
|
+
sessionKey: params.params.sessionKey,
|
|
3077
|
+
threadId: binding.threadId,
|
|
3078
|
+
action: "resumed"
|
|
3079
|
+
});
|
|
3080
|
+
return {
|
|
3081
|
+
binding: {
|
|
3082
|
+
...binding,
|
|
3083
|
+
cwd: params.cwd,
|
|
3084
|
+
model,
|
|
3085
|
+
nativeHookRelayGeneration,
|
|
3086
|
+
environmentSelectionFingerprint,
|
|
3087
|
+
liveThreadConfigFingerprint,
|
|
3088
|
+
liveThreadOwnership: retainedThread,
|
|
3089
|
+
...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
|
|
3090
|
+
lifecycle: { action: "resumed" }
|
|
3091
|
+
},
|
|
3092
|
+
prebuiltFinalConfigPatch
|
|
3093
|
+
};
|
|
3094
|
+
} catch (error) {
|
|
3095
|
+
await releaseConsumedThread(binding.threadId, error);
|
|
3096
|
+
throw error;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
//#endregion
|
|
3100
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-adoption.ts
|
|
3101
|
+
/** Passive refusal must precede releasing or acquiring any native subscription. */
|
|
3102
|
+
async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context) {
|
|
3103
|
+
const { thread } = await context.lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
3104
|
+
threadId,
|
|
3105
|
+
includeTurns: false
|
|
3106
|
+
}, { signal: params.signal }));
|
|
3107
|
+
context.throwIfAborted();
|
|
3108
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
3109
|
+
if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
3110
|
+
}
|
|
3111
|
+
/** Preserve attach's native-queue-before-binding-lease order when consuming pending intent. */
|
|
3112
|
+
async function withCodexThreadLifecycleBinding(params, run) {
|
|
3113
|
+
const identity = sessionBindingIdentity({
|
|
3114
|
+
sessionId: params.params.sessionId,
|
|
3115
|
+
sessionKey: params.params.sessionKey,
|
|
3116
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
3117
|
+
config: params.params.config
|
|
3118
|
+
});
|
|
3119
|
+
const snapshot = await params.bindingStore.read(identity);
|
|
3120
|
+
const pendingThreadId = snapshot?.pendingResumeConfiguration ? snapshot.threadId : void 0;
|
|
3121
|
+
const runWithLease = () => params.bindingStore.withLease(identity, async () => {
|
|
3122
|
+
const binding = await params.bindingStore.read(identity);
|
|
3123
|
+
if (pendingThreadId && (binding?.threadId !== pendingThreadId || !binding.pendingResumeConfiguration)) throw new CodexThreadBindingConflictError(pendingThreadId, "acquiring a pending resume configuration");
|
|
3124
|
+
if (!pendingThreadId && binding?.pendingResumeConfiguration) throw new CodexThreadBindingConflictError(binding.threadId, "acquiring a pending resume configuration");
|
|
3125
|
+
return await run(identity, binding);
|
|
3126
|
+
});
|
|
3127
|
+
return pendingThreadId ? await withExclusiveCodexAppServerThread({
|
|
3128
|
+
bindingStore: params.bindingStore,
|
|
3129
|
+
identity,
|
|
3130
|
+
threadId: pendingThreadId,
|
|
3131
|
+
run: runWithLease
|
|
3132
|
+
}) : await runWithLease();
|
|
3133
|
+
}
|
|
3134
|
+
/** Completes manual attachment only under the native queue and exact binding lease. */
|
|
3135
|
+
async function resumePendingCodexThread(params, context) {
|
|
3136
|
+
const { binding, contextEngineBinding, lifecycleTiming, restrictedToolSurface } = context;
|
|
3137
|
+
if (isIncognitoSessionKey(params.params.sessionKey) || context.transientRestriction || !restrictedToolSurface && binding.nativeToolPolicyRestricted === true || (contextEngineBinding ? !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding) : binding.contextEngine !== void 0) || shouldRotateCodexGpt56MultiAgentBinding({
|
|
3138
|
+
bindingModel: binding.model,
|
|
3139
|
+
requestedModel: params.params.modelId
|
|
3140
|
+
})) 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.`);
|
|
3141
|
+
const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
3142
|
+
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
3143
|
+
const configuration = await preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint, async (assertCurrent) => {
|
|
3144
|
+
const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
|
|
3145
|
+
assertCurrent();
|
|
3146
|
+
if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
|
|
3147
|
+
client: params.client,
|
|
3148
|
+
abandonClient: params.abandonClient,
|
|
3149
|
+
lifecycleTiming,
|
|
3150
|
+
threadId: binding.threadId,
|
|
3151
|
+
assertCurrent
|
|
3152
|
+
});
|
|
3153
|
+
});
|
|
3154
|
+
try {
|
|
3155
|
+
const resumed = await resumeExistingCodexThread(params, {
|
|
3156
|
+
...context,
|
|
3157
|
+
prebuiltPluginThreadConfig,
|
|
3158
|
+
assertResumeConfiguration: configuration.assertConfigured,
|
|
3159
|
+
assertResumeOwnership: configuration.assertCurrent
|
|
3160
|
+
});
|
|
3161
|
+
if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
|
|
3162
|
+
return resumed;
|
|
3163
|
+
} finally {
|
|
3164
|
+
configuration.dispose();
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
/** Manual attachment is intent, never evidence that loaded native overrides took effect. */
|
|
3168
|
+
async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
|
|
3169
|
+
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.`);
|
|
3170
|
+
const agentDir = resolveCodexThreadAgentDir(params);
|
|
3171
|
+
const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
|
|
3172
|
+
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");
|
|
3173
|
+
if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw fail("the thread is claimed by active work; stop that run before resuming");
|
|
3174
|
+
const assertCurrent = captureExclusiveSharedCodexAppServerClient(params.client);
|
|
3175
|
+
const { thread } = await params.client.request("thread/read", {
|
|
3176
|
+
threadId: binding.threadId,
|
|
3177
|
+
includeTurns: false
|
|
3178
|
+
}, { signal: params.signal });
|
|
3179
|
+
assertCurrent();
|
|
3180
|
+
const statusType = thread.status?.type;
|
|
3181
|
+
if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
|
|
3182
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
3183
|
+
let unloaded = statusType === "notLoaded";
|
|
3184
|
+
const dispose = params.client.addNotificationHandler((notification) => {
|
|
3185
|
+
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;
|
|
3186
|
+
});
|
|
3187
|
+
try {
|
|
3188
|
+
const rolloutPath = thread.path ?? binding.rolloutPath;
|
|
3189
|
+
const metadata = rolloutPath ? await readCodexSessionMeta(path.join(localHome, "sessions"), rolloutPath, binding.threadId) : void 0;
|
|
3190
|
+
if (!metadata) throw fail("its native tool catalog could not be read from the selected Codex home");
|
|
3191
|
+
const recordedTools = metadata.dynamic_tools ?? [];
|
|
3192
|
+
if (!Array.isArray(recordedTools) || codexDynamicToolsFingerprint(recordedTools) !== dynamicToolsFingerprint) throw fail("its immutable native tool catalog does not match the current OpenClaw tools");
|
|
3193
|
+
assertCurrent();
|
|
3194
|
+
await releaseSubscription(assertCurrent);
|
|
3195
|
+
assertCurrent();
|
|
3196
|
+
return {
|
|
3197
|
+
assertConfigured: () => {
|
|
3198
|
+
assertCurrent();
|
|
3199
|
+
if (!unloaded) throw fail("Codex did not confirm unloading its previous configuration");
|
|
3200
|
+
},
|
|
3201
|
+
assertCurrent,
|
|
3202
|
+
dispose
|
|
3203
|
+
};
|
|
3204
|
+
} catch (error) {
|
|
3205
|
+
dispose();
|
|
3206
|
+
throw error;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
//#endregion
|
|
3210
|
+
//#region extensions/codex/src/app-server/native-hook-relay-state.ts
|
|
3211
|
+
const pending = /* @__PURE__ */ new Set();
|
|
3212
|
+
/** Owns delayed hook-relay cleanup across runtime scheduling and test teardown. */
|
|
3213
|
+
const nativeHookRelayUnregisterQueue = {
|
|
3214
|
+
add(entry) {
|
|
3215
|
+
pending.add(entry);
|
|
3216
|
+
},
|
|
3217
|
+
delete(entry) {
|
|
3218
|
+
return pending.delete(entry);
|
|
3219
|
+
},
|
|
3220
|
+
flush() {
|
|
3221
|
+
while (pending.size > 0) {
|
|
3222
|
+
const entry = pending.values().next().value;
|
|
3223
|
+
if (!entry) return;
|
|
3224
|
+
clearTimeout(entry.timeout);
|
|
3225
|
+
entry.unregister();
|
|
3226
|
+
}
|
|
3227
|
+
},
|
|
3228
|
+
clear() {
|
|
3229
|
+
for (const entry of pending) clearTimeout(entry.timeout);
|
|
3230
|
+
pending.clear();
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3233
|
+
//#endregion
|
|
3234
|
+
//#region extensions/codex/src/app-server/native-hook-relay.ts
|
|
3235
|
+
/**
|
|
3236
|
+
* Bridges Codex native hook callbacks into OpenClaw's native hook relay so
|
|
3237
|
+
* app-server tool events can still run OpenClaw policy and diagnostics.
|
|
3238
|
+
*/
|
|
3239
|
+
/** Codex hook events that can be registered through OpenClaw's native relay. */
|
|
3240
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
|
|
3241
|
+
"pre_tool_use",
|
|
3242
|
+
"post_tool_use",
|
|
3243
|
+
"permission_request",
|
|
3244
|
+
"before_agent_finalize"
|
|
3245
|
+
];
|
|
3246
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
|
|
3247
|
+
const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
|
|
3248
|
+
/** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
|
|
3249
|
+
const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 6e4;
|
|
3250
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
|
|
3251
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
|
|
3252
|
+
const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
|
|
3253
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
|
|
3254
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
|
|
3255
|
+
const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
|
|
3256
|
+
const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
|
|
3257
|
+
const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
|
|
3258
|
+
exec: [
|
|
3259
|
+
"Bash",
|
|
3260
|
+
"exec",
|
|
3261
|
+
"exec_command"
|
|
3262
|
+
],
|
|
3263
|
+
apply_patch: [
|
|
3264
|
+
"apply_patch",
|
|
3265
|
+
"Write",
|
|
3266
|
+
"Edit"
|
|
3267
|
+
],
|
|
3268
|
+
spawn_agent: ["spawn_agent", "Agent"]
|
|
3269
|
+
};
|
|
3270
|
+
/** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
|
|
3271
|
+
async function assertCodexNativeHookRelayAllowed(client, signal) {
|
|
3272
|
+
let attestation = nativeHookPolicyByClient.get(client);
|
|
3273
|
+
if (!attestation) {
|
|
3274
|
+
attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
|
|
3275
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
|
|
3276
|
+
const requirements = response.requirements;
|
|
3277
|
+
if (requirements === null) return;
|
|
3278
|
+
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
|
|
3279
|
+
const managedOnly = requirements.allowManagedHooksOnly;
|
|
3280
|
+
if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
|
|
3281
|
+
if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
|
|
3282
|
+
});
|
|
3283
|
+
nativeHookPolicyByClient.set(client, attestation);
|
|
3284
|
+
attestation.catch(() => {
|
|
3285
|
+
if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
await attestation;
|
|
3289
|
+
}
|
|
3290
|
+
/** Defers relay unregister so late native hook subprocesses can still resolve. */
|
|
3291
|
+
function scheduleCodexNativeHookRelayUnregister(params) {
|
|
3292
|
+
let pending;
|
|
3293
|
+
const unregister = () => {
|
|
3294
|
+
if (!pending) return;
|
|
3295
|
+
const current = pending;
|
|
3296
|
+
pending = void 0;
|
|
3297
|
+
if (!nativeHookRelayUnregisterQueue.delete(current)) return;
|
|
3298
|
+
params.relay.unregister();
|
|
3299
|
+
};
|
|
3300
|
+
const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
|
|
3301
|
+
pending = {
|
|
3302
|
+
timeout,
|
|
3303
|
+
unregister
|
|
3304
|
+
};
|
|
3305
|
+
nativeHookRelayUnregisterQueue.add(pending);
|
|
3306
|
+
timeout.unref();
|
|
3307
|
+
}
|
|
3308
|
+
/** Computes the delayed unregister window from Codex's hook timeout. */
|
|
3309
|
+
function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
|
|
3310
|
+
const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
|
|
3311
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
|
|
3312
|
+
}
|
|
3313
|
+
/** Records a native pre-tool failure that Codex does not project as a tool item. */
|
|
3314
|
+
function emitCodexNativePreToolUseFailureDiagnostic(params) {
|
|
3315
|
+
emitTrustedDiagnosticEvent({
|
|
3316
|
+
type: "tool.execution.error",
|
|
3317
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
3318
|
+
sessionId: params.sessionId,
|
|
3319
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
3320
|
+
runId: params.runId,
|
|
3321
|
+
toolName: params.failure.toolName,
|
|
3322
|
+
toolCallId: params.failure.toolCallId,
|
|
3323
|
+
durationMs: params.failure.durationMs,
|
|
3324
|
+
errorCategory: "before_tool_call",
|
|
3325
|
+
terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
|
|
3326
|
+
...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
/** Registers an OpenClaw native hook relay for a Codex app-server turn. */
|
|
3330
|
+
function createCodexNativeHookRelay(params) {
|
|
3331
|
+
if (params.options?.enabled === false) return;
|
|
3332
|
+
const directChildClaims = /* @__PURE__ */ new Map();
|
|
3333
|
+
const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
|
|
3334
|
+
let foregroundClosed = false;
|
|
3335
|
+
let successfulYieldRetentionAuthorized = false;
|
|
3336
|
+
const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
|
|
3337
|
+
const rejectPendingAdmissions = (reason) => {
|
|
3338
|
+
for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
|
|
3339
|
+
pendingDirectChildAdmissions.clear();
|
|
3340
|
+
};
|
|
3341
|
+
const relay = registerRetainedNativeHookRelayForBundledRuntime({
|
|
3342
|
+
provider: "codex",
|
|
3343
|
+
relayId: buildCodexNativeHookRelayId({
|
|
3344
|
+
agentId: params.agentId,
|
|
3345
|
+
sessionId: params.sessionId,
|
|
3346
|
+
sessionKey: params.sessionKey
|
|
3347
|
+
}),
|
|
3348
|
+
...params.generation ? { generation: params.generation } : {},
|
|
3349
|
+
...params.generationMismatchGraceMs ? { generationMismatchGraceMs: params.generationMismatchGraceMs } : {},
|
|
3350
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
3351
|
+
sessionId: params.sessionId,
|
|
3352
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
3353
|
+
...params.config ? { config: params.config } : {},
|
|
3354
|
+
runId: params.runId,
|
|
3355
|
+
...params.channelId ? { channelId: params.channelId } : {},
|
|
3356
|
+
...params.requester ? { requester: params.requester } : {},
|
|
3357
|
+
...params.approvalContext ? { approvalContext: params.approvalContext } : {},
|
|
3358
|
+
allowedEvents: params.events,
|
|
3359
|
+
preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
|
|
3360
|
+
ttlMs: resolveCodexNativeHookRelayTtlMs({
|
|
3361
|
+
explicitTtlMs: params.options?.ttlMs,
|
|
3362
|
+
attemptTimeoutMs: params.attemptTimeoutMs,
|
|
3363
|
+
startupTimeoutMs: params.startupTimeoutMs,
|
|
3364
|
+
turnStartTimeoutMs: params.turnStartTimeoutMs
|
|
3365
|
+
}),
|
|
3366
|
+
signal: params.signal,
|
|
3367
|
+
runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
|
|
3368
|
+
assertActive: params.hostCapabilities.assertActive,
|
|
3369
|
+
retention: {
|
|
3370
|
+
readClaim: readCodexNativeChildThreadId,
|
|
3371
|
+
shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
|
|
3372
|
+
allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
|
|
3373
|
+
awaitForegroundAdmission: (childThreadId) => {
|
|
3374
|
+
if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
|
|
3375
|
+
const existingClaim = directChildClaims.get(childThreadId);
|
|
3376
|
+
if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
|
|
3377
|
+
const existingPending = pendingDirectChildAdmissions.get(childThreadId);
|
|
3378
|
+
if (existingPending) return existingPending.promise.then((claim) => assertClaim(childThreadId, claim));
|
|
3379
|
+
if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
|
|
3380
|
+
const { promise, resolve, reject } = createDeferred();
|
|
3381
|
+
pendingDirectChildAdmissions.set(childThreadId, {
|
|
3382
|
+
promise,
|
|
3383
|
+
resolve,
|
|
3384
|
+
reject
|
|
3385
|
+
});
|
|
3386
|
+
return promise.then((claim) => assertClaim(childThreadId, claim));
|
|
3387
|
+
},
|
|
3388
|
+
onDispose: () => {
|
|
3389
|
+
foregroundClosed = true;
|
|
3390
|
+
rejectPendingAdmissions("native hook relay registration closed");
|
|
3391
|
+
}
|
|
3392
|
+
},
|
|
3393
|
+
onPreToolUseFailure: params.onPreToolUseFailure,
|
|
3394
|
+
command: {
|
|
3395
|
+
nice: 10,
|
|
3396
|
+
timeoutMs: params.options?.gatewayTimeoutMs
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
const unregister = () => {
|
|
3400
|
+
foregroundClosed = true;
|
|
3401
|
+
rejectPendingAdmissions("native hook relay foreground closed");
|
|
3402
|
+
relay.unregister();
|
|
3403
|
+
};
|
|
3404
|
+
return {
|
|
3405
|
+
...relay,
|
|
3406
|
+
unregister,
|
|
3407
|
+
authorizeRetentionAfterSuccessfulYield: () => {
|
|
3408
|
+
successfulYieldRetentionAuthorized = true;
|
|
3409
|
+
},
|
|
3410
|
+
hasClaimedDirectChild: () => directChildClaims.size > 0,
|
|
3411
|
+
rejectPendingDirectChild: (threadIdInput, reason) => {
|
|
3412
|
+
const threadId = threadIdInput.trim();
|
|
3413
|
+
const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
|
|
3414
|
+
if (!pending) return;
|
|
3415
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
3416
|
+
pending.reject(new Error(reason));
|
|
3417
|
+
},
|
|
3418
|
+
claimDirectChild: (threadIdInput) => {
|
|
3419
|
+
const threadId = threadIdInput.trim();
|
|
3420
|
+
if (!threadId) return () => void 0;
|
|
3421
|
+
if (directChildClaims.get(threadId)) return () => void 0;
|
|
3422
|
+
const claim = Symbol(threadId);
|
|
3423
|
+
directChildClaims.set(threadId, claim);
|
|
3424
|
+
const pending = pendingDirectChildAdmissions.get(threadId);
|
|
3425
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
3426
|
+
pending?.resolve(claim);
|
|
3427
|
+
let released = false;
|
|
3428
|
+
return () => {
|
|
3429
|
+
if (released) return;
|
|
3430
|
+
released = true;
|
|
3431
|
+
if (directChildClaims.get(threadId) !== claim) return;
|
|
3432
|
+
directChildClaims.delete(threadId);
|
|
3433
|
+
if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
function readCodexNativeChildThreadId(rawPayload) {
|
|
3439
|
+
if (!isJsonObject(rawPayload) || typeof rawPayload.agent_id !== "string") return;
|
|
3440
|
+
return rawPayload.agent_id.trim() || void 0;
|
|
3441
|
+
}
|
|
3442
|
+
/** Selects the native hook events Codex should install for the current approval mode. */
|
|
3443
|
+
function resolveCodexNativeHookRelayEvents(params) {
|
|
3444
|
+
if (params.configuredEvents?.length) return params.configuredEvents;
|
|
3445
|
+
return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
|
|
3446
|
+
}
|
|
3447
|
+
/** Derives the native hook relay TTL from the turn budget unless explicitly configured. */
|
|
3448
|
+
function resolveCodexNativeHookRelayTtlMs(params) {
|
|
3449
|
+
if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
|
|
3450
|
+
const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
|
|
3451
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
|
|
3452
|
+
}
|
|
3453
|
+
/** Builds a stable relay id scoped to the agent and session identity. */
|
|
3454
|
+
function buildCodexNativeHookRelayId(params) {
|
|
3455
|
+
const hash = createHash("sha256");
|
|
3456
|
+
hash.update("openclaw:codex:native-hook-relay:v1");
|
|
3457
|
+
hash.update("\0");
|
|
3458
|
+
hash.update(params.agentId?.trim() || "");
|
|
3459
|
+
hash.update("\0");
|
|
3460
|
+
hash.update(params.sessionKey?.trim() || params.sessionId);
|
|
3461
|
+
return `codex-${hash.digest("hex").slice(0, 40)}`;
|
|
3462
|
+
}
|
|
3463
|
+
const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
|
|
3464
|
+
pre_tool_use: "PreToolUse",
|
|
3465
|
+
post_tool_use: "PostToolUse",
|
|
3466
|
+
permission_request: "PermissionRequest",
|
|
3467
|
+
before_agent_finalize: "Stop"
|
|
3468
|
+
};
|
|
3469
|
+
const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
|
|
3470
|
+
pre_tool_use: "pre_tool_use",
|
|
3471
|
+
post_tool_use: "post_tool_use",
|
|
3472
|
+
permission_request: "permission_request",
|
|
3473
|
+
before_agent_finalize: "stop"
|
|
3474
|
+
};
|
|
3475
|
+
const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
|
|
3476
|
+
/** Builds the Codex config overlay that installs trusted command hooks for relay events. */
|
|
3477
|
+
function buildCodexNativeHookRelayConfig(params) {
|
|
3478
|
+
const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
|
|
3479
|
+
const selectedEvents = new Set(events);
|
|
3480
|
+
const config = { "features.hooks": true };
|
|
3481
|
+
const hookState = {};
|
|
3482
|
+
for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
|
|
3483
|
+
const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
|
|
3484
|
+
const selected = selectedEvents.has(event);
|
|
3485
|
+
const shouldRelay = params.relay.shouldRelayEvent(event);
|
|
3486
|
+
if (!selected || !shouldRelay) {
|
|
3487
|
+
if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
|
|
3488
|
+
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 };
|
|
3489
|
+
continue;
|
|
3490
|
+
}
|
|
3491
|
+
const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
|
|
3492
|
+
const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
|
|
3493
|
+
const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
|
|
3494
|
+
config[`hooks.${codexEvent}`] = [{
|
|
3495
|
+
...matcher ? { matcher } : {},
|
|
3496
|
+
hooks: [{
|
|
3497
|
+
type: "command",
|
|
3498
|
+
command,
|
|
3499
|
+
timeout,
|
|
3500
|
+
async: false,
|
|
3501
|
+
statusMessage: "OpenClaw native hook relay"
|
|
3502
|
+
}]
|
|
3503
|
+
}];
|
|
3504
|
+
const state = {
|
|
3505
|
+
enabled: true,
|
|
3506
|
+
trusted_hash: codexCommandHookTrustedHash({
|
|
3507
|
+
event,
|
|
3508
|
+
command,
|
|
3509
|
+
matcher,
|
|
3510
|
+
timeout,
|
|
3511
|
+
statusMessage: "OpenClaw native hook relay"
|
|
3512
|
+
})
|
|
3513
|
+
};
|
|
3514
|
+
for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
|
|
3515
|
+
}
|
|
3516
|
+
config["hooks.state"] = hookState;
|
|
3517
|
+
return config;
|
|
3518
|
+
}
|
|
3519
|
+
/** Builds a Codex config overlay that disables native hooks and clears hook arrays. */
|
|
3520
|
+
function buildCodexNativeHookRelayDisabledConfig() {
|
|
3521
|
+
return {
|
|
3522
|
+
"features.hooks": false,
|
|
3523
|
+
"hooks.PreToolUse": [],
|
|
3524
|
+
"hooks.PostToolUse": [],
|
|
3525
|
+
"hooks.PermissionRequest": [],
|
|
3526
|
+
"hooks.Stop": []
|
|
3527
|
+
};
|
|
3528
|
+
}
|
|
3529
|
+
function normalizeHookTimeoutSec(value) {
|
|
3530
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC;
|
|
3531
|
+
}
|
|
3532
|
+
function resolveCodexNativeHookRelayCommandTimeoutMs(hookTimeoutSec) {
|
|
3533
|
+
const parentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 5e3;
|
|
3534
|
+
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)));
|
|
3535
|
+
return Math.max(1, parentTimeoutMs - parentMarginMs);
|
|
3536
|
+
}
|
|
3537
|
+
function buildCodexNativeToolMatcher(toolNames) {
|
|
3538
|
+
if (toolNames === void 0) return;
|
|
3539
|
+
if (toolNames.length === 0) throw new TypeError("Codex native hook matcher requires at least one tool name");
|
|
3540
|
+
const nativeNames = /* @__PURE__ */ new Set();
|
|
3541
|
+
let hasCustomToolName = false;
|
|
3542
|
+
for (const toolName of toolNames) {
|
|
3543
|
+
const canonicalToolName = toolName.trim();
|
|
3544
|
+
if (!canonicalToolName || canonicalToolName === "*") throw new TypeError("Codex native hook matcher requires canonical OpenClaw tool ids");
|
|
3545
|
+
const nativeAliases = CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID[canonicalToolName];
|
|
3546
|
+
if (!nativeAliases) hasCustomToolName = true;
|
|
3547
|
+
for (const nativeName of nativeAliases ?? [canonicalToolName]) nativeNames.add(nativeName);
|
|
3548
|
+
}
|
|
3549
|
+
const sortedNames = Array.from(nativeNames).toSorted();
|
|
3550
|
+
if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
|
|
3551
|
+
return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
|
|
3552
|
+
}
|
|
3553
|
+
function codexCommandHookTrustedHash(params) {
|
|
3554
|
+
const identity = {
|
|
3555
|
+
event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
|
|
3556
|
+
...params.matcher ? { matcher: params.matcher } : {},
|
|
3557
|
+
hooks: [{
|
|
3558
|
+
async: false,
|
|
3559
|
+
command: params.command,
|
|
3560
|
+
statusMessage: params.statusMessage,
|
|
3561
|
+
timeout: params.timeout,
|
|
3562
|
+
type: "command"
|
|
3563
|
+
}]
|
|
3564
|
+
};
|
|
3565
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
|
|
3566
|
+
}
|
|
3567
|
+
function sortJsonValue(value) {
|
|
3568
|
+
if (!value || typeof value !== "object") return value;
|
|
3569
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
3570
|
+
const sorted = {};
|
|
3571
|
+
for (const [key, entry] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) sorted[key] = sortJsonValue(entry);
|
|
3572
|
+
return sorted;
|
|
3573
|
+
}
|
|
3574
|
+
//#endregion
|
|
2961
3575
|
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
2962
3576
|
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
2963
3577
|
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
@@ -3056,6 +3670,13 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
|
3056
3670
|
//#endregion
|
|
3057
3671
|
//#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
|
|
3058
3672
|
async function prepareCodexThreadLifecyclePreflight(params) {
|
|
3673
|
+
await assertCodexModelBackedReviewerEffectiveConfig({
|
|
3674
|
+
client: params.client,
|
|
3675
|
+
approvalsReviewer: params.appServer.approvalsReviewer,
|
|
3676
|
+
cwd: params.cwd,
|
|
3677
|
+
signal: params.signal
|
|
3678
|
+
});
|
|
3679
|
+
if (params.nativeHookRelayRequired) await assertCodexNativeHookRelayAllowed(params.client, params.signal);
|
|
3059
3680
|
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
3060
3681
|
...params.timing,
|
|
3061
3682
|
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
@@ -3072,11 +3693,12 @@ async function prepareCodexThreadLifecyclePreflight(params) {
|
|
|
3072
3693
|
})).threadConfig);
|
|
3073
3694
|
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
3074
3695
|
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
3075
|
-
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await
|
|
3696
|
+
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRun({
|
|
3697
|
+
run: params.params,
|
|
3698
|
+
cwd: params.cwd,
|
|
3076
3699
|
agentId: params.agentId ?? params.params.agentId,
|
|
3077
|
-
agentDir: params.params.agentDir,
|
|
3078
3700
|
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
3079
|
-
|
|
3701
|
+
warn: (message) => embeddedAgentLog.warn(message),
|
|
3080
3702
|
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
3081
3703
|
serverName,
|
|
3082
3704
|
error: formatErrorMessage(error)
|
|
@@ -3136,118 +3758,6 @@ async function prepareCodexThreadLifecyclePreflight(params) {
|
|
|
3136
3758
|
};
|
|
3137
3759
|
}
|
|
3138
3760
|
//#endregion
|
|
3139
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
|
|
3140
|
-
/** Preserves the caller's abort reason across thread ownership transitions. */
|
|
3141
|
-
function throwIfCodexThreadLifecycleAborted(signal) {
|
|
3142
|
-
if (!signal?.aborted) return;
|
|
3143
|
-
const reason = signal.reason;
|
|
3144
|
-
if (reason instanceof Error) throw reason;
|
|
3145
|
-
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
3146
|
-
error.name = "AbortError";
|
|
3147
|
-
throw error;
|
|
3148
|
-
}
|
|
3149
|
-
/** Releases consumed subscription ownership or retires an unsafe client. */
|
|
3150
|
-
async function releaseCodexConsumedLiveThread(options) {
|
|
3151
|
-
if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
|
|
3152
|
-
threadId: options.threadId,
|
|
3153
|
-
timeoutMs: 5e3
|
|
3154
|
-
}))) return;
|
|
3155
|
-
await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
|
|
3156
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, options.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
3157
|
-
}
|
|
3158
|
-
/** Transfers retained-slot ownership before unconditionally releasing it. */
|
|
3159
|
-
async function releaseCodexRetainedLiveThread(options) {
|
|
3160
|
-
if (await consumeCodexAppServerLiveThread(options.client, options.threadId)) await releaseCodexConsumedLiveThread(options);
|
|
3161
|
-
}
|
|
3162
|
-
/** Reuses one safely owned, fully matching subscription on its original client. */
|
|
3163
|
-
async function tryReuseCodexLiveThread(options) {
|
|
3164
|
-
const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, releaseConsumedThread, ringZeroActive, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
|
|
3165
|
-
if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive) return {};
|
|
3166
|
-
const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
|
|
3167
|
-
action: "resume",
|
|
3168
|
-
binding
|
|
3169
|
-
}) ?? {
|
|
3170
|
-
configPatch: params.finalConfigPatch,
|
|
3171
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
3172
|
-
};
|
|
3173
|
-
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0;
|
|
3174
|
-
const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
|
|
3175
|
-
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
|
|
3176
|
-
const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
3177
|
-
threadId: binding.threadId,
|
|
3178
|
-
cwd: params.cwd,
|
|
3179
|
-
authProfileId: resumeAuthProfileId,
|
|
3180
|
-
model: startModelSelection.model,
|
|
3181
|
-
modelProvider: startModelProvider,
|
|
3182
|
-
preserveNativeModel: false,
|
|
3183
|
-
appServer: params.appServer,
|
|
3184
|
-
dynamicTools: params.dynamicTools,
|
|
3185
|
-
developerInstructions: params.developerInstructions,
|
|
3186
|
-
config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
|
|
3187
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
3188
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3189
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
3190
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
3191
|
-
hostSystemAgentActive,
|
|
3192
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3193
|
-
shellEnvironment: params.shellEnvironment,
|
|
3194
|
-
disableLoginShell: params.disableLoginShell
|
|
3195
|
-
}));
|
|
3196
|
-
const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
|
|
3197
|
-
...resumeParams,
|
|
3198
|
-
model: binding.model ?? resumeParams.model ?? null,
|
|
3199
|
-
requestedModel: resumeParams.model ?? null,
|
|
3200
|
-
modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
|
|
3201
|
-
requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
|
|
3202
|
-
}, resumeAuthProfileId, dynamicToolsFingerprint);
|
|
3203
|
-
const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId, liveThreadConfigFingerprint);
|
|
3204
|
-
if (!retainedThread) {
|
|
3205
|
-
const incompatibleOwnership = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
|
|
3206
|
-
if (incompatibleOwnership) await incompatibleOwnership.release(binding.threadId);
|
|
3207
|
-
return { prebuiltFinalConfigPatch };
|
|
3208
|
-
}
|
|
3209
|
-
try {
|
|
3210
|
-
const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
|
|
3211
|
-
const model = startModelSelection.model;
|
|
3212
|
-
if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
3213
|
-
kind: "patch",
|
|
3214
|
-
threadId: binding.threadId,
|
|
3215
|
-
patch: {
|
|
3216
|
-
cwd: params.cwd,
|
|
3217
|
-
model,
|
|
3218
|
-
nativeHookRelayGeneration,
|
|
3219
|
-
environmentSelectionFingerprint
|
|
3220
|
-
}
|
|
3221
|
-
}))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
|
|
3222
|
-
throwIfAborted();
|
|
3223
|
-
lifecycleTiming.mark("thread-ready");
|
|
3224
|
-
lifecycleTiming.logSummary({
|
|
3225
|
-
runId: params.params.runId,
|
|
3226
|
-
sessionId: params.params.sessionId,
|
|
3227
|
-
sessionKey: params.params.sessionKey,
|
|
3228
|
-
threadId: binding.threadId,
|
|
3229
|
-
action: "resumed"
|
|
3230
|
-
});
|
|
3231
|
-
return {
|
|
3232
|
-
binding: {
|
|
3233
|
-
...binding,
|
|
3234
|
-
cwd: params.cwd,
|
|
3235
|
-
model,
|
|
3236
|
-
nativeHookRelayGeneration,
|
|
3237
|
-
environmentSelectionFingerprint,
|
|
3238
|
-
liveThreadConfigFingerprint,
|
|
3239
|
-
liveThreadOwnership: retainedThread,
|
|
3240
|
-
...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
|
|
3241
|
-
lifecycle: { action: "resumed" }
|
|
3242
|
-
},
|
|
3243
|
-
prebuiltFinalConfigPatch
|
|
3244
|
-
};
|
|
3245
|
-
} catch (error) {
|
|
3246
|
-
await releaseConsumedThread(binding.threadId, error);
|
|
3247
|
-
throw error;
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
//#endregion
|
|
3251
3761
|
//#region extensions/codex/src/app-server/thread-supervision.ts
|
|
3252
3762
|
async function materializePendingSupervisionBranch(params) {
|
|
3253
3763
|
let pending = params.binding.pendingSupervisionBranch;
|
|
@@ -3271,6 +3781,32 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3271
3781
|
});
|
|
3272
3782
|
let bindingCommitted = false;
|
|
3273
3783
|
let provisionalCleanupSafe = true;
|
|
3784
|
+
let cleanupExpected = pending;
|
|
3785
|
+
const trackPendingSupervisionArtifacts = async (cleanupThreadIds) => {
|
|
3786
|
+
const expected = pending;
|
|
3787
|
+
pending = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
3788
|
+
let updated;
|
|
3789
|
+
try {
|
|
3790
|
+
updated = await params.bindingStore.mutate(params.bindingIdentity, {
|
|
3791
|
+
kind: "patch-pending-supervision-branch",
|
|
3792
|
+
expected,
|
|
3793
|
+
pending
|
|
3794
|
+
});
|
|
3795
|
+
} catch (error) {
|
|
3796
|
+
try {
|
|
3797
|
+
const current = await params.bindingStore.read(params.bindingIdentity);
|
|
3798
|
+
if (matchesPendingSupervisionState(current, pending)) cleanupExpected = pending;
|
|
3799
|
+
else if (matchesPendingSupervisionState(current, expected)) cleanupExpected = expected;
|
|
3800
|
+
else throw new CodexThreadBindingConflictError(pending.sourceThreadId, "verifying supervised Codex cleanup tracking");
|
|
3801
|
+
} catch (verificationError) {
|
|
3802
|
+
provisionalCleanupSafe = false;
|
|
3803
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup tracking could not be verified: ${cleanupThreadIds.join(", ")}`, { cause: new AggregateError([error, verificationError], void 0, { cause: error }) });
|
|
3804
|
+
}
|
|
3805
|
+
throw error;
|
|
3806
|
+
}
|
|
3807
|
+
cleanupExpected = updated ? pending : void 0;
|
|
3808
|
+
if (!updated) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
3809
|
+
};
|
|
3274
3810
|
try {
|
|
3275
3811
|
const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
|
|
3276
3812
|
const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
|
|
@@ -3286,10 +3822,17 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3286
3822
|
sourceThreadId: pending.sourceThreadId,
|
|
3287
3823
|
role: "model probe"
|
|
3288
3824
|
});
|
|
3289
|
-
|
|
3825
|
+
let probeResponse;
|
|
3826
|
+
try {
|
|
3827
|
+
params.throwIfAborted();
|
|
3828
|
+
probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
3829
|
+
if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
|
|
3830
|
+
} finally {
|
|
3831
|
+
await unsubscribeCodexAppServerLiveThread(params.client, probeThreadId, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS).catch((cause) => {
|
|
3832
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex model probe subscription could not be released: ${probeThreadId}`, { cause });
|
|
3833
|
+
});
|
|
3834
|
+
}
|
|
3290
3835
|
params.throwIfAborted();
|
|
3291
|
-
const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
3292
|
-
if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
|
|
3293
3836
|
const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
|
|
3294
3837
|
const nativeModelProvider = requireNativeSupervisionModelProvider({
|
|
3295
3838
|
responseModelProvider: probeResponse.modelProvider,
|
|
@@ -3335,7 +3878,7 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3335
3878
|
otherThreadId: probeThreadId,
|
|
3336
3879
|
role: "canonical branch"
|
|
3337
3880
|
});
|
|
3338
|
-
|
|
3881
|
+
await trackPendingSupervisionArtifacts([finalThreadId]);
|
|
3339
3882
|
params.throwIfAborted();
|
|
3340
3883
|
assertExactSupervisionModelSelection(assertCodexThreadStartResponse(rawStartResponse), {
|
|
3341
3884
|
model: nativeModel,
|
|
@@ -3355,11 +3898,11 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3355
3898
|
client: params.client,
|
|
3356
3899
|
threadId: finalThreadId,
|
|
3357
3900
|
ephemeral: startParams.ephemeral === true
|
|
3358
|
-
})
|
|
3901
|
+
})) {
|
|
3359
3902
|
provisionalCleanupSafe = false;
|
|
3360
3903
|
throw new CodexAppServerUnsafeSubscriptionError("Codex supervised plugin app attestation cleanup failed", { cause: error });
|
|
3361
3904
|
}
|
|
3362
|
-
|
|
3905
|
+
await trackPendingSupervisionArtifacts([]);
|
|
3363
3906
|
throw error;
|
|
3364
3907
|
}
|
|
3365
3908
|
if (history.responseItems.length > 0) {
|
|
@@ -3369,8 +3912,6 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3369
3912
|
}, { signal: params.signal }));
|
|
3370
3913
|
params.throwIfAborted();
|
|
3371
3914
|
}
|
|
3372
|
-
if (!await archiveSupervisionArtifact(params.client, probeThreadId)) throw new Error(`Failed to archive temporary Codex model probe: ${probeThreadId}`);
|
|
3373
|
-
pending = await trackPendingSupervisionArtifacts(params, pending, [finalThreadId]);
|
|
3374
3915
|
const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
|
|
3375
3916
|
const bindingModelProvider = params.normalizeBindingModelProvider(params.attempt.authProfileId, nativeModelProvider);
|
|
3376
3917
|
let committed = false;
|
|
@@ -3432,31 +3973,28 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3432
3973
|
};
|
|
3433
3974
|
} catch (error) {
|
|
3434
3975
|
if (bindingCommitted) throw error;
|
|
3435
|
-
if (error instanceof CodexThreadBindingConflictAfterCleanupError) throw error;
|
|
3436
3976
|
if (!provisionalCleanupSafe) {
|
|
3437
3977
|
await params.abandonClient();
|
|
3438
3978
|
throw error;
|
|
3439
3979
|
}
|
|
3440
3980
|
const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
|
|
3981
|
+
const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
|
|
3441
3982
|
let cleanupStateError;
|
|
3442
|
-
if (
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
} catch (stateError) {
|
|
3451
|
-
cleanupStateError = stateError;
|
|
3452
|
-
}
|
|
3983
|
+
if (cleanupExpected && !isDeepStrictEqual(cleanupExpected, nextPending)) try {
|
|
3984
|
+
await params.bindingStore.mutate(params.bindingIdentity, {
|
|
3985
|
+
kind: "patch-pending-supervision-branch",
|
|
3986
|
+
expected: cleanupExpected,
|
|
3987
|
+
pending: nextPending
|
|
3988
|
+
});
|
|
3989
|
+
} catch (stateError) {
|
|
3990
|
+
cleanupStateError = stateError;
|
|
3453
3991
|
}
|
|
3454
3992
|
const unsafeCleanup = cleanup.remaining.length > 0 || isCodexAppServerUnsafeSubscriptionError(error);
|
|
3455
3993
|
if (unsafeCleanup) await params.abandonClient();
|
|
3456
3994
|
if (cleanupStateError) {
|
|
3457
|
-
const cause = new AggregateError([error, cleanupStateError]);
|
|
3995
|
+
const cause = new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
|
|
3458
3996
|
if (unsafeCleanup) throw new CodexAppServerUnsafeSubscriptionError("Codex supervised branch cleanup state could not be recorded", { cause });
|
|
3459
|
-
throw
|
|
3997
|
+
throw cause;
|
|
3460
3998
|
}
|
|
3461
3999
|
if (cleanup.remaining.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup remains pending: ${cleanup.remaining.join(", ")}`, { cause: error });
|
|
3462
4000
|
throw error;
|
|
@@ -3484,7 +4022,7 @@ function buildPendingSupervisionProbeForkParams(params, pending) {
|
|
|
3484
4022
|
...params.appServer.serviceTier !== void 0 ? { serviceTier: params.appServer.serviceTier } : {},
|
|
3485
4023
|
config: runtimeConfig,
|
|
3486
4024
|
developerInstructions: params.developerInstructions ?? buildDeveloperInstructions(params.attempt, { dynamicTools: params.dynamicTools }),
|
|
3487
|
-
ephemeral:
|
|
4025
|
+
ephemeral: true,
|
|
3488
4026
|
threadSource: "appServer",
|
|
3489
4027
|
excludeTurns: true
|
|
3490
4028
|
};
|
|
@@ -3526,10 +4064,8 @@ function requireDistinctSupervisionThreadId(params) {
|
|
|
3526
4064
|
return threadId;
|
|
3527
4065
|
}
|
|
3528
4066
|
function readSupervisionResponseThreadId(value) {
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
if (!thread || typeof thread !== "object" || Array.isArray(thread)) return;
|
|
3532
|
-
return thread.id;
|
|
4067
|
+
const thread = isRecord(value) ? value.thread : void 0;
|
|
4068
|
+
return isRecord(thread) ? thread.id : void 0;
|
|
3533
4069
|
}
|
|
3534
4070
|
async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
3535
4071
|
if (!pending.cleanupThreadIds?.length) return pending;
|
|
@@ -3552,20 +4088,6 @@ async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
|
3552
4088
|
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
|
|
3553
4089
|
return next;
|
|
3554
4090
|
}
|
|
3555
|
-
async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
|
|
3556
|
-
const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
3557
|
-
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
3558
|
-
kind: "patch-pending-supervision-branch",
|
|
3559
|
-
expected: pending,
|
|
3560
|
-
pending: next
|
|
3561
|
-
})) {
|
|
3562
|
-
const cleanupFailed = [];
|
|
3563
|
-
for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
|
|
3564
|
-
if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
|
|
3565
|
-
throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
3566
|
-
}
|
|
3567
|
-
return next;
|
|
3568
|
-
}
|
|
3569
4091
|
function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
|
|
3570
4092
|
return {
|
|
3571
4093
|
sourceThreadId: pending.sourceThreadId,
|
|
@@ -3602,15 +4124,10 @@ async function archiveSupervisionArtifact(client, threadId) {
|
|
|
3602
4124
|
async function startOrResumeThread(params) {
|
|
3603
4125
|
const incognito = isIncognitoSessionKey(params.params.sessionKey);
|
|
3604
4126
|
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
config: params.params.config
|
|
3610
|
-
});
|
|
3611
|
-
return await params.bindingStore.withLease(bindingIdentity, async () => {
|
|
3612
|
-
const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = await prepareCodexThreadLifecyclePreflight(params);
|
|
3613
|
-
let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
|
|
4127
|
+
return await withCodexThreadLifecycleBinding(params, async (bindingIdentity, currentBinding) => {
|
|
4128
|
+
let binding = currentBinding;
|
|
4129
|
+
const preflight = await prepareCodexThreadLifecyclePreflight(params);
|
|
4130
|
+
const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = preflight;
|
|
3614
4131
|
let replacementPredecessor;
|
|
3615
4132
|
const initialBoundThreadId = binding?.threadId;
|
|
3616
4133
|
const initialBoundClientId = binding?.clientId;
|
|
@@ -3622,26 +4139,33 @@ async function startOrResumeThread(params) {
|
|
|
3622
4139
|
config: params.params.config
|
|
3623
4140
|
});
|
|
3624
4141
|
const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
|
|
3625
|
-
const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId) => {
|
|
4142
|
+
const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId, assertCurrent) => {
|
|
3626
4143
|
if (ownerClientId && ownerClientId !== clientId) {
|
|
3627
4144
|
const previousClient = retainSharedCodexAppServerClientByInstanceId(ownerClientId);
|
|
3628
|
-
if (!previousClient) return;
|
|
4145
|
+
if (!previousClient) return false;
|
|
3629
4146
|
try {
|
|
3630
|
-
|
|
4147
|
+
const assertPrevious = assertCurrent ? captureExclusiveSharedCodexAppServerClient(previousClient.client) : void 0;
|
|
4148
|
+
if (isCodexAppServerLiveThreadClaimed(previousClient.client, threadId)) throw new Error(`Codex thread ${threadId} is claimed by active work; stop it first.`);
|
|
4149
|
+
return await releaseCodexRetainedLiveThread({
|
|
3631
4150
|
client: previousClient.client,
|
|
3632
4151
|
lifecycleTiming,
|
|
3633
|
-
threadId
|
|
4152
|
+
threadId,
|
|
4153
|
+
assertCurrent: assertCurrent ? () => {
|
|
4154
|
+
assertCurrent();
|
|
4155
|
+
assertPrevious?.();
|
|
4156
|
+
} : void 0
|
|
3634
4157
|
});
|
|
3635
4158
|
} finally {
|
|
3636
4159
|
previousClient.release();
|
|
3637
4160
|
}
|
|
3638
|
-
return;
|
|
3639
4161
|
}
|
|
3640
|
-
|
|
4162
|
+
if (isCodexAppServerLiveThreadClaimed(params.client, threadId)) throw new Error(`Codex thread ${threadId} is claimed by active work; stop it first.`);
|
|
4163
|
+
return await releaseCodexRetainedLiveThread({
|
|
3641
4164
|
client: params.client,
|
|
3642
4165
|
abandonClient: params.abandonClient,
|
|
3643
4166
|
lifecycleTiming,
|
|
3644
|
-
threadId
|
|
4167
|
+
threadId,
|
|
4168
|
+
assertCurrent
|
|
3645
4169
|
});
|
|
3646
4170
|
};
|
|
3647
4171
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
@@ -3723,6 +4247,36 @@ async function startOrResumeThread(params) {
|
|
|
3723
4247
|
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
3724
4248
|
binding = void 0;
|
|
3725
4249
|
};
|
|
4250
|
+
const resolveRequestContext = () => {
|
|
4251
|
+
const startModelSelection = resolveCodexAppServerThreadModelSelection({
|
|
4252
|
+
provider: params.params.provider,
|
|
4253
|
+
model: params.runtimeModelId ?? params.params.modelId,
|
|
4254
|
+
binding,
|
|
4255
|
+
authProfileId: params.params.authProfileId,
|
|
4256
|
+
authProfileStore: params.params.authProfileStore,
|
|
4257
|
+
agentDir: params.params.agentDir,
|
|
4258
|
+
config: params.params.config
|
|
4259
|
+
});
|
|
4260
|
+
return {
|
|
4261
|
+
...preflight,
|
|
4262
|
+
bindingIdentity,
|
|
4263
|
+
startModelSelection,
|
|
4264
|
+
startModelProvider: startModelSelection.modelProvider,
|
|
4265
|
+
normalizeBindingModelProvider,
|
|
4266
|
+
throwIfAborted
|
|
4267
|
+
};
|
|
4268
|
+
};
|
|
4269
|
+
const transientDelegationRestriction = params.params.delegationCapability === "report_only";
|
|
4270
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
4271
|
+
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
4272
|
+
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
4273
|
+
if (binding?.pendingResumeConfiguration) return await resumePendingCodexThread(params, {
|
|
4274
|
+
...resolveRequestContext(),
|
|
4275
|
+
binding,
|
|
4276
|
+
clearCurrentBinding,
|
|
4277
|
+
releaseRetainedThread: (threadId, assertCurrent) => releaseRetainedThread(threadId, initialBoundClientId, assertCurrent),
|
|
4278
|
+
transientRestriction: transientDelegationRestriction || transientNativeToolRestriction || transientWebSearchRestriction
|
|
4279
|
+
});
|
|
3726
4280
|
if (binding?.threadId && !restrictedToolSurface && binding.nativeToolPolicyRestricted === true) await clearCurrentBinding("rotating a host-policy-restricted thread binding");
|
|
3727
4281
|
if (binding?.threadId && binding.nativeSkillIsolationFingerprint !== nativeSkillIsolationFingerprint) {
|
|
3728
4282
|
embeddedAgentLog.debug("codex app-server native skill isolation changed; starting a new thread", { threadId: binding.threadId });
|
|
@@ -3756,24 +4310,12 @@ async function startOrResumeThread(params) {
|
|
|
3756
4310
|
await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
|
|
3757
4311
|
binding = void 0;
|
|
3758
4312
|
}
|
|
3759
|
-
const
|
|
3760
|
-
|
|
3761
|
-
model: params.runtimeModelId ?? params.params.modelId,
|
|
3762
|
-
binding,
|
|
3763
|
-
authProfileId: params.params.authProfileId,
|
|
3764
|
-
authProfileStore: params.params.authProfileStore,
|
|
3765
|
-
agentDir: params.params.agentDir,
|
|
3766
|
-
config: params.params.config
|
|
3767
|
-
});
|
|
3768
|
-
const startModelProvider = startModelSelection.modelProvider;
|
|
3769
|
-
const transientDelegationRestriction = params.params.delegationCapability === "report_only";
|
|
4313
|
+
const requestContext = resolveRequestContext();
|
|
4314
|
+
const { startModelSelection, startModelProvider } = requestContext;
|
|
3770
4315
|
let preserveExistingBinding = transientDelegationRestriction || !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
3771
4316
|
let rotatedContextEngineBinding = false;
|
|
3772
4317
|
let prebuiltPluginThreadConfig;
|
|
3773
4318
|
const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
|
|
3774
|
-
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
3775
|
-
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
3776
|
-
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
3777
4319
|
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
3778
4320
|
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
3779
4321
|
if (binding?.threadId && (params.configuredMcpOwnershipVersion === 1 && (binding.configuredMcpOwnershipVersion !== 1 || binding.dynamicToolsFingerprint === void 0 || binding.mcpServersFingerprint !== void 0 || binding.userMcpServersFingerprint !== void 0) || params.configuredMcpOwnershipVersion !== 1 && binding.configuredMcpOwnershipVersion === 1) && binding?.threadId) {
|
|
@@ -3944,31 +4486,11 @@ async function startOrResumeThread(params) {
|
|
|
3944
4486
|
userMcpServersConfigPatch
|
|
3945
4487
|
});
|
|
3946
4488
|
if (warmReuse.binding) return warmReuse.binding;
|
|
4489
|
+
if (binding.preserveNativeModel === true) await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, requestContext);
|
|
3947
4490
|
await releaseRetainedThread(binding.threadId);
|
|
3948
4491
|
const resumed = await resumeExistingCodexThread(params, {
|
|
4492
|
+
...requestContext,
|
|
3949
4493
|
binding,
|
|
3950
|
-
bindingIdentity,
|
|
3951
|
-
startModelSelection,
|
|
3952
|
-
startModelProvider,
|
|
3953
|
-
userMcpServersConfigPatch,
|
|
3954
|
-
dynamicToolsFingerprint,
|
|
3955
|
-
dynamicToolsContainDeferred,
|
|
3956
|
-
webSearchThreadConfigFingerprint,
|
|
3957
|
-
nativeSkillIsolationFingerprint,
|
|
3958
|
-
userMcpServersFingerprint,
|
|
3959
|
-
ringZeroConfigFingerprint,
|
|
3960
|
-
ringZeroClientInstanceId,
|
|
3961
|
-
networkProxyConfigFingerprint,
|
|
3962
|
-
contextEngineBinding,
|
|
3963
|
-
environmentSelectionFingerprint,
|
|
3964
|
-
hostSystemAgentActive,
|
|
3965
|
-
ringZeroActive,
|
|
3966
|
-
restrictedToolSurface,
|
|
3967
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3968
|
-
nativeSkillIsolation,
|
|
3969
|
-
lifecycleTiming,
|
|
3970
|
-
normalizeBindingModelProvider,
|
|
3971
|
-
throwIfAborted,
|
|
3972
4494
|
clearCurrentBinding,
|
|
3973
4495
|
prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
|
|
3974
4496
|
prebuiltPluginThreadConfig
|
|
@@ -3977,28 +4499,7 @@ async function startOrResumeThread(params) {
|
|
|
3977
4499
|
}
|
|
3978
4500
|
if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
|
|
3979
4501
|
const started = await startFreshCodexThread(params, {
|
|
3980
|
-
|
|
3981
|
-
startModelSelection,
|
|
3982
|
-
startModelProvider,
|
|
3983
|
-
userMcpServersConfigPatch,
|
|
3984
|
-
dynamicToolsFingerprint,
|
|
3985
|
-
dynamicToolsContainDeferred,
|
|
3986
|
-
webSearchThreadConfigFingerprint,
|
|
3987
|
-
nativeSkillIsolationFingerprint,
|
|
3988
|
-
userMcpServersFingerprint,
|
|
3989
|
-
ringZeroConfigFingerprint,
|
|
3990
|
-
ringZeroClientInstanceId,
|
|
3991
|
-
networkProxyConfigFingerprint,
|
|
3992
|
-
contextEngineBinding,
|
|
3993
|
-
environmentSelectionFingerprint,
|
|
3994
|
-
hostSystemAgentActive,
|
|
3995
|
-
ringZeroActive,
|
|
3996
|
-
restrictedToolSurface,
|
|
3997
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3998
|
-
nativeSkillIsolation,
|
|
3999
|
-
lifecycleTiming,
|
|
4000
|
-
normalizeBindingModelProvider,
|
|
4001
|
-
throwIfAborted,
|
|
4502
|
+
...requestContext,
|
|
4002
4503
|
prebuiltPluginThreadConfig,
|
|
4003
4504
|
preserveExistingBinding,
|
|
4004
4505
|
rotatedContextEngineBinding,
|
|
@@ -4105,7 +4606,6 @@ function buildTurnCollaborationMode(params, options = {}) {
|
|
|
4105
4606
|
function buildTurnScopedCollaborationInstructions(params, options = {}) {
|
|
4106
4607
|
const contextInstructions = joinPresentSections(options.turnScopedDeveloperInstructions, options.memoryCollaborationInstructions, options.skillsCollaborationInstructions);
|
|
4107
4608
|
if (params.trigger === "cron") return joinPresentSections(buildCronCollaborationInstructions(), contextInstructions);
|
|
4108
|
-
if (params.trigger === "heartbeat") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions);
|
|
4109
4609
|
if (contextInstructions?.trim()) return joinPresentSections(buildDefaultCollaborationInstructions(), contextInstructions);
|
|
4110
4610
|
return null;
|
|
4111
4611
|
}
|
|
@@ -4132,15 +4632,8 @@ function buildCronCollaborationInstructions() {
|
|
|
4132
4632
|
"Keep output concise and automation-oriented. Prefer the final command result or a short failure summary over status narration."
|
|
4133
4633
|
].join("\n\n");
|
|
4134
4634
|
}
|
|
4135
|
-
function buildHeartbeatCollaborationInstructions() {
|
|
4136
|
-
return [
|
|
4137
|
-
"This is an OpenClaw heartbeat turn. Apply these instructions only to this heartbeat wake; ordinary chat turns should stay in Codex Default mode.",
|
|
4138
|
-
"When you are ready to end the heartbeat, prefer the structured `heartbeat_respond` tool so OpenClaw can record the wake outcome and notification decision. If `heartbeat_respond` is not already available and `tool_search` is available, search for `heartbeat_respond`, load it, then call it. Use `notify=false` when nothing should visibly interrupt the user.",
|
|
4139
|
-
GPT5_HEARTBEAT_PROMPT_OVERLAY
|
|
4140
|
-
].join("\n\n");
|
|
4141
|
-
}
|
|
4142
4635
|
function joinPresentSections(...sections) {
|
|
4143
4636
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
4144
4637
|
}
|
|
4145
4638
|
//#endregion
|
|
4146
|
-
export {
|
|
4639
|
+
export { resolveCodexContinuityProjectionMaxChars as $, readCodexSupportedReasoningEfforts as A, isMessageOnlyCodexSourceReply as B, buildCodexProjectDocThreadConfig as C, resolveCodexAppServerThreadModelSelection as D, resolveCodexAppServerRequestModelSelection as E, codexDynamicToolsFingerprint as F, buildContextEngineBinding as G, normalizeCodexDynamicToolName as H, codexLegacyDynamicToolsFingerprint as I, fitCodexProjectedContextForTurnStart as J, isContextEngineBindingCompatible as K, filterCodexDynamicTools as L, CODEX_MANAGED_THREAD_NAMESPACE as M, createCodexManagedThreadStore as N, resolveCodexBindingModelProviderFallback as O, areCodexDynamicToolFingerprintsCompatible as P, resolveCodexContextEngineProjectionReserveTokens as Q, filterCodexDynamicToolsForDisabledNativeSurface as R, buildDeveloperInstructions as S, resolveCodexAppServerModelProvider as T, resolveCodexDynamicToolsLoading as U, isSystemAgentOnlyCodexDynamicToolAllowlist as V, resolveCodexDynamicToolsLoadingForRuntime as W, projectContextEngineAssemblyForCodex as X, neutralizeCodexExplicitMentionSigils as Y, resolveCodexContextEngineProjectionMaxChars as Z, buildCodexRingZeroThreadConfigPatch as _, isCodexAppServerProfilerEnabled as a, buildPluginAppPolicyContext as at, buildCodexNativeWebSearchThreadConfig as b, buildCodexNativeHookRelayConfig as c, ensureCodexPluginActivation as ct, emitCodexNativePreToolUseFailureDiagnostic as d, isOpenAiCuratedMarketplaceName as dt, buildCodexPluginAppsConfigPatchFromPolicyContext as et, resolveCodexNativeHookRelayEvents as f, pluginReadParams as ft, attestCodexRestrictedToolSurfaceMcpServersDisabled as g, assertCodexManagedRequirementsDoNotOverrideToolPolicy as h, startOrResumeThread as i, buildDisabledAppsConfigPatch as it, CODEX_MANAGED_THREAD_MAX_ENTRIES as j, resolveReasoningEffort as k, buildCodexNativeHookRelayDisabledConfig as l, refreshCodexPluginRuntimeState as lt, scheduleCodexNativeHookRelayUnregister as m, buildTurnStartParams as n, buildCodexPluginThreadConfigInputFingerprint as nt, CODEX_NATIVE_HOOK_RELAY_EVENTS as o, mergeCodexThreadConfigs as ot, resolveCodexNativeHookRelayTtlMs as p, resolveRecoverableCodexPluginConfigKeys as pt, buildCodexContinuityCalibration as q, buildCodexUserInput as r, buildCodexPluginThreadConfigTimeoutFallback as rt, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as s, shouldBuildCodexPluginThreadConfig as st, buildTurnCollaborationMode as t, buildCodexPluginThreadConfig as tt, createCodexNativeHookRelay as u, isOpenAiCuratedMarketplace as ut, buildCodexRuntimeThreadConfig as v, CODEX_NATIVE_PERSONALITY_NONE as w, resolveCodexWebSearchPlan as x, readCodexInheritedMcpServerNames as y, isForcedPrivateQaCodexRuntime as z };
|