@openclaw/codex 2026.8.1-beta.3 → 2026.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +5 -6
- package/dist/{attempt-client-cleanup-URjUlCHg.js → attempt-client-cleanup-CpVfwctT.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-CF7BmrDk.js} +2 -2
- package/dist/{bounded-turn-4BjrZEwW.js → bounded-turn-DRC3hcjm.js} +16 -20
- package/dist/{capabilities-CH3K57v4.js → capabilities-D6m_TdVy.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-D0nZ3qtd.js → command-formatters--UToCokS.js} +4 -3
- package/dist/{command-handlers-DC86lgT4.js → command-handlers-CLFx2Ypa.js} +114 -79
- package/dist/{compact-COqgBRny.js → compact-5xqW1oCh.js} +13 -12
- package/dist/{config-CpNeo5AE.js → config-CcYL77O6.js} +350 -288
- package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
- package/dist/doctor-contract-api.js +121 -16
- package/dist/{dynamic-tools-fJZhl4CE.js → dynamic-tools-BmeXjwNZ.js} +243 -1002
- package/dist/{effective-mcp-catalog-4YKn51aG.js → effective-mcp-catalog-XXQKgK1U.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-B2r5vhWH.js} +9 -14
- package/dist/{harness-DoKQcF8n.js → harness-BgeB7LnD.js} +42 -52
- package/dist/harness.js +1 -1
- package/dist/index.js +308 -306
- package/dist/{isolated-completion-DPMtlH9r.js → isolated-completion-CxTus3vZ.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-DULBcniB.js +101 -0
- package/dist/{models-CZw1wClT.js → models-DaWfETQk.js} +27 -29
- package/dist/{native-subagent-monitor-GGq3RnOo.js → native-subagent-monitor-vXYUZH37.js} +226 -129
- package/dist/{node-exec-server.runtime-Bsu1LdFZ.js → node-exec-server.runtime-DirKVZ5h.js} +9 -6
- package/dist/{plugin-approval-roundtrip-C75Yiiwc.js → plugin-approval-roundtrip-CCWQGlAI.js} +2 -1
- package/dist/{plugin-marketplace-discovery-CSWDkMrP.js → plugin-marketplace-discovery-BXjIFEiw.js} +38 -46
- package/dist/{plugin-metadata-cache-BXH5NF-4.js → plugin-metadata-cache-DQQm4gWT.js} +163 -136
- package/dist/{protocol-validators-VAVyEnDp.js → protocol-validators-Bg5rpNxk.js} +148 -5
- package/dist/{rate-limits-BI1dMHDG.js → rate-limits-BqHEHXQJ.js} +3 -2
- package/dist/{sandbox-guard-C1qEeZ5C.js → request-BWxweEdK.js} +183 -2
- package/dist/{run-attempt-BV3HzuCf.js → run-attempt-BVLE4Gbi.js} +706 -517
- package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-D9pYp4zL.js} +3 -3
- package/dist/{session-binding-BTrLf9Qm.js → session-binding-D0zinCP1.js} +195 -122
- package/dist/{session-catalog-ezP_UcRM.js → session-catalog-eMhVm0Ql.js} +448 -313
- package/dist/session-catalog-home-id-Dt2GiG5o.js +112 -0
- package/dist/{session-cli-B1hKvfkI.js → session-cli-B2lyjJA0.js} +1 -1
- package/dist/session-history-C5J1AKzG.js +87 -0
- package/dist/session-permission-policy-ScnAyG90.js +160 -0
- package/dist/session-retirement-CtUtUTr9.js +90 -0
- package/dist/{session-upstream-marker-D_2npHlA.js → session-upstream-marker-gqrYIRHk.js} +2 -3
- package/dist/settled-turn-context-Bk3IUwod.js +290 -0
- package/dist/settled-turn-finalizer-BXLN9PfJ.js +104 -0
- package/dist/{shared-client-Dw-rzii0.js → shared-client-B1tT5U6r.js} +1738 -806
- package/dist/{side-question-DbXkJZzg.js → side-question-CZ-mCw-t.js} +76 -43
- package/dist/{thread-lifecycle-DKdrnTMX.js → thread-lifecycle-CGGKP720.js} +991 -455
- package/dist/{thread-ownership-i2NBwDUL.js → thread-ownership-RkaoZCmQ.js} +18 -8
- package/dist/{plugin-app-cache-key-DMNQ8Zi8.js → thread-resume-BFt1-ZDa.js} +55 -8
- package/dist/{transcript-mirror-BU0_LKAL.js → transcript-mirror-1ep4XjzF.js} +124 -18
- package/dist/transport-stdio-NT_Dpd9V.js +724 -0
- package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
- package/dist/{upstream-session-fork-BDVJCLAD.js → upstream-session-fork-Dc8BFExw.js} +76 -90
- package/dist/{usage-9dKJCgcy.js → usage-DUhd58gI.js} +3 -3
- package/dist/{web-search-provider.runtime-DCdx4DGu.js → web-search-provider.runtime-BEmlE7ov.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/settled-turn-finalizer-Dx1onIqg.js +0 -328
- 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-CcYL77O6.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, yt as isCodexAppServerClientRuntimeLive } from "./shared-client-B1tT5U6r.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-D0zinCP1.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-CpVfwctT.js";
|
|
6
|
+
import { c as serializeCodexAppInventoryError, n as buildCodexAppServerConnectionFingerprint, s as defaultCodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-BFt1-ZDa.js";
|
|
7
|
+
import { c as withExclusiveCodexAppServerThread, l as isIncognitoSessionKey } from "./thread-ownership-RkaoZCmQ.js";
|
|
8
|
+
import { i as readCodexSessionMeta, n as codexCatalogHomeId } from "./session-catalog-home-id-Dt2GiG5o.js";
|
|
9
|
+
import { f as projectBoundedCodexThreadHistory } from "./transcript-mirror-1ep4XjzF.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;
|
|
@@ -1154,6 +1198,7 @@ function renderMessagesForCodexContext(messages, options) {
|
|
|
1154
1198
|
}).filter((value) => Boolean(value)).join("\n\n");
|
|
1155
1199
|
}
|
|
1156
1200
|
function renderMessageBody(message, options) {
|
|
1201
|
+
if (message.role === "compactionSummary" || message.role === "branchSummary") return truncateText(message.summary.trim(), options.maxTextPartChars);
|
|
1157
1202
|
if (!hasMessageContent(message)) return "";
|
|
1158
1203
|
if (typeof message.content === "string") return truncateText(message.content.trim(), options.maxTextPartChars);
|
|
1159
1204
|
if (!Array.isArray(message.content)) return "[non-text content omitted]";
|
|
@@ -1622,7 +1667,8 @@ async function collectPersonalSkillRealPaths(homes, codexHome) {
|
|
|
1622
1667
|
}
|
|
1623
1668
|
const configuredCodexHome = codexHome?.trim() || process.env.CODEX_HOME?.trim();
|
|
1624
1669
|
if (configuredCodexHome) {
|
|
1625
|
-
const
|
|
1670
|
+
const realCodexHome = await canonicalizeExistingPath(configuredCodexHome);
|
|
1671
|
+
const stateOwned = isPathInside(realStateDir, realCodexHome);
|
|
1626
1672
|
roots.push({
|
|
1627
1673
|
dir: path.join(configuredCodexHome, "skills"),
|
|
1628
1674
|
onlyEscapedStateTargets: stateOwned
|
|
@@ -1685,12 +1731,14 @@ async function collectPersonalSkillRealPaths(homes, codexHome) {
|
|
|
1685
1731
|
try {
|
|
1686
1732
|
const stat = await fs.stat(entryPath);
|
|
1687
1733
|
if (entry.name === "SKILL.md" && stat.isFile()) await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
|
|
1688
|
-
else if (stat.isDirectory())
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1734
|
+
else if (stat.isDirectory()) {
|
|
1735
|
+
if (current.depth < MAX_PERSONAL_SKILL_DEPTH) queue.push({
|
|
1736
|
+
dir: entryPath,
|
|
1737
|
+
depth: current.depth + 1,
|
|
1738
|
+
onlyEscapedStateTargets: current.onlyEscapedStateTargets
|
|
1739
|
+
});
|
|
1740
|
+
else complete = false;
|
|
1741
|
+
}
|
|
1694
1742
|
} catch (error) {
|
|
1695
1743
|
if (!isMissingPathError(error)) complete = false;
|
|
1696
1744
|
}
|
|
@@ -1780,33 +1828,6 @@ function applyCodexNativeSkillIsolation(config, isolation) {
|
|
|
1780
1828
|
};
|
|
1781
1829
|
}
|
|
1782
1830
|
//#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
1831
|
//#region extensions/codex/src/app-server/managed-thread-store.ts
|
|
1811
1832
|
const CODEX_MANAGED_THREAD_NAMESPACE = "app-server-managed-threads";
|
|
1812
1833
|
const CODEX_MANAGED_THREAD_MAX_ENTRIES = 2e4;
|
|
@@ -1835,6 +1856,10 @@ function managedThreadStoreKey(sourceHomeId, threadId) {
|
|
|
1835
1856
|
/** Durable ownership index for Codex threads created by OpenClaw. */
|
|
1836
1857
|
function createCodexManagedThreadStore(state) {
|
|
1837
1858
|
return {
|
|
1859
|
+
async has(sourceHomeId, threadId) {
|
|
1860
|
+
const parsed = managedThreadSchema.safeParse(state.lookup(managedThreadStoreKey(sourceHomeId, threadId)));
|
|
1861
|
+
return parsed.success && parsed.data.sourceHomeId === sourceHomeId && parsed.data.threadId === threadId;
|
|
1862
|
+
},
|
|
1838
1863
|
async mark(params) {
|
|
1839
1864
|
try {
|
|
1840
1865
|
const value = managedThreadSchema.parse({
|
|
@@ -1981,11 +2006,6 @@ const LEGACY_PRO_REASONING_EFFORTS = [
|
|
|
1981
2006
|
];
|
|
1982
2007
|
const LEGACY_PRO_MODEL_ID_RE = /^gpt-5\.[45]-pro$/u;
|
|
1983
2008
|
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
2009
|
/** Read reasoning metadata after the Codex app-server route has been selected. */
|
|
1990
2010
|
function readCodexSupportedReasoningEfforts(compat) {
|
|
1991
2011
|
if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
|
|
@@ -1994,7 +2014,8 @@ function readCodexSupportedReasoningEfforts(compat) {
|
|
|
1994
2014
|
return efforts.filter((effort) => typeof effort === "string");
|
|
1995
2015
|
}
|
|
1996
2016
|
function resolveSupportedReasoningEffort(params) {
|
|
1997
|
-
const
|
|
2017
|
+
const declared = new Set(params.supportedReasoningEfforts.map((effort) => effort.trim().toLowerCase()));
|
|
2018
|
+
const supported = CODEX_REASONING_EFFORTS.filter((effort) => declared.has(effort));
|
|
1998
2019
|
if (supported.includes(params.requested)) return params.requested;
|
|
1999
2020
|
const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
|
|
2000
2021
|
const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
|
|
@@ -2002,7 +2023,8 @@ function resolveSupportedReasoningEffort(params) {
|
|
|
2002
2023
|
}
|
|
2003
2024
|
/** Resolve a turn effort from the selected model's provider-owned metadata. */
|
|
2004
2025
|
function resolveCodexAppServerReasoningEffort(params) {
|
|
2005
|
-
if (params.thinkLevel === "off"
|
|
2026
|
+
if (params.thinkLevel === "off") return params.supportedReasoningEfforts?.includes("none") ? "none" : null;
|
|
2027
|
+
if (params.thinkLevel === "adaptive") return null;
|
|
2006
2028
|
if (params.supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
2007
2029
|
requested: params.thinkLevel,
|
|
2008
2030
|
supportedReasoningEfforts: params.supportedReasoningEfforts
|
|
@@ -2093,7 +2115,7 @@ function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts)
|
|
|
2093
2115
|
}
|
|
2094
2116
|
//#endregion
|
|
2095
2117
|
//#region extensions/codex/src/app-server/project-doc-thread-config.ts
|
|
2096
|
-
const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES =
|
|
2118
|
+
const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES = 131072;
|
|
2097
2119
|
function buildCodexProjectDocThreadConfig(config) {
|
|
2098
2120
|
const defaults = { project_doc_max_bytes: CODEX_NATIVE_PROJECT_DOC_MAX_BYTES };
|
|
2099
2121
|
return mergeCodexThreadConfigs(defaults, config) ?? defaults;
|
|
@@ -2102,6 +2124,7 @@ function buildCodexProjectDocThreadConfig(config) {
|
|
|
2102
2124
|
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
2103
2125
|
function buildDeveloperInstructions(params, options = {}) {
|
|
2104
2126
|
const deferredToolNames = /* @__PURE__ */ new Set();
|
|
2127
|
+
let secretsToolName;
|
|
2105
2128
|
let hasSkillWorkshop = false;
|
|
2106
2129
|
let hasSessionsSpawn = false;
|
|
2107
2130
|
let hasSessionsYield = false;
|
|
@@ -2115,6 +2138,7 @@ function buildDeveloperInstructions(params, options = {}) {
|
|
|
2115
2138
|
for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
|
|
2116
2139
|
const name = tool.name.trim();
|
|
2117
2140
|
if (tool.deferLoading === true && name) deferredToolNames.add(name);
|
|
2141
|
+
if (name === "secrets" && params.disableTools !== true) secretsToolName ??= spec.type === "namespace" ? `${spec.name}.${name}` : name;
|
|
2118
2142
|
hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
|
|
2119
2143
|
hasSessionsSpawn ||= name === "sessions_spawn";
|
|
2120
2144
|
hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
|
|
@@ -2154,7 +2178,7 @@ function buildDeveloperInstructions(params, options = {}) {
|
|
|
2154
2178
|
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
|
2155
2179
|
messageToolAvailable
|
|
2156
2180
|
}),
|
|
2157
|
-
|
|
2181
|
+
buildCredentialSafetyPrompt(secretsToolName),
|
|
2158
2182
|
nativeCommandGuidance,
|
|
2159
2183
|
params.extraSystemPrompt
|
|
2160
2184
|
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
@@ -2261,7 +2285,8 @@ function resolveCodexWebSearchPlan(params) {
|
|
|
2261
2285
|
const CODEX_CODE_MODE_THREAD_CONFIG = {
|
|
2262
2286
|
"features.code_mode": true,
|
|
2263
2287
|
"features.code_mode_only": false,
|
|
2264
|
-
"features.apply_patch_streaming_events": true
|
|
2288
|
+
"features.apply_patch_streaming_events": true,
|
|
2289
|
+
suppress_unstable_features_warning: true
|
|
2265
2290
|
};
|
|
2266
2291
|
const CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG = { "features.goals": false };
|
|
2267
2292
|
const CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG = { "tools.update_plan.enabled": false };
|
|
@@ -2451,33 +2476,24 @@ function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
|
2451
2476
|
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
2452
2477
|
};
|
|
2453
2478
|
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
|
-
};
|
|
2479
|
+
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
2480
|
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
2460
2481
|
return disabledConfig;
|
|
2461
2482
|
}
|
|
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);
|
|
2483
|
+
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);
|
|
2484
|
+
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
2485
|
}
|
|
2474
2486
|
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
2475
2487
|
if (!requiredNamespaces?.length) return config;
|
|
2476
|
-
const
|
|
2477
|
-
const
|
|
2488
|
+
const feature = expectDefined(config["features.code_mode"], "Codex code mode config");
|
|
2489
|
+
const configured = isJsonObject(feature) ? feature : { enabled: feature };
|
|
2490
|
+
const namespaces = Array.isArray(configured.direct_only_tool_namespaces) ? configured.direct_only_tool_namespaces.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2478
2491
|
return {
|
|
2479
2492
|
...config,
|
|
2480
|
-
"code_mode
|
|
2493
|
+
"features.code_mode": {
|
|
2494
|
+
...configured,
|
|
2495
|
+
direct_only_tool_namespaces: [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
2496
|
+
}
|
|
2481
2497
|
};
|
|
2482
2498
|
}
|
|
2483
2499
|
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
@@ -2491,10 +2507,6 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
2491
2507
|
const configMcpServers = config?.mcp_servers;
|
|
2492
2508
|
if (restrictedToolSurface && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex restricted tool surface received invalid thread mcp_servers config");
|
|
2493
2509
|
const restrictedToolSurfaceMcpServerNames = [...options.restrictedToolSurfaceInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
2494
|
-
const restrictedRunConfig = restrictedToolSurface && isJsonObject(configMcpServers) ? {
|
|
2495
|
-
...config,
|
|
2496
|
-
mcp_servers: {}
|
|
2497
|
-
} : config;
|
|
2498
2510
|
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
2499
2511
|
config: params.config,
|
|
2500
2512
|
disableTools: params.disableTools,
|
|
@@ -2502,7 +2514,7 @@ function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
|
2502
2514
|
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2503
2515
|
webSearchAllowed: options.webSearchAllowed
|
|
2504
2516
|
}).threadConfig;
|
|
2505
|
-
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(
|
|
2517
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
2506
2518
|
return applyCodexManagedShellEnvironment({
|
|
2507
2519
|
...mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") ? { "features.image_generation": false } : void 0, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildRestrictedToolConfigPatch(restrictedToolSurfaceMcpServerNames) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames), restrictedTurnDisablesProjectDocs ? CODEX_NO_PROJECT_DOCS_CONFIG : void 0, params.authoredContextTokenCap === void 0 ? void 0 : { model_context_window: params.authoredContextTokenCap }) ?? baseConfig,
|
|
2508
2520
|
...params.bootstrapContextMode === "lightweight" ? CODEX_NO_PROJECT_DOCS_CONFIG : {}
|
|
@@ -2613,14 +2625,13 @@ function resolveCodexThreadEnvironmentSelection(options) {
|
|
|
2613
2625
|
}
|
|
2614
2626
|
//#endregion
|
|
2615
2627
|
//#region extensions/codex/src/app-server/thread-lifecycle-io.ts
|
|
2616
|
-
function
|
|
2617
|
-
const agentId =
|
|
2628
|
+
function resolveCodexThreadAgentDir(params) {
|
|
2629
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
2618
2630
|
config: params.params.config,
|
|
2619
2631
|
sessionKey: params.params.sessionKey,
|
|
2620
2632
|
agentId: params.agentId ?? params.params.agentId
|
|
2621
2633
|
}).sessionAgentId;
|
|
2622
|
-
|
|
2623
|
-
return codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir));
|
|
2634
|
+
return params.agentDir ?? params.params.agentDir ?? resolveAgentDir$1(params.params.config ?? {}, agentId);
|
|
2624
2635
|
}
|
|
2625
2636
|
function resolveCodexThreadRolloutPath(thread) {
|
|
2626
2637
|
const rolloutPath = thread.path?.trim();
|
|
@@ -2630,6 +2641,9 @@ function resolveCodexThreadRolloutPath(thread) {
|
|
|
2630
2641
|
async function resumeExistingCodexThread(params, context) {
|
|
2631
2642
|
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
2643
|
let resumeReservation;
|
|
2644
|
+
let resumeResponseAccepted = false;
|
|
2645
|
+
let checkingLoadedPluginThreadConfig = false;
|
|
2646
|
+
const abandonClient = params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client));
|
|
2633
2647
|
try {
|
|
2634
2648
|
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
2635
2649
|
const finalConfigPatch = context.prebuiltFinalConfigPatch ?? params.buildFinalConfigPatch?.({
|
|
@@ -2639,7 +2653,8 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2639
2653
|
configPatch: params.finalConfigPatch,
|
|
2640
2654
|
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2641
2655
|
};
|
|
2642
|
-
const
|
|
2656
|
+
const pluginThreadConfig = context.prebuiltPluginThreadConfig ?? (params.pluginThreadConfig?.requiresCurrentPolicyCheck ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0);
|
|
2657
|
+
const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0);
|
|
2643
2658
|
const resumeConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch), nativeSkillIsolation);
|
|
2644
2659
|
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2645
2660
|
threadId: resumeBinding.threadId,
|
|
@@ -2663,25 +2678,36 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2663
2678
|
}));
|
|
2664
2679
|
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2665
2680
|
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
2681
|
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
2675
2682
|
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
2676
2683
|
client: params.client,
|
|
2677
|
-
abandonClient
|
|
2684
|
+
abandonClient,
|
|
2678
2685
|
request: resumeParams,
|
|
2679
|
-
signal: params.signal
|
|
2686
|
+
signal: params.signal,
|
|
2687
|
+
assertCurrent: context.assertResumeOwnership,
|
|
2688
|
+
isPrewriteOwnershipError: (error) => error instanceof CodexAdoptedThreadActiveError
|
|
2680
2689
|
}));
|
|
2690
|
+
resumeResponseAccepted = true;
|
|
2691
|
+
assertCodexThreadAcceptsDirectInput(response.thread);
|
|
2692
|
+
context.assertResumeConfiguration?.();
|
|
2693
|
+
checkingLoadedPluginThreadConfig = true;
|
|
2694
|
+
const loadedPluginThreadConfig = await context.buildLoadedPluginThreadConfig?.(resumeBinding);
|
|
2695
|
+
if (loadedPluginThreadConfig && loadedPluginThreadConfig.fingerprint !== (pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint)) {
|
|
2696
|
+
checkingLoadedPluginThreadConfig = false;
|
|
2697
|
+
throw new Error("Codex thread app policy changed; a fresh thread configuration is required");
|
|
2698
|
+
}
|
|
2699
|
+
await attestCodexPluginThreadApps({
|
|
2700
|
+
client: params.client,
|
|
2701
|
+
threadId: response.thread.id,
|
|
2702
|
+
appIds: loadedPluginThreadConfig?.provisionalAppIds ?? pluginThreadConfig?.provisionalAppIds ?? [],
|
|
2703
|
+
signal: params.signal
|
|
2704
|
+
});
|
|
2705
|
+
checkingLoadedPluginThreadConfig = false;
|
|
2681
2706
|
if (ringZeroActive || isMessageOnlyCodexSourceReply(params.params) || params.params.pluginHarnessToolPolicyRestricted === true) try {
|
|
2682
2707
|
await lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, response.thread.id, resumeParams.config, params.signal));
|
|
2683
2708
|
} catch (error) {
|
|
2684
|
-
|
|
2709
|
+
context.assertResumeOwnership?.();
|
|
2710
|
+
await abandonClient();
|
|
2685
2711
|
throw new CodexRestrictedToolSurfaceAttestationError(error);
|
|
2686
2712
|
}
|
|
2687
2713
|
throwIfAborted();
|
|
@@ -2689,6 +2715,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2689
2715
|
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2690
2716
|
const resumePatch = {
|
|
2691
2717
|
clientId: resolveCodexAppServerClientInstanceId(params.client),
|
|
2718
|
+
pendingResumeConfiguration: void 0,
|
|
2692
2719
|
cwd: params.cwd,
|
|
2693
2720
|
rolloutPath: resolveCodexThreadRolloutPath(response.thread) ?? resumeBinding.rolloutPath,
|
|
2694
2721
|
authProfileId: boundAuthProfileId,
|
|
@@ -2709,9 +2736,9 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2709
2736
|
networkProxyConfigFingerprint,
|
|
2710
2737
|
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
2711
2738
|
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2712
|
-
pluginAppsFingerprint:
|
|
2713
|
-
pluginAppsInputFingerprint:
|
|
2714
|
-
pluginAppPolicyContext:
|
|
2739
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint ?? resumeBinding.pluginAppsFingerprint,
|
|
2740
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint ?? resumeBinding.pluginAppsInputFingerprint,
|
|
2741
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext ?? resumeBinding.pluginAppPolicyContext,
|
|
2715
2742
|
contextEngine: contextEngineBinding,
|
|
2716
2743
|
environmentSelectionFingerprint
|
|
2717
2744
|
};
|
|
@@ -2719,7 +2746,7 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2719
2746
|
kind: "patch",
|
|
2720
2747
|
threadId: resumeBinding.threadId,
|
|
2721
2748
|
patch: resumePatch
|
|
2722
|
-
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2749
|
+
}, context.assertResumeConfiguration))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2723
2750
|
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2724
2751
|
sessionId: params.params.sessionId,
|
|
2725
2752
|
sessionKey: params.params.sessionKey,
|
|
@@ -2756,19 +2783,26 @@ async function resumeExistingCodexThread(params, context) {
|
|
|
2756
2783
|
};
|
|
2757
2784
|
} catch (error) {
|
|
2758
2785
|
resumeReservation?.release();
|
|
2759
|
-
if (
|
|
2786
|
+
if (!resumeResponseAccepted && (!(error instanceof CodexAppServerRpcError) || isCodexAppServerOverloadError(error))) throw error;
|
|
2760
2787
|
if (error instanceof CodexRestrictedToolSurfaceAttestationError) {
|
|
2761
|
-
await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
|
|
2788
|
+
if (!resumeBinding.pendingResumeConfiguration) await clearCurrentBinding("retiring a failed restricted-tool-surface attestation");
|
|
2762
2789
|
throw error;
|
|
2763
2790
|
}
|
|
2764
|
-
if (
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2791
|
+
if (resumeResponseAccepted) {
|
|
2792
|
+
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
2793
|
+
threadId: resumeBinding.threadId,
|
|
2794
|
+
timeoutMs: 5e3,
|
|
2795
|
+
assertCurrent: context.assertResumeOwnership
|
|
2796
|
+
}).catch(() => false)) {
|
|
2797
|
+
try {
|
|
2798
|
+
await abandonClient();
|
|
2799
|
+
} catch (abandonError) {
|
|
2800
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume client could not be retired", { cause: abandonError });
|
|
2801
|
+
}
|
|
2802
|
+
throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
if (checkingLoadedPluginThreadConfig || resumeBinding.pendingResumeConfiguration || error instanceof CodexThreadDirectInputError || params.signal?.aborted) throw error;
|
|
2772
2806
|
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
2773
2807
|
await clearCurrentBinding("rotating a stale thread binding");
|
|
2774
2808
|
}
|
|
@@ -2801,14 +2835,15 @@ async function startFreshCodexThread(params, context) {
|
|
|
2801
2835
|
disableLoginShell: params.disableLoginShell
|
|
2802
2836
|
}));
|
|
2803
2837
|
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2804
|
-
const
|
|
2838
|
+
const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2805
2839
|
try {
|
|
2806
2840
|
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2807
2841
|
} catch (error) {
|
|
2808
2842
|
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2809
2843
|
throw error;
|
|
2810
2844
|
}
|
|
2811
|
-
})
|
|
2845
|
+
});
|
|
2846
|
+
const response = assertCodexThreadStartResponse(threadStartResponse);
|
|
2812
2847
|
const provisionalAppIds = pluginThreadConfig?.provisionalAppIds;
|
|
2813
2848
|
if (provisionalAppIds?.length) try {
|
|
2814
2849
|
await lifecycleTiming.measure("plugin-app-attestation", () => attestCodexPluginThreadApps({
|
|
@@ -2899,7 +2934,7 @@ async function startFreshCodexThread(params, context) {
|
|
|
2899
2934
|
throw new CodexAppServerUnsafeSubscriptionError("Codex successor cleanup failed after a binding replacement conflict", cause === void 0 ? void 0 : { cause });
|
|
2900
2935
|
}
|
|
2901
2936
|
};
|
|
2902
|
-
const managedSourceHomeId =
|
|
2937
|
+
const managedSourceHomeId = codexCatalogHomeId(resolveCodexAppServerLocalHomeDir(params.appServer.start, resolveCodexThreadAgentDir(params)));
|
|
2903
2938
|
await lifecycleTiming.measure("thread-start-mark-managed", () => markStartedCodexManagedThread(params.bindingStore.managedThreads, {
|
|
2904
2939
|
sourceHomeId: managedSourceHomeId,
|
|
2905
2940
|
threadId: response.thread.id,
|
|
@@ -2958,6 +2993,627 @@ async function startFreshCodexThread(params, context) {
|
|
|
2958
2993
|
});
|
|
2959
2994
|
}
|
|
2960
2995
|
//#endregion
|
|
2996
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-warm.ts
|
|
2997
|
+
/** Preserves the caller's abort reason across thread ownership transitions. */
|
|
2998
|
+
function throwIfCodexThreadLifecycleAborted(signal) {
|
|
2999
|
+
if (!signal?.aborted) return;
|
|
3000
|
+
const reason = signal.reason;
|
|
3001
|
+
if (reason instanceof Error) throw reason;
|
|
3002
|
+
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
3003
|
+
error.name = "AbortError";
|
|
3004
|
+
throw error;
|
|
3005
|
+
}
|
|
3006
|
+
/** Releases consumed subscription ownership or retires an unsafe client. */
|
|
3007
|
+
async function releaseCodexConsumedLiveThread(options) {
|
|
3008
|
+
if (await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => unsubscribeCodexThreadBestEffort(options.client, {
|
|
3009
|
+
threadId: options.threadId,
|
|
3010
|
+
timeoutMs: 5e3,
|
|
3011
|
+
assertCurrent: options.assertCurrent
|
|
3012
|
+
}))) return;
|
|
3013
|
+
return await abandonCodexLiveThreadRelease(options, options.cause);
|
|
3014
|
+
}
|
|
3015
|
+
async function abandonCodexLiveThreadRelease(options, cause) {
|
|
3016
|
+
options.assertCurrent?.();
|
|
3017
|
+
await (options.abandonClient ?? (() => closeCodexStartupClientBestEffort(options.client)))();
|
|
3018
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex retained thread subscription could not be released: ${options.threadId}`, cause !== void 0 ? { cause } : void 0);
|
|
3019
|
+
}
|
|
3020
|
+
/** Releases through the retained owner, preserving its guarded callback and rollback. */
|
|
3021
|
+
async function releaseCodexRetainedLiveThread(options) {
|
|
3022
|
+
try {
|
|
3023
|
+
return await options.lifecycleTiming.measure("retained-thread-unsubscribe", () => releaseCodexAppServerLiveThread(options.client, options.threadId, options.assertCurrent));
|
|
3024
|
+
} catch (error) {
|
|
3025
|
+
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
3026
|
+
return await abandonCodexLiveThreadRelease(options, error);
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
/** Reuses one safely owned, fully matching subscription on its original client. */
|
|
3030
|
+
async function tryReuseCodexLiveThread(options) {
|
|
3031
|
+
const { params, binding, bindingIdentity, clientId, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, lifecycleTiming, nativeSkillIsolation, ringZeroActive, restrictedToolSurfaceInheritedMcpServerNames, startModelProvider, startModelSelection, throwIfAborted, userMcpServersConfigPatch } = options;
|
|
3032
|
+
if (!binding.clientId || binding.clientId !== clientId || binding.preserveNativeModel === true || binding.connectionScope === "supervision" || ringZeroActive) return { kind: "resume" };
|
|
3033
|
+
const retainedThread = await consumeCodexAppServerLiveThread(params.client, binding.threadId);
|
|
3034
|
+
if (!retainedThread) return { kind: "resume" };
|
|
3035
|
+
const assertCurrentClient = () => {
|
|
3036
|
+
throwIfAborted();
|
|
3037
|
+
if (!isCodexAppServerClientRuntimeLive(params.client)) throw params.client.getCloseError() ?? /* @__PURE__ */ new Error("codex app-server client is closed");
|
|
3038
|
+
};
|
|
3039
|
+
let ownershipTransferred = false;
|
|
3040
|
+
try {
|
|
3041
|
+
const pluginThreadConfig = await options.buildLoadedPluginThreadConfig(binding);
|
|
3042
|
+
assertCurrentClient();
|
|
3043
|
+
if (pluginThreadConfig && pluginThreadConfig.fingerprint !== binding.pluginAppsFingerprint) return { kind: "rotate" };
|
|
3044
|
+
const prebuiltFinalConfigPatch = params.buildFinalConfigPatch?.({
|
|
3045
|
+
action: "resume",
|
|
3046
|
+
binding
|
|
3047
|
+
}) ?? {
|
|
3048
|
+
configPatch: params.finalConfigPatch,
|
|
3049
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
3050
|
+
};
|
|
3051
|
+
const pluginAppsConfigPatch = pluginThreadConfig?.configPatch ?? (params.pluginThreadConfig?.enabled && binding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(binding.pluginAppPolicyContext) : void 0);
|
|
3052
|
+
const resumeAuthProfileId = params.params.authProfileId ?? binding.authProfileId;
|
|
3053
|
+
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, prebuiltFinalConfigPatch.configPatch);
|
|
3054
|
+
const resumeParams = lifecycleTiming.measureSync("warm-thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
3055
|
+
threadId: binding.threadId,
|
|
3056
|
+
cwd: params.cwd,
|
|
3057
|
+
authProfileId: resumeAuthProfileId,
|
|
3058
|
+
model: startModelSelection.model,
|
|
3059
|
+
modelProvider: startModelProvider,
|
|
3060
|
+
preserveNativeModel: false,
|
|
3061
|
+
appServer: params.appServer,
|
|
3062
|
+
dynamicTools: params.dynamicTools,
|
|
3063
|
+
developerInstructions: params.developerInstructions,
|
|
3064
|
+
config: applyCodexNativeSkillIsolation(resumeConfig, nativeSkillIsolation),
|
|
3065
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
3066
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
3067
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
3068
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
3069
|
+
hostSystemAgentActive,
|
|
3070
|
+
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3071
|
+
shellEnvironment: params.shellEnvironment,
|
|
3072
|
+
disableLoginShell: params.disableLoginShell
|
|
3073
|
+
}));
|
|
3074
|
+
const liveThreadConfigFingerprint = fingerprintCodexThreadConfig({
|
|
3075
|
+
...resumeParams,
|
|
3076
|
+
model: binding.model ?? resumeParams.model ?? null,
|
|
3077
|
+
requestedModel: resumeParams.model ?? null,
|
|
3078
|
+
modelProvider: binding.modelProvider ?? resumeParams.modelProvider ?? null,
|
|
3079
|
+
requestedModelProvider: resumeParams.modelProvider ?? binding.modelProvider ?? null
|
|
3080
|
+
}, resumeAuthProfileId, dynamicToolsFingerprint);
|
|
3081
|
+
if (retainedThread.configFingerprint !== liveThreadConfigFingerprint) return {
|
|
3082
|
+
kind: "resume",
|
|
3083
|
+
prebuiltFinalConfigPatch
|
|
3084
|
+
};
|
|
3085
|
+
await attestCodexPluginThreadApps({
|
|
3086
|
+
client: params.client,
|
|
3087
|
+
threadId: binding.threadId,
|
|
3088
|
+
appIds: pluginThreadConfig?.provisionalAppIds ?? [],
|
|
3089
|
+
signal: params.signal
|
|
3090
|
+
});
|
|
3091
|
+
assertCurrentClient();
|
|
3092
|
+
const nativeHookRelayGeneration = prebuiltFinalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration;
|
|
3093
|
+
const model = startModelSelection.model;
|
|
3094
|
+
if (!await lifecycleTiming.measure("warm-thread-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
3095
|
+
kind: "patch",
|
|
3096
|
+
threadId: binding.threadId,
|
|
3097
|
+
patch: {
|
|
3098
|
+
cwd: params.cwd,
|
|
3099
|
+
model,
|
|
3100
|
+
nativeHookRelayGeneration,
|
|
3101
|
+
environmentSelectionFingerprint
|
|
3102
|
+
}
|
|
3103
|
+
}, assertCurrentClient))) throw new CodexThreadBindingConflictError(binding.threadId, "committing a reused thread");
|
|
3104
|
+
assertCurrentClient();
|
|
3105
|
+
lifecycleTiming.mark("thread-ready");
|
|
3106
|
+
lifecycleTiming.logSummary({
|
|
3107
|
+
runId: params.params.runId,
|
|
3108
|
+
sessionId: params.params.sessionId,
|
|
3109
|
+
sessionKey: params.params.sessionKey,
|
|
3110
|
+
threadId: binding.threadId,
|
|
3111
|
+
action: "resumed"
|
|
3112
|
+
});
|
|
3113
|
+
ownershipTransferred = true;
|
|
3114
|
+
return {
|
|
3115
|
+
kind: "ready",
|
|
3116
|
+
binding: {
|
|
3117
|
+
...binding,
|
|
3118
|
+
cwd: params.cwd,
|
|
3119
|
+
model,
|
|
3120
|
+
nativeHookRelayGeneration,
|
|
3121
|
+
environmentSelectionFingerprint,
|
|
3122
|
+
liveThreadConfigFingerprint,
|
|
3123
|
+
liveThreadOwnership: retainedThread,
|
|
3124
|
+
...retainedThread.serviceTier && resumeParams.serviceTier === void 0 ? { clearInheritedServiceTier: true } : {},
|
|
3125
|
+
lifecycle: { action: "resumed" }
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
} finally {
|
|
3129
|
+
if (!ownershipTransferred) try {
|
|
3130
|
+
await retainedThread.release(binding.threadId);
|
|
3131
|
+
} catch (error) {
|
|
3132
|
+
await abandonCodexLiveThreadRelease({
|
|
3133
|
+
client: params.client,
|
|
3134
|
+
abandonClient: params.abandonClient,
|
|
3135
|
+
lifecycleTiming,
|
|
3136
|
+
threadId: binding.threadId
|
|
3137
|
+
}, error);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
//#endregion
|
|
3142
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-adoption.ts
|
|
3143
|
+
/** Passive refusal must precede releasing or acquiring any native subscription. */
|
|
3144
|
+
async function assertAdoptedCodexThreadResumeAllowed(params, threadId, context) {
|
|
3145
|
+
const { thread } = await context.lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
3146
|
+
threadId,
|
|
3147
|
+
includeTurns: false
|
|
3148
|
+
}, { signal: params.signal }));
|
|
3149
|
+
context.throwIfAborted();
|
|
3150
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
3151
|
+
if (thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
3152
|
+
}
|
|
3153
|
+
/** Preserve attach's native-queue-before-binding-lease order when consuming pending intent. */
|
|
3154
|
+
async function withCodexThreadLifecycleBinding(params, run) {
|
|
3155
|
+
const identity = sessionBindingIdentity({
|
|
3156
|
+
sessionId: params.params.sessionId,
|
|
3157
|
+
sessionKey: params.params.sessionKey,
|
|
3158
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
3159
|
+
config: params.params.config
|
|
3160
|
+
});
|
|
3161
|
+
const snapshot = await params.bindingStore.read(identity);
|
|
3162
|
+
const pendingThreadId = snapshot?.pendingResumeConfiguration ? snapshot.threadId : void 0;
|
|
3163
|
+
const runWithLease = () => params.bindingStore.withLease(identity, async () => {
|
|
3164
|
+
const binding = await params.bindingStore.read(identity);
|
|
3165
|
+
if (pendingThreadId && (binding?.threadId !== pendingThreadId || !binding.pendingResumeConfiguration)) throw new CodexThreadBindingConflictError(pendingThreadId, "acquiring a pending resume configuration");
|
|
3166
|
+
if (!pendingThreadId && binding?.pendingResumeConfiguration) throw new CodexThreadBindingConflictError(binding.threadId, "acquiring a pending resume configuration");
|
|
3167
|
+
return await run(identity, binding);
|
|
3168
|
+
});
|
|
3169
|
+
return pendingThreadId ? await withExclusiveCodexAppServerThread({
|
|
3170
|
+
bindingStore: params.bindingStore,
|
|
3171
|
+
identity,
|
|
3172
|
+
threadId: pendingThreadId,
|
|
3173
|
+
run: runWithLease
|
|
3174
|
+
}) : await runWithLease();
|
|
3175
|
+
}
|
|
3176
|
+
/** Completes manual attachment only under the native queue and exact binding lease. */
|
|
3177
|
+
async function resumePendingCodexThread(params, context) {
|
|
3178
|
+
const { binding, contextEngineBinding, lifecycleTiming, restrictedToolSurface } = context;
|
|
3179
|
+
if (isIncognitoSessionKey(params.params.sessionKey) || context.transientRestriction || !restrictedToolSurface && binding.nativeToolPolicyRestricted === true || (contextEngineBinding ? !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding) : binding.contextEngine !== void 0) || shouldRotateCodexGpt56MultiAgentBinding({
|
|
3180
|
+
bindingModel: binding.model,
|
|
3181
|
+
requestedModel: params.params.modelId
|
|
3182
|
+
})) throw new Error(`Cannot configure resumed Codex thread ${binding.threadId} under a transient or incompatible session policy. The thread is preserved; retry from its normal session or use /new for the current policy.`);
|
|
3183
|
+
const prebuiltPluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
3184
|
+
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
3185
|
+
const configuration = await preparePendingCodexThreadResume(params, binding, context.dynamicToolsFingerprint, async (assertCurrent) => {
|
|
3186
|
+
const released = await context.releaseRetainedThread(binding.threadId, assertCurrent);
|
|
3187
|
+
assertCurrent();
|
|
3188
|
+
if (!released || binding.clientId && binding.clientId !== clientId) await releaseCodexConsumedLiveThread({
|
|
3189
|
+
client: params.client,
|
|
3190
|
+
abandonClient: params.abandonClient,
|
|
3191
|
+
lifecycleTiming,
|
|
3192
|
+
threadId: binding.threadId,
|
|
3193
|
+
assertCurrent
|
|
3194
|
+
});
|
|
3195
|
+
});
|
|
3196
|
+
try {
|
|
3197
|
+
const resumed = await resumeExistingCodexThread(params, {
|
|
3198
|
+
...context,
|
|
3199
|
+
prebuiltPluginThreadConfig,
|
|
3200
|
+
assertResumeConfiguration: configuration.assertConfigured,
|
|
3201
|
+
assertResumeOwnership: configuration.assertCurrent
|
|
3202
|
+
});
|
|
3203
|
+
if (!resumed) throw new Error(`Codex did not configure resumed thread ${binding.threadId}.`);
|
|
3204
|
+
return resumed;
|
|
3205
|
+
} finally {
|
|
3206
|
+
configuration.dispose();
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
/** Manual attachment is intent, never evidence that loaded native overrides took effect. */
|
|
3210
|
+
async function preparePendingCodexThreadResume(params, binding, dynamicToolsFingerprint, releaseSubscription) {
|
|
3211
|
+
const fail = (reason) => /* @__PURE__ */ new Error(`Cannot configure resumed Codex thread ${binding.threadId}: ${reason}. The thread is preserved; continue it in native Codex or use /new for the current OpenClaw tools.`);
|
|
3212
|
+
const agentDir = resolveCodexThreadAgentDir(params);
|
|
3213
|
+
const localHome = resolveCodexAppServerLocalHomeDir(params.appServer.start, agentDir);
|
|
3214
|
+
if (params.appServer.start.transport !== "stdio" || params.appServer.start.homeScope === "user" || path.resolve(localHome) !== resolveCodexAppServerHomeDir(agentDir) || binding.connectionScope === "supervision" || binding.preserveNativeModel === true) throw fail("configuration adoption requires an OpenClaw-owned local Codex home");
|
|
3215
|
+
if (isCodexAppServerLiveThreadClaimed(params.client, binding.threadId)) throw fail("the thread is claimed by active work; stop that run before resuming");
|
|
3216
|
+
const assertCurrent = captureExclusiveSharedCodexAppServerClient(params.client);
|
|
3217
|
+
const { thread } = await params.client.request("thread/read", {
|
|
3218
|
+
threadId: binding.threadId,
|
|
3219
|
+
includeTurns: false
|
|
3220
|
+
}, { signal: params.signal });
|
|
3221
|
+
assertCurrent();
|
|
3222
|
+
const statusType = thread.status?.type;
|
|
3223
|
+
if (thread.id !== binding.threadId || statusType !== "idle" && statusType !== "notLoaded") throw fail("the native thread is not idle; wait for its current run to finish");
|
|
3224
|
+
assertCodexThreadAcceptsDirectInput(thread);
|
|
3225
|
+
let unloaded = statusType === "notLoaded";
|
|
3226
|
+
const dispose = params.client.addNotificationHandler((notification) => {
|
|
3227
|
+
if (notification.method === "thread/status/changed" && isJsonObject(notification.params) && notification.params.threadId === binding.threadId && isJsonObject(notification.params.status) && notification.params.status.type === "notLoaded") unloaded = true;
|
|
3228
|
+
});
|
|
3229
|
+
try {
|
|
3230
|
+
const rolloutPath = thread.path ?? binding.rolloutPath;
|
|
3231
|
+
const metadata = rolloutPath ? await readCodexSessionMeta(path.join(localHome, "sessions"), rolloutPath, binding.threadId) : void 0;
|
|
3232
|
+
if (!metadata) throw fail("its native tool catalog could not be read from the selected Codex home");
|
|
3233
|
+
const recordedTools = metadata.dynamic_tools ?? [];
|
|
3234
|
+
if (!Array.isArray(recordedTools) || codexDynamicToolsFingerprint(recordedTools) !== dynamicToolsFingerprint) throw fail("its immutable native tool catalog does not match the current OpenClaw tools");
|
|
3235
|
+
assertCurrent();
|
|
3236
|
+
await releaseSubscription(assertCurrent);
|
|
3237
|
+
assertCurrent();
|
|
3238
|
+
return {
|
|
3239
|
+
assertConfigured: () => {
|
|
3240
|
+
assertCurrent();
|
|
3241
|
+
if (!unloaded) throw fail("Codex did not confirm unloading its previous configuration");
|
|
3242
|
+
},
|
|
3243
|
+
assertCurrent,
|
|
3244
|
+
dispose
|
|
3245
|
+
};
|
|
3246
|
+
} catch (error) {
|
|
3247
|
+
dispose();
|
|
3248
|
+
throw error;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
//#endregion
|
|
3252
|
+
//#region extensions/codex/src/app-server/native-hook-relay-state.ts
|
|
3253
|
+
const pending = /* @__PURE__ */ new Set();
|
|
3254
|
+
/** Owns delayed hook-relay cleanup across runtime scheduling and test teardown. */
|
|
3255
|
+
const nativeHookRelayUnregisterQueue = {
|
|
3256
|
+
add(entry) {
|
|
3257
|
+
pending.add(entry);
|
|
3258
|
+
},
|
|
3259
|
+
delete(entry) {
|
|
3260
|
+
return pending.delete(entry);
|
|
3261
|
+
},
|
|
3262
|
+
flush() {
|
|
3263
|
+
while (pending.size > 0) {
|
|
3264
|
+
const entry = pending.values().next().value;
|
|
3265
|
+
if (!entry) return;
|
|
3266
|
+
clearTimeout(entry.timeout);
|
|
3267
|
+
entry.unregister();
|
|
3268
|
+
}
|
|
3269
|
+
},
|
|
3270
|
+
clear() {
|
|
3271
|
+
for (const entry of pending) clearTimeout(entry.timeout);
|
|
3272
|
+
pending.clear();
|
|
3273
|
+
}
|
|
3274
|
+
};
|
|
3275
|
+
//#endregion
|
|
3276
|
+
//#region extensions/codex/src/app-server/native-hook-relay.ts
|
|
3277
|
+
/**
|
|
3278
|
+
* Bridges Codex native hook callbacks into OpenClaw's native hook relay so
|
|
3279
|
+
* app-server tool events can still run OpenClaw policy and diagnostics.
|
|
3280
|
+
*/
|
|
3281
|
+
/** Codex hook events that can be registered through OpenClaw's native relay. */
|
|
3282
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
|
|
3283
|
+
"pre_tool_use",
|
|
3284
|
+
"post_tool_use",
|
|
3285
|
+
"permission_request",
|
|
3286
|
+
"before_agent_finalize"
|
|
3287
|
+
];
|
|
3288
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
|
|
3289
|
+
const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 18e5;
|
|
3290
|
+
/** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
|
|
3291
|
+
const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 3e5;
|
|
3292
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
|
|
3293
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
|
|
3294
|
+
const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
|
|
3295
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
|
|
3296
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
|
|
3297
|
+
const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
|
|
3298
|
+
const nativeHookPolicyByClient = /* @__PURE__ */ new WeakMap();
|
|
3299
|
+
const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
|
|
3300
|
+
exec: [
|
|
3301
|
+
"Bash",
|
|
3302
|
+
"exec",
|
|
3303
|
+
"exec_command"
|
|
3304
|
+
],
|
|
3305
|
+
apply_patch: [
|
|
3306
|
+
"apply_patch",
|
|
3307
|
+
"Write",
|
|
3308
|
+
"Edit"
|
|
3309
|
+
],
|
|
3310
|
+
spawn_agent: ["spawn_agent", "Agent"]
|
|
3311
|
+
};
|
|
3312
|
+
/** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
|
|
3313
|
+
async function assertCodexNativeHookRelayAllowed(client, signal) {
|
|
3314
|
+
let attestation = nativeHookPolicyByClient.get(client);
|
|
3315
|
+
if (!attestation) {
|
|
3316
|
+
attestation = client.request("configRequirements/read", void 0, { signal }).then((response) => {
|
|
3317
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
|
|
3318
|
+
const requirements = response.requirements;
|
|
3319
|
+
if (requirements === null) return;
|
|
3320
|
+
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
|
|
3321
|
+
const managedOnly = requirements.allowManagedHooksOnly;
|
|
3322
|
+
if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
|
|
3323
|
+
if (managedOnly === true) throw new Error("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
|
|
3324
|
+
});
|
|
3325
|
+
nativeHookPolicyByClient.set(client, attestation);
|
|
3326
|
+
attestation.catch(() => {
|
|
3327
|
+
if (nativeHookPolicyByClient.get(client) === attestation) nativeHookPolicyByClient.delete(client);
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
await attestation;
|
|
3331
|
+
}
|
|
3332
|
+
/** Defers relay unregister so late native hook subprocesses can still resolve. */
|
|
3333
|
+
function scheduleCodexNativeHookRelayUnregister(params) {
|
|
3334
|
+
let pending;
|
|
3335
|
+
const unregister = () => {
|
|
3336
|
+
if (!pending) return;
|
|
3337
|
+
const current = pending;
|
|
3338
|
+
pending = void 0;
|
|
3339
|
+
if (!nativeHookRelayUnregisterQueue.delete(current)) return;
|
|
3340
|
+
params.relay.unregister();
|
|
3341
|
+
};
|
|
3342
|
+
const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
|
|
3343
|
+
pending = {
|
|
3344
|
+
timeout,
|
|
3345
|
+
unregister
|
|
3346
|
+
};
|
|
3347
|
+
nativeHookRelayUnregisterQueue.add(pending);
|
|
3348
|
+
timeout.unref();
|
|
3349
|
+
}
|
|
3350
|
+
/** Computes the delayed unregister window from Codex's hook timeout. */
|
|
3351
|
+
function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
|
|
3352
|
+
const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
|
|
3353
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
|
|
3354
|
+
}
|
|
3355
|
+
/** Records a native pre-tool failure that Codex does not project as a tool item. */
|
|
3356
|
+
function emitCodexNativePreToolUseFailureDiagnostic(params) {
|
|
3357
|
+
emitTrustedDiagnosticEvent({
|
|
3358
|
+
type: "tool.execution.error",
|
|
3359
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
3360
|
+
sessionId: params.sessionId,
|
|
3361
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
3362
|
+
runId: params.runId,
|
|
3363
|
+
toolName: params.failure.toolName,
|
|
3364
|
+
toolCallId: params.failure.toolCallId,
|
|
3365
|
+
durationMs: params.failure.durationMs,
|
|
3366
|
+
errorCategory: "before_tool_call",
|
|
3367
|
+
terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
|
|
3368
|
+
...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
/** Registers an OpenClaw native hook relay for a Codex app-server turn. */
|
|
3372
|
+
function createCodexNativeHookRelay(params) {
|
|
3373
|
+
if (params.options?.enabled === false) return;
|
|
3374
|
+
const directChildClaims = /* @__PURE__ */ new Map();
|
|
3375
|
+
const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
|
|
3376
|
+
let foregroundClosed = false;
|
|
3377
|
+
let successfulYieldRetentionAuthorized = false;
|
|
3378
|
+
const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
|
|
3379
|
+
const rejectPendingAdmissions = (reason) => {
|
|
3380
|
+
for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
|
|
3381
|
+
pendingDirectChildAdmissions.clear();
|
|
3382
|
+
};
|
|
3383
|
+
const relay = registerRetainedNativeHookRelayForBundledRuntime({
|
|
3384
|
+
provider: "codex",
|
|
3385
|
+
relayId: buildCodexNativeHookRelayId({
|
|
3386
|
+
agentId: params.agentId,
|
|
3387
|
+
sessionId: params.sessionId,
|
|
3388
|
+
sessionKey: params.sessionKey
|
|
3389
|
+
}),
|
|
3390
|
+
...params.generation ? { generation: params.generation } : {},
|
|
3391
|
+
...params.generationMismatchGraceMs ? { generationMismatchGraceMs: params.generationMismatchGraceMs } : {},
|
|
3392
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
3393
|
+
sessionId: params.sessionId,
|
|
3394
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
3395
|
+
...params.config ? { config: params.config } : {},
|
|
3396
|
+
runId: params.runId,
|
|
3397
|
+
...params.channelId ? { channelId: params.channelId } : {},
|
|
3398
|
+
...params.requester ? { requester: params.requester } : {},
|
|
3399
|
+
...params.approvalContext ? { approvalContext: params.approvalContext } : {},
|
|
3400
|
+
allowedEvents: params.events,
|
|
3401
|
+
preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
|
|
3402
|
+
ttlMs: resolveCodexNativeHookRelayTtlMs({
|
|
3403
|
+
explicitTtlMs: params.options?.ttlMs,
|
|
3404
|
+
attemptTimeoutMs: params.attemptTimeoutMs,
|
|
3405
|
+
startupTimeoutMs: params.startupTimeoutMs,
|
|
3406
|
+
turnStartTimeoutMs: params.turnStartTimeoutMs
|
|
3407
|
+
}),
|
|
3408
|
+
signal: params.signal,
|
|
3409
|
+
runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
|
|
3410
|
+
assertActive: params.hostCapabilities.assertActive,
|
|
3411
|
+
retention: {
|
|
3412
|
+
readClaim: readCodexNativeChildThreadId,
|
|
3413
|
+
shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
|
|
3414
|
+
allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
|
|
3415
|
+
awaitForegroundAdmission: (childThreadId) => {
|
|
3416
|
+
if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
|
|
3417
|
+
const existingClaim = directChildClaims.get(childThreadId);
|
|
3418
|
+
if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
|
|
3419
|
+
const existingPending = pendingDirectChildAdmissions.get(childThreadId);
|
|
3420
|
+
if (existingPending) return existingPending.promise.then((claim) => assertClaim(childThreadId, claim));
|
|
3421
|
+
if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
|
|
3422
|
+
const { promise, resolve, reject } = createDeferred();
|
|
3423
|
+
pendingDirectChildAdmissions.set(childThreadId, {
|
|
3424
|
+
promise,
|
|
3425
|
+
resolve,
|
|
3426
|
+
reject
|
|
3427
|
+
});
|
|
3428
|
+
return promise.then((claim) => assertClaim(childThreadId, claim));
|
|
3429
|
+
},
|
|
3430
|
+
onDispose: () => {
|
|
3431
|
+
foregroundClosed = true;
|
|
3432
|
+
rejectPendingAdmissions("native hook relay registration closed");
|
|
3433
|
+
}
|
|
3434
|
+
},
|
|
3435
|
+
onPreToolUseFailure: params.onPreToolUseFailure,
|
|
3436
|
+
command: {
|
|
3437
|
+
nice: 10,
|
|
3438
|
+
timeoutMs: params.options?.gatewayTimeoutMs
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
3441
|
+
const unregister = () => {
|
|
3442
|
+
foregroundClosed = true;
|
|
3443
|
+
rejectPendingAdmissions("native hook relay foreground closed");
|
|
3444
|
+
relay.unregister();
|
|
3445
|
+
};
|
|
3446
|
+
return {
|
|
3447
|
+
...relay,
|
|
3448
|
+
unregister,
|
|
3449
|
+
authorizeRetentionAfterSuccessfulYield: () => {
|
|
3450
|
+
successfulYieldRetentionAuthorized = true;
|
|
3451
|
+
},
|
|
3452
|
+
hasClaimedDirectChild: () => directChildClaims.size > 0,
|
|
3453
|
+
rejectPendingDirectChild: (threadIdInput, reason) => {
|
|
3454
|
+
const threadId = threadIdInput.trim();
|
|
3455
|
+
const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
|
|
3456
|
+
if (!pending) return;
|
|
3457
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
3458
|
+
pending.reject(new Error(reason));
|
|
3459
|
+
},
|
|
3460
|
+
claimDirectChild: (threadIdInput) => {
|
|
3461
|
+
const threadId = threadIdInput.trim();
|
|
3462
|
+
if (!threadId) return () => void 0;
|
|
3463
|
+
if (directChildClaims.get(threadId)) return () => void 0;
|
|
3464
|
+
const claim = Symbol(threadId);
|
|
3465
|
+
directChildClaims.set(threadId, claim);
|
|
3466
|
+
const pending = pendingDirectChildAdmissions.get(threadId);
|
|
3467
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
3468
|
+
pending?.resolve(claim);
|
|
3469
|
+
let released = false;
|
|
3470
|
+
return () => {
|
|
3471
|
+
if (released) return;
|
|
3472
|
+
released = true;
|
|
3473
|
+
if (directChildClaims.get(threadId) !== claim) return;
|
|
3474
|
+
directChildClaims.delete(threadId);
|
|
3475
|
+
if (foregroundClosed && directChildClaims.size === 0) relay.unregister();
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
function readCodexNativeChildThreadId(rawPayload) {
|
|
3481
|
+
if (!isJsonObject(rawPayload) || typeof rawPayload.agent_id !== "string") return;
|
|
3482
|
+
return rawPayload.agent_id.trim() || void 0;
|
|
3483
|
+
}
|
|
3484
|
+
/** Selects the native hook events Codex should install for the current approval mode. */
|
|
3485
|
+
function resolveCodexNativeHookRelayEvents(params) {
|
|
3486
|
+
if (params.configuredEvents?.length) return params.configuredEvents;
|
|
3487
|
+
return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
|
|
3488
|
+
}
|
|
3489
|
+
/** Derives the native hook relay TTL from the turn budget unless explicitly configured. */
|
|
3490
|
+
function resolveCodexNativeHookRelayTtlMs(params) {
|
|
3491
|
+
if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
|
|
3492
|
+
const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
|
|
3493
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
|
|
3494
|
+
}
|
|
3495
|
+
/** Builds a stable relay id scoped to the agent and session identity. */
|
|
3496
|
+
function buildCodexNativeHookRelayId(params) {
|
|
3497
|
+
const hash = createHash("sha256");
|
|
3498
|
+
hash.update("openclaw:codex:native-hook-relay:v1");
|
|
3499
|
+
hash.update("\0");
|
|
3500
|
+
hash.update(params.agentId?.trim() || "");
|
|
3501
|
+
hash.update("\0");
|
|
3502
|
+
hash.update(params.sessionKey?.trim() || params.sessionId);
|
|
3503
|
+
return `codex-${hash.digest("hex").slice(0, 40)}`;
|
|
3504
|
+
}
|
|
3505
|
+
const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
|
|
3506
|
+
pre_tool_use: "PreToolUse",
|
|
3507
|
+
post_tool_use: "PostToolUse",
|
|
3508
|
+
permission_request: "PermissionRequest",
|
|
3509
|
+
before_agent_finalize: "Stop"
|
|
3510
|
+
};
|
|
3511
|
+
const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
|
|
3512
|
+
pre_tool_use: "pre_tool_use",
|
|
3513
|
+
post_tool_use: "post_tool_use",
|
|
3514
|
+
permission_request: "permission_request",
|
|
3515
|
+
before_agent_finalize: "stop"
|
|
3516
|
+
};
|
|
3517
|
+
const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
|
|
3518
|
+
/** Builds the Codex config overlay that installs trusted command hooks for relay events. */
|
|
3519
|
+
function buildCodexNativeHookRelayConfig(params) {
|
|
3520
|
+
const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
|
|
3521
|
+
const selectedEvents = new Set(events);
|
|
3522
|
+
const config = { "features.hooks": true };
|
|
3523
|
+
const hookState = {};
|
|
3524
|
+
for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
|
|
3525
|
+
const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
|
|
3526
|
+
const selected = selectedEvents.has(event);
|
|
3527
|
+
const shouldRelay = params.relay.shouldRelayEvent(event);
|
|
3528
|
+
if (!selected || !shouldRelay) {
|
|
3529
|
+
if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
|
|
3530
|
+
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 };
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
|
|
3534
|
+
const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
|
|
3535
|
+
const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
|
|
3536
|
+
config[`hooks.${codexEvent}`] = [{
|
|
3537
|
+
...matcher ? { matcher } : {},
|
|
3538
|
+
hooks: [{
|
|
3539
|
+
type: "command",
|
|
3540
|
+
command,
|
|
3541
|
+
timeout,
|
|
3542
|
+
async: false,
|
|
3543
|
+
statusMessage: "OpenClaw native hook relay"
|
|
3544
|
+
}]
|
|
3545
|
+
}];
|
|
3546
|
+
const state = {
|
|
3547
|
+
enabled: true,
|
|
3548
|
+
trusted_hash: codexCommandHookTrustedHash({
|
|
3549
|
+
event,
|
|
3550
|
+
command,
|
|
3551
|
+
matcher,
|
|
3552
|
+
timeout,
|
|
3553
|
+
statusMessage: "OpenClaw native hook relay"
|
|
3554
|
+
})
|
|
3555
|
+
};
|
|
3556
|
+
for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
|
|
3557
|
+
}
|
|
3558
|
+
config["hooks.state"] = hookState;
|
|
3559
|
+
return config;
|
|
3560
|
+
}
|
|
3561
|
+
/** Builds a Codex config overlay that disables native hooks and clears hook arrays. */
|
|
3562
|
+
function buildCodexNativeHookRelayDisabledConfig() {
|
|
3563
|
+
return {
|
|
3564
|
+
"features.hooks": false,
|
|
3565
|
+
"hooks.PreToolUse": [],
|
|
3566
|
+
"hooks.PostToolUse": [],
|
|
3567
|
+
"hooks.PermissionRequest": [],
|
|
3568
|
+
"hooks.Stop": []
|
|
3569
|
+
};
|
|
3570
|
+
}
|
|
3571
|
+
function normalizeHookTimeoutSec(value) {
|
|
3572
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC;
|
|
3573
|
+
}
|
|
3574
|
+
function resolveCodexNativeHookRelayCommandTimeoutMs(hookTimeoutSec) {
|
|
3575
|
+
const parentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 5e3;
|
|
3576
|
+
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)));
|
|
3577
|
+
return Math.max(1, parentTimeoutMs - parentMarginMs);
|
|
3578
|
+
}
|
|
3579
|
+
function buildCodexNativeToolMatcher(toolNames) {
|
|
3580
|
+
if (toolNames === void 0) return;
|
|
3581
|
+
if (toolNames.length === 0) throw new TypeError("Codex native hook matcher requires at least one tool name");
|
|
3582
|
+
const nativeNames = /* @__PURE__ */ new Set();
|
|
3583
|
+
let hasCustomToolName = false;
|
|
3584
|
+
for (const toolName of toolNames) {
|
|
3585
|
+
const canonicalToolName = toolName.trim();
|
|
3586
|
+
if (!canonicalToolName || canonicalToolName === "*") throw new TypeError("Codex native hook matcher requires canonical OpenClaw tool ids");
|
|
3587
|
+
const nativeAliases = CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID[canonicalToolName];
|
|
3588
|
+
if (!nativeAliases) hasCustomToolName = true;
|
|
3589
|
+
for (const nativeName of nativeAliases ?? [canonicalToolName]) nativeNames.add(nativeName);
|
|
3590
|
+
}
|
|
3591
|
+
const sortedNames = Array.from(nativeNames).toSorted();
|
|
3592
|
+
if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
|
|
3593
|
+
return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
|
|
3594
|
+
}
|
|
3595
|
+
function codexCommandHookTrustedHash(params) {
|
|
3596
|
+
const identity = {
|
|
3597
|
+
event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
|
|
3598
|
+
...params.matcher ? { matcher: params.matcher } : {},
|
|
3599
|
+
hooks: [{
|
|
3600
|
+
async: false,
|
|
3601
|
+
command: params.command,
|
|
3602
|
+
statusMessage: params.statusMessage,
|
|
3603
|
+
timeout: params.timeout,
|
|
3604
|
+
type: "command"
|
|
3605
|
+
}]
|
|
3606
|
+
};
|
|
3607
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
|
|
3608
|
+
}
|
|
3609
|
+
function sortJsonValue(value) {
|
|
3610
|
+
if (!value || typeof value !== "object") return value;
|
|
3611
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
3612
|
+
const sorted = {};
|
|
3613
|
+
for (const [key, entry] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) sorted[key] = sortJsonValue(entry);
|
|
3614
|
+
return sorted;
|
|
3615
|
+
}
|
|
3616
|
+
//#endregion
|
|
2961
3617
|
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
2962
3618
|
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
2963
3619
|
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
@@ -3056,6 +3712,13 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
|
3056
3712
|
//#endregion
|
|
3057
3713
|
//#region extensions/codex/src/app-server/thread-lifecycle-preflight.ts
|
|
3058
3714
|
async function prepareCodexThreadLifecyclePreflight(params) {
|
|
3715
|
+
await assertCodexModelBackedReviewerEffectiveConfig({
|
|
3716
|
+
client: params.client,
|
|
3717
|
+
approvalsReviewer: params.appServer.approvalsReviewer,
|
|
3718
|
+
cwd: params.cwd,
|
|
3719
|
+
signal: params.signal
|
|
3720
|
+
});
|
|
3721
|
+
if (params.nativeHookRelayRequired) await assertCodexNativeHookRelayAllowed(params.client, params.signal);
|
|
3059
3722
|
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
3060
3723
|
...params.timing,
|
|
3061
3724
|
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
@@ -3072,11 +3735,12 @@ async function prepareCodexThreadLifecyclePreflight(params) {
|
|
|
3072
3735
|
})).threadConfig);
|
|
3073
3736
|
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
3074
3737
|
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
3075
|
-
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await
|
|
3738
|
+
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRun({
|
|
3739
|
+
run: params.params,
|
|
3740
|
+
cwd: params.cwd,
|
|
3076
3741
|
agentId: params.agentId ?? params.params.agentId,
|
|
3077
|
-
agentDir: params.params.agentDir,
|
|
3078
3742
|
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
3079
|
-
|
|
3743
|
+
warn: (message) => embeddedAgentLog.warn(message),
|
|
3080
3744
|
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
3081
3745
|
serverName,
|
|
3082
3746
|
error: formatErrorMessage(error)
|
|
@@ -3136,118 +3800,6 @@ async function prepareCodexThreadLifecyclePreflight(params) {
|
|
|
3136
3800
|
};
|
|
3137
3801
|
}
|
|
3138
3802
|
//#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
3803
|
//#region extensions/codex/src/app-server/thread-supervision.ts
|
|
3252
3804
|
async function materializePendingSupervisionBranch(params) {
|
|
3253
3805
|
let pending = params.binding.pendingSupervisionBranch;
|
|
@@ -3271,6 +3823,32 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3271
3823
|
});
|
|
3272
3824
|
let bindingCommitted = false;
|
|
3273
3825
|
let provisionalCleanupSafe = true;
|
|
3826
|
+
let cleanupExpected = pending;
|
|
3827
|
+
const trackPendingSupervisionArtifacts = async (cleanupThreadIds) => {
|
|
3828
|
+
const expected = pending;
|
|
3829
|
+
pending = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
3830
|
+
let updated;
|
|
3831
|
+
try {
|
|
3832
|
+
updated = await params.bindingStore.mutate(params.bindingIdentity, {
|
|
3833
|
+
kind: "patch-pending-supervision-branch",
|
|
3834
|
+
expected,
|
|
3835
|
+
pending
|
|
3836
|
+
});
|
|
3837
|
+
} catch (error) {
|
|
3838
|
+
try {
|
|
3839
|
+
const current = await params.bindingStore.read(params.bindingIdentity);
|
|
3840
|
+
if (matchesPendingSupervisionState(current, pending)) cleanupExpected = pending;
|
|
3841
|
+
else if (matchesPendingSupervisionState(current, expected)) cleanupExpected = expected;
|
|
3842
|
+
else throw new CodexThreadBindingConflictError(pending.sourceThreadId, "verifying supervised Codex cleanup tracking");
|
|
3843
|
+
} catch (verificationError) {
|
|
3844
|
+
provisionalCleanupSafe = false;
|
|
3845
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup tracking could not be verified: ${cleanupThreadIds.join(", ")}`, { cause: new AggregateError([error, verificationError], void 0, { cause: error }) });
|
|
3846
|
+
}
|
|
3847
|
+
throw error;
|
|
3848
|
+
}
|
|
3849
|
+
cleanupExpected = updated ? pending : void 0;
|
|
3850
|
+
if (!updated) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
3851
|
+
};
|
|
3274
3852
|
try {
|
|
3275
3853
|
const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
|
|
3276
3854
|
const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
|
|
@@ -3286,10 +3864,17 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3286
3864
|
sourceThreadId: pending.sourceThreadId,
|
|
3287
3865
|
role: "model probe"
|
|
3288
3866
|
});
|
|
3289
|
-
|
|
3867
|
+
let probeResponse;
|
|
3868
|
+
try {
|
|
3869
|
+
params.throwIfAborted();
|
|
3870
|
+
probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
3871
|
+
if (params.restrictedToolSurface) await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, probeThreadId, probeParams.config ?? void 0, params.signal));
|
|
3872
|
+
} finally {
|
|
3873
|
+
await unsubscribeCodexAppServerLiveThread(params.client, probeThreadId, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS).catch((cause) => {
|
|
3874
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex model probe subscription could not be released: ${probeThreadId}`, { cause });
|
|
3875
|
+
});
|
|
3876
|
+
}
|
|
3290
3877
|
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
3878
|
const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
|
|
3294
3879
|
const nativeModelProvider = requireNativeSupervisionModelProvider({
|
|
3295
3880
|
responseModelProvider: probeResponse.modelProvider,
|
|
@@ -3335,7 +3920,7 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3335
3920
|
otherThreadId: probeThreadId,
|
|
3336
3921
|
role: "canonical branch"
|
|
3337
3922
|
});
|
|
3338
|
-
|
|
3923
|
+
await trackPendingSupervisionArtifacts([finalThreadId]);
|
|
3339
3924
|
params.throwIfAborted();
|
|
3340
3925
|
assertExactSupervisionModelSelection(assertCodexThreadStartResponse(rawStartResponse), {
|
|
3341
3926
|
model: nativeModel,
|
|
@@ -3355,11 +3940,11 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3355
3940
|
client: params.client,
|
|
3356
3941
|
threadId: finalThreadId,
|
|
3357
3942
|
ephemeral: startParams.ephemeral === true
|
|
3358
|
-
})
|
|
3943
|
+
})) {
|
|
3359
3944
|
provisionalCleanupSafe = false;
|
|
3360
3945
|
throw new CodexAppServerUnsafeSubscriptionError("Codex supervised plugin app attestation cleanup failed", { cause: error });
|
|
3361
3946
|
}
|
|
3362
|
-
|
|
3947
|
+
await trackPendingSupervisionArtifacts([]);
|
|
3363
3948
|
throw error;
|
|
3364
3949
|
}
|
|
3365
3950
|
if (history.responseItems.length > 0) {
|
|
@@ -3369,8 +3954,6 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3369
3954
|
}, { signal: params.signal }));
|
|
3370
3955
|
params.throwIfAborted();
|
|
3371
3956
|
}
|
|
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
3957
|
const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
|
|
3375
3958
|
const bindingModelProvider = params.normalizeBindingModelProvider(params.attempt.authProfileId, nativeModelProvider);
|
|
3376
3959
|
let committed = false;
|
|
@@ -3432,31 +4015,28 @@ async function materializePendingSupervisionBranch(params) {
|
|
|
3432
4015
|
};
|
|
3433
4016
|
} catch (error) {
|
|
3434
4017
|
if (bindingCommitted) throw error;
|
|
3435
|
-
if (error instanceof CodexThreadBindingConflictAfterCleanupError) throw error;
|
|
3436
4018
|
if (!provisionalCleanupSafe) {
|
|
3437
4019
|
await params.abandonClient();
|
|
3438
4020
|
throw error;
|
|
3439
4021
|
}
|
|
3440
4022
|
const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
|
|
4023
|
+
const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
|
|
3441
4024
|
let cleanupStateError;
|
|
3442
|
-
if (
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
} catch (stateError) {
|
|
3451
|
-
cleanupStateError = stateError;
|
|
3452
|
-
}
|
|
4025
|
+
if (cleanupExpected && !isDeepStrictEqual(cleanupExpected, nextPending)) try {
|
|
4026
|
+
await params.bindingStore.mutate(params.bindingIdentity, {
|
|
4027
|
+
kind: "patch-pending-supervision-branch",
|
|
4028
|
+
expected: cleanupExpected,
|
|
4029
|
+
pending: nextPending
|
|
4030
|
+
});
|
|
4031
|
+
} catch (stateError) {
|
|
4032
|
+
cleanupStateError = stateError;
|
|
3453
4033
|
}
|
|
3454
4034
|
const unsafeCleanup = cleanup.remaining.length > 0 || isCodexAppServerUnsafeSubscriptionError(error);
|
|
3455
4035
|
if (unsafeCleanup) await params.abandonClient();
|
|
3456
4036
|
if (cleanupStateError) {
|
|
3457
|
-
const cause = new AggregateError([error, cleanupStateError]);
|
|
4037
|
+
const cause = new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
|
|
3458
4038
|
if (unsafeCleanup) throw new CodexAppServerUnsafeSubscriptionError("Codex supervised branch cleanup state could not be recorded", { cause });
|
|
3459
|
-
throw
|
|
4039
|
+
throw cause;
|
|
3460
4040
|
}
|
|
3461
4041
|
if (cleanup.remaining.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup remains pending: ${cleanup.remaining.join(", ")}`, { cause: error });
|
|
3462
4042
|
throw error;
|
|
@@ -3484,7 +4064,7 @@ function buildPendingSupervisionProbeForkParams(params, pending) {
|
|
|
3484
4064
|
...params.appServer.serviceTier !== void 0 ? { serviceTier: params.appServer.serviceTier } : {},
|
|
3485
4065
|
config: runtimeConfig,
|
|
3486
4066
|
developerInstructions: params.developerInstructions ?? buildDeveloperInstructions(params.attempt, { dynamicTools: params.dynamicTools }),
|
|
3487
|
-
ephemeral:
|
|
4067
|
+
ephemeral: true,
|
|
3488
4068
|
threadSource: "appServer",
|
|
3489
4069
|
excludeTurns: true
|
|
3490
4070
|
};
|
|
@@ -3526,10 +4106,8 @@ function requireDistinctSupervisionThreadId(params) {
|
|
|
3526
4106
|
return threadId;
|
|
3527
4107
|
}
|
|
3528
4108
|
function readSupervisionResponseThreadId(value) {
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
if (!thread || typeof thread !== "object" || Array.isArray(thread)) return;
|
|
3532
|
-
return thread.id;
|
|
4109
|
+
const thread = isRecord(value) ? value.thread : void 0;
|
|
4110
|
+
return isRecord(thread) ? thread.id : void 0;
|
|
3533
4111
|
}
|
|
3534
4112
|
async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
3535
4113
|
if (!pending.cleanupThreadIds?.length) return pending;
|
|
@@ -3552,20 +4130,6 @@ async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
|
3552
4130
|
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
|
|
3553
4131
|
return next;
|
|
3554
4132
|
}
|
|
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
4133
|
function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
|
|
3570
4134
|
return {
|
|
3571
4135
|
sourceThreadId: pending.sourceThreadId,
|
|
@@ -3602,15 +4166,10 @@ async function archiveSupervisionArtifact(client, threadId) {
|
|
|
3602
4166
|
async function startOrResumeThread(params) {
|
|
3603
4167
|
const incognito = isIncognitoSessionKey(params.params.sessionKey);
|
|
3604
4168
|
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));
|
|
4169
|
+
return await withCodexThreadLifecycleBinding(params, async (bindingIdentity, currentBinding) => {
|
|
4170
|
+
let binding = currentBinding;
|
|
4171
|
+
const preflight = await prepareCodexThreadLifecyclePreflight(params);
|
|
4172
|
+
const { contextEngineBinding, dynamicToolsContainDeferred, dynamicToolsFingerprint, environmentSelectionFingerprint, hostSystemAgentActive, legacyDynamicToolsFingerprint, legacyUserMcpServersFingerprint, lifecycleTiming, nativeSkillIsolation, nativeSkillIsolationFingerprint, networkProxyConfigFingerprint, ringZeroActive, ringZeroClientInstanceId, ringZeroConfigFingerprint, restrictedToolSurface, restrictedToolSurfaceInheritedMcpServerNames, userMcpServersConfigPatch, userMcpServersFingerprint, webSearchThreadConfigFingerprint } = preflight;
|
|
3614
4173
|
let replacementPredecessor;
|
|
3615
4174
|
const initialBoundThreadId = binding?.threadId;
|
|
3616
4175
|
const initialBoundClientId = binding?.clientId;
|
|
@@ -3622,26 +4181,33 @@ async function startOrResumeThread(params) {
|
|
|
3622
4181
|
config: params.params.config
|
|
3623
4182
|
});
|
|
3624
4183
|
const throwIfAborted = () => throwIfCodexThreadLifecycleAborted(params.signal);
|
|
3625
|
-
const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId) => {
|
|
4184
|
+
const releaseRetainedThread = async (threadId, ownerClientId = initialBoundClientId, assertCurrent) => {
|
|
3626
4185
|
if (ownerClientId && ownerClientId !== clientId) {
|
|
3627
4186
|
const previousClient = retainSharedCodexAppServerClientByInstanceId(ownerClientId);
|
|
3628
|
-
if (!previousClient) return;
|
|
4187
|
+
if (!previousClient) return false;
|
|
3629
4188
|
try {
|
|
3630
|
-
|
|
4189
|
+
const assertPrevious = assertCurrent ? captureExclusiveSharedCodexAppServerClient(previousClient.client) : void 0;
|
|
4190
|
+
if (isCodexAppServerLiveThreadClaimed(previousClient.client, threadId)) throw new Error(`Codex thread ${threadId} is claimed by active work; stop it first.`);
|
|
4191
|
+
return await releaseCodexRetainedLiveThread({
|
|
3631
4192
|
client: previousClient.client,
|
|
3632
4193
|
lifecycleTiming,
|
|
3633
|
-
threadId
|
|
4194
|
+
threadId,
|
|
4195
|
+
assertCurrent: assertCurrent ? () => {
|
|
4196
|
+
assertCurrent();
|
|
4197
|
+
assertPrevious?.();
|
|
4198
|
+
} : void 0
|
|
3634
4199
|
});
|
|
3635
4200
|
} finally {
|
|
3636
4201
|
previousClient.release();
|
|
3637
4202
|
}
|
|
3638
|
-
return;
|
|
3639
4203
|
}
|
|
3640
|
-
|
|
4204
|
+
if (isCodexAppServerLiveThreadClaimed(params.client, threadId)) throw new Error(`Codex thread ${threadId} is claimed by active work; stop it first.`);
|
|
4205
|
+
return await releaseCodexRetainedLiveThread({
|
|
3641
4206
|
client: params.client,
|
|
3642
4207
|
abandonClient: params.abandonClient,
|
|
3643
4208
|
lifecycleTiming,
|
|
3644
|
-
threadId
|
|
4209
|
+
threadId,
|
|
4210
|
+
assertCurrent
|
|
3645
4211
|
});
|
|
3646
4212
|
};
|
|
3647
4213
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
@@ -3723,6 +4289,36 @@ async function startOrResumeThread(params) {
|
|
|
3723
4289
|
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
3724
4290
|
binding = void 0;
|
|
3725
4291
|
};
|
|
4292
|
+
const resolveRequestContext = () => {
|
|
4293
|
+
const startModelSelection = resolveCodexAppServerThreadModelSelection({
|
|
4294
|
+
provider: params.params.provider,
|
|
4295
|
+
model: params.runtimeModelId ?? params.params.modelId,
|
|
4296
|
+
binding,
|
|
4297
|
+
authProfileId: params.params.authProfileId,
|
|
4298
|
+
authProfileStore: params.params.authProfileStore,
|
|
4299
|
+
agentDir: params.params.agentDir,
|
|
4300
|
+
config: params.params.config
|
|
4301
|
+
});
|
|
4302
|
+
return {
|
|
4303
|
+
...preflight,
|
|
4304
|
+
bindingIdentity,
|
|
4305
|
+
startModelSelection,
|
|
4306
|
+
startModelProvider: startModelSelection.modelProvider,
|
|
4307
|
+
normalizeBindingModelProvider,
|
|
4308
|
+
throwIfAborted
|
|
4309
|
+
};
|
|
4310
|
+
};
|
|
4311
|
+
const transientDelegationRestriction = params.params.delegationCapability === "report_only";
|
|
4312
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
4313
|
+
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
4314
|
+
const transientWebSearchRestriction = isTransientWebSearchRestriction(params);
|
|
4315
|
+
if (binding?.pendingResumeConfiguration) return await resumePendingCodexThread(params, {
|
|
4316
|
+
...resolveRequestContext(),
|
|
4317
|
+
binding,
|
|
4318
|
+
clearCurrentBinding,
|
|
4319
|
+
releaseRetainedThread: (threadId, assertCurrent) => releaseRetainedThread(threadId, initialBoundClientId, assertCurrent),
|
|
4320
|
+
transientRestriction: transientDelegationRestriction || transientNativeToolRestriction || transientWebSearchRestriction
|
|
4321
|
+
});
|
|
3726
4322
|
if (binding?.threadId && !restrictedToolSurface && binding.nativeToolPolicyRestricted === true) await clearCurrentBinding("rotating a host-policy-restricted thread binding");
|
|
3727
4323
|
if (binding?.threadId && binding.nativeSkillIsolationFingerprint !== nativeSkillIsolationFingerprint) {
|
|
3728
4324
|
embeddedAgentLog.debug("codex app-server native skill isolation changed; starting a new thread", { threadId: binding.threadId });
|
|
@@ -3756,24 +4352,30 @@ async function startOrResumeThread(params) {
|
|
|
3756
4352
|
await clearCurrentBinding("rotating a GPT-5.6 multi-agent thread binding");
|
|
3757
4353
|
binding = void 0;
|
|
3758
4354
|
}
|
|
3759
|
-
const
|
|
3760
|
-
provider: params.params.provider,
|
|
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";
|
|
4355
|
+
const requestContext = resolveRequestContext();
|
|
3770
4356
|
let preserveExistingBinding = transientDelegationRestriction || !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
3771
4357
|
let rotatedContextEngineBinding = false;
|
|
3772
4358
|
let prebuiltPluginThreadConfig;
|
|
4359
|
+
const buildLoadedPluginThreadConfig = async (current) => {
|
|
4360
|
+
if (!params.pluginThreadConfig?.requiresCurrentPolicyCheck && !shouldRecheckRecoverablePluginBinding({
|
|
4361
|
+
binding: current,
|
|
4362
|
+
pluginThreadConfig: params.pluginThreadConfig
|
|
4363
|
+
})) return;
|
|
4364
|
+
try {
|
|
4365
|
+
prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: current.threadId }));
|
|
4366
|
+
} catch (error) {
|
|
4367
|
+
throwIfAborted();
|
|
4368
|
+
if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
|
|
4369
|
+
embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
|
|
4370
|
+
error,
|
|
4371
|
+
threadId: current.threadId
|
|
4372
|
+
});
|
|
4373
|
+
return;
|
|
4374
|
+
}
|
|
4375
|
+
throwIfAborted();
|
|
4376
|
+
return prebuiltPluginThreadConfig;
|
|
4377
|
+
};
|
|
3773
4378
|
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
4379
|
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
3778
4380
|
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
3779
4381
|
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) {
|
|
@@ -3851,28 +4453,13 @@ async function startOrResumeThread(params) {
|
|
|
3851
4453
|
binding = void 0;
|
|
3852
4454
|
}
|
|
3853
4455
|
if (binding?.threadId) {
|
|
3854
|
-
|
|
4456
|
+
if (isCodexPluginThreadBindingStale({
|
|
3855
4457
|
codexPluginsEnabled: params.pluginThreadConfig?.enabled ?? false,
|
|
3856
4458
|
bindingFingerprint: binding.pluginAppsFingerprint,
|
|
3857
4459
|
bindingInputFingerprint: binding.pluginAppsInputFingerprint,
|
|
3858
4460
|
currentInputFingerprint: params.pluginThreadConfig?.inputFingerprint,
|
|
3859
4461
|
hasBindingPolicyContext: Boolean(binding.pluginAppPolicyContext)
|
|
3860
|
-
})
|
|
3861
|
-
if (!pluginBindingStale && (params.pluginThreadConfig?.requiresCurrentPolicyCheck || shouldRecheckRecoverablePluginBinding({
|
|
3862
|
-
binding,
|
|
3863
|
-
pluginThreadConfig: params.pluginThreadConfig
|
|
3864
|
-
}))) try {
|
|
3865
|
-
const bindingThreadId = binding.threadId;
|
|
3866
|
-
prebuiltPluginThreadConfig = await lifecycleTiming.measure("plugin-config-recovery", () => params.pluginThreadConfig?.build({ threadId: bindingThreadId }));
|
|
3867
|
-
pluginBindingStale = prebuiltPluginThreadConfig?.fingerprint !== binding.pluginAppsFingerprint;
|
|
3868
|
-
} catch (error) {
|
|
3869
|
-
if (params.pluginThreadConfig?.requiresCurrentPolicyCheck) throw error;
|
|
3870
|
-
embeddedAgentLog.warn("codex app-server plugin app config recovery check failed", {
|
|
3871
|
-
error,
|
|
3872
|
-
threadId: binding.threadId
|
|
3873
|
-
});
|
|
3874
|
-
}
|
|
3875
|
-
if (pluginBindingStale) {
|
|
4462
|
+
})) {
|
|
3876
4463
|
embeddedAgentLog.debug("codex app-server plugin app config changed; starting a new thread", { threadId: binding.threadId });
|
|
3877
4464
|
await clearCurrentBinding("rotating a stale thread binding");
|
|
3878
4465
|
binding = void 0;
|
|
@@ -3885,120 +4472,70 @@ async function startOrResumeThread(params) {
|
|
|
3885
4472
|
binding = void 0;
|
|
3886
4473
|
}
|
|
3887
4474
|
}
|
|
3888
|
-
if (binding?.threadId)
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
3895
|
-
else {
|
|
3896
|
-
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
3897
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
3898
|
-
}
|
|
3899
|
-
} else if (incognito) {
|
|
3900
|
-
if (binding.clientId && binding.clientId === clientId) {
|
|
3901
|
-
params.buildFinalConfigPatch?.({
|
|
3902
|
-
action: "resume",
|
|
3903
|
-
binding
|
|
4475
|
+
if (binding?.threadId) {
|
|
4476
|
+
if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
|
|
4477
|
+
assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
|
|
4478
|
+
preserveExistingBinding = shouldStartTransientNoToolThread({
|
|
4479
|
+
previous: binding.dynamicToolsFingerprint,
|
|
4480
|
+
nextHasDynamicTools: params.dynamicTools.length > 0
|
|
3904
4481
|
});
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
4482
|
+
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
4483
|
+
else {
|
|
4484
|
+
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
4485
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
4486
|
+
}
|
|
4487
|
+
} else if (incognito) {
|
|
4488
|
+
if (binding.clientId && binding.clientId === clientId && ((await buildLoadedPluginThreadConfig(binding))?.fingerprint ?? binding.pluginAppsFingerprint) === binding.pluginAppsFingerprint) {
|
|
4489
|
+
params.buildFinalConfigPatch?.({
|
|
4490
|
+
action: "resume",
|
|
4491
|
+
binding
|
|
4492
|
+
});
|
|
4493
|
+
throwIfAborted();
|
|
4494
|
+
lifecycleTiming.mark("thread-ready");
|
|
4495
|
+
lifecycleTiming.logSummary({
|
|
4496
|
+
runId: params.params.runId,
|
|
4497
|
+
sessionId: params.params.sessionId,
|
|
4498
|
+
sessionKey: params.params.sessionKey,
|
|
4499
|
+
threadId: binding.threadId,
|
|
4500
|
+
action: "resumed"
|
|
4501
|
+
});
|
|
4502
|
+
return {
|
|
4503
|
+
...binding,
|
|
4504
|
+
lifecycle: { action: "resumed" }
|
|
4505
|
+
};
|
|
4506
|
+
}
|
|
4507
|
+
await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
|
|
4508
|
+
binding = void 0;
|
|
4509
|
+
} else {
|
|
4510
|
+
const warmReuse = await tryReuseCodexLiveThread({
|
|
4511
|
+
...requestContext,
|
|
4512
|
+
params,
|
|
4513
|
+
binding,
|
|
4514
|
+
clientId,
|
|
4515
|
+
buildLoadedPluginThreadConfig
|
|
3913
4516
|
});
|
|
3914
|
-
return
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
4517
|
+
if (warmReuse.kind === "ready") return warmReuse.binding;
|
|
4518
|
+
if (warmReuse.kind === "rotate") {
|
|
4519
|
+
throwIfAborted();
|
|
4520
|
+
await clearCurrentBinding("rotating a stale plugin app binding");
|
|
4521
|
+
} else {
|
|
4522
|
+
if (binding.preserveNativeModel === true) await assertAdoptedCodexThreadResumeAllowed(params, binding.threadId, requestContext);
|
|
4523
|
+
await releaseRetainedThread(binding.threadId);
|
|
4524
|
+
const resumed = await resumeExistingCodexThread(params, {
|
|
4525
|
+
...requestContext,
|
|
4526
|
+
binding,
|
|
4527
|
+
clearCurrentBinding,
|
|
4528
|
+
prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
|
|
4529
|
+
prebuiltPluginThreadConfig,
|
|
4530
|
+
buildLoadedPluginThreadConfig
|
|
4531
|
+
});
|
|
4532
|
+
if (resumed) return resumed;
|
|
4533
|
+
}
|
|
3918
4534
|
}
|
|
3919
|
-
await clearCurrentBinding("rotating an unavailable ephemeral thread binding");
|
|
3920
|
-
binding = void 0;
|
|
3921
|
-
} else {
|
|
3922
|
-
const warmReuse = await tryReuseCodexLiveThread({
|
|
3923
|
-
params,
|
|
3924
|
-
binding,
|
|
3925
|
-
bindingIdentity,
|
|
3926
|
-
clientId,
|
|
3927
|
-
dynamicToolsFingerprint,
|
|
3928
|
-
environmentSelectionFingerprint,
|
|
3929
|
-
hostSystemAgentActive,
|
|
3930
|
-
lifecycleTiming,
|
|
3931
|
-
nativeSkillIsolation,
|
|
3932
|
-
releaseConsumedThread: (threadId, cause) => releaseCodexConsumedLiveThread({
|
|
3933
|
-
client: params.client,
|
|
3934
|
-
abandonClient: params.abandonClient,
|
|
3935
|
-
lifecycleTiming,
|
|
3936
|
-
threadId,
|
|
3937
|
-
cause
|
|
3938
|
-
}),
|
|
3939
|
-
ringZeroActive,
|
|
3940
|
-
restrictedToolSurfaceInheritedMcpServerNames,
|
|
3941
|
-
startModelProvider,
|
|
3942
|
-
startModelSelection,
|
|
3943
|
-
throwIfAborted,
|
|
3944
|
-
userMcpServersConfigPatch
|
|
3945
|
-
});
|
|
3946
|
-
if (warmReuse.binding) return warmReuse.binding;
|
|
3947
|
-
await releaseRetainedThread(binding.threadId);
|
|
3948
|
-
const resumed = await resumeExistingCodexThread(params, {
|
|
3949
|
-
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
|
-
clearCurrentBinding,
|
|
3973
|
-
prebuiltFinalConfigPatch: warmReuse.prebuiltFinalConfigPatch,
|
|
3974
|
-
prebuiltPluginThreadConfig
|
|
3975
|
-
});
|
|
3976
|
-
if (resumed) return resumed;
|
|
3977
4535
|
}
|
|
3978
4536
|
if (initialBoundThreadId && !preserveExistingBinding && !replacementPredecessor) await releaseRetainedThread(initialBoundThreadId);
|
|
3979
4537
|
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,
|
|
4538
|
+
...requestContext,
|
|
4002
4539
|
prebuiltPluginThreadConfig,
|
|
4003
4540
|
preserveExistingBinding,
|
|
4004
4541
|
rotatedContextEngineBinding,
|
|
@@ -4063,10 +4600,17 @@ function buildTurnStartParams(params, options) {
|
|
|
4063
4600
|
});
|
|
4064
4601
|
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
4065
4602
|
const currentSenderContext = params.trigger === "user" ? buildCodexCurrentSenderContextValue(params) : void 0;
|
|
4066
|
-
|
|
4603
|
+
let additionalContext = currentSenderContext ? { openclaw_current_sender: {
|
|
4067
4604
|
kind: "untrusted",
|
|
4068
4605
|
value: currentSenderContext
|
|
4069
4606
|
} } : void 0;
|
|
4607
|
+
if (params.permissionChange?.notice) additionalContext = {
|
|
4608
|
+
...additionalContext,
|
|
4609
|
+
openclaw_permission_change: {
|
|
4610
|
+
kind: "application",
|
|
4611
|
+
value: params.permissionChange.notice
|
|
4612
|
+
}
|
|
4613
|
+
};
|
|
4070
4614
|
return {
|
|
4071
4615
|
threadId: options.threadId,
|
|
4072
4616
|
input: [...buildCodexUserInput(options.promptText ?? params.prompt, params.images), ...options.explicitSkillInputs ?? []],
|
|
@@ -4105,7 +4649,6 @@ function buildTurnCollaborationMode(params, options = {}) {
|
|
|
4105
4649
|
function buildTurnScopedCollaborationInstructions(params, options = {}) {
|
|
4106
4650
|
const contextInstructions = joinPresentSections(options.turnScopedDeveloperInstructions, options.memoryCollaborationInstructions, options.skillsCollaborationInstructions);
|
|
4107
4651
|
if (params.trigger === "cron") return joinPresentSections(buildCronCollaborationInstructions(), contextInstructions);
|
|
4108
|
-
if (params.trigger === "heartbeat") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions);
|
|
4109
4652
|
if (contextInstructions?.trim()) return joinPresentSections(buildDefaultCollaborationInstructions(), contextInstructions);
|
|
4110
4653
|
return null;
|
|
4111
4654
|
}
|
|
@@ -4132,15 +4675,8 @@ function buildCronCollaborationInstructions() {
|
|
|
4132
4675
|
"Keep output concise and automation-oriented. Prefer the final command result or a short failure summary over status narration."
|
|
4133
4676
|
].join("\n\n");
|
|
4134
4677
|
}
|
|
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
4678
|
function joinPresentSections(...sections) {
|
|
4143
4679
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
4144
4680
|
}
|
|
4145
4681
|
//#endregion
|
|
4146
|
-
export {
|
|
4682
|
+
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 };
|