@openclaw/codex 2026.9.1-beta.1 → 2026.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +5 -6
- package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-DYHcRQAa.js} +31 -45
- package/dist/attempt-context-DmQUNWwT.js +634 -0
- package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
- package/dist/{binding-connection-BMvPX73o.js → binding-connection-E6uDsPlx.js} +2 -2
- package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-wzJrwKKe.js} +15 -20
- package/dist/{capabilities-BRMh0aIq.js → capabilities-dIjNVCYH.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-DEsIufkt.js → command-formatters-CLHhEB1Q.js} +5 -4
- package/dist/{command-handlers-D_-1_qRp.js → command-handlers-CTGb2csu.js} +124 -101
- package/dist/{compact-BglNFZrQ.js → compact-_t0HQrUS.js} +38 -29
- package/dist/{config-giW8X0QA.js → config-BhmfC1s6.js} +294 -282
- package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
- package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
- package/dist/doctor-contract-api.js +143 -12
- package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BU9tmlYm.js} +599 -655
- package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
- package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
- package/dist/{event-projector-usage-Bk1I90Bg.js → event-projector-usage-C5mcrGjp.js} +15 -148
- package/dist/{harness-Csz0F-DK.js → harness-DXVZ5II5.js} +42 -52
- package/dist/harness.js +1 -1
- package/dist/index.js +458 -404
- package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-kg9kv4iH.js} +4 -4
- package/dist/launch-args-RXQwn8zV.js +90 -0
- package/dist/{managed-binary-DFYK4DWq.js → managed-binary-4-Vjz5GH.js} +14 -9
- package/dist/media-understanding-provider.js +1 -1
- package/dist/model-catalog-CUM68Hkl.js +101 -0
- package/dist/{models-TS3HBU-q.js → models-sYnEc0gd.js} +25 -28
- package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-CnH147jH.js} +55 -115
- package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-CstNHCmC.js} +9 -6
- package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-CNe-Xrjx.js} +30 -3
- package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-CfFlKqDl.js} +15 -14
- package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BFrPhb2c.js} +6 -6
- package/dist/provider-capabilities-DW5p4k9w.js +101 -0
- package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-CawQJfUS.js} +3 -2
- package/dist/{sandbox-guard-kuWTn9C5.js → request-DYAI5FQE.js} +195 -2
- package/dist/{run-attempt-DYc0QCBF.js → run-attempt-D1hJO4fX.js} +828 -2100
- package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-C3jVimFn.js} +21 -14
- package/dist/{session-binding-Bcynfd-_.js → session-binding-BHuTCRZQ.js} +198 -127
- package/dist/{session-catalog-DaLKVtpH.js → session-catalog-yyvTWFOw.js} +564 -408
- package/dist/{session-cli-DMc4oS2w.js → session-cli-CmFEXMAM.js} +1 -1
- package/dist/session-history-C5J1AKzG.js +87 -0
- package/dist/session-initialization-BeWzjGiR.js +74 -0
- package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-DeWVTCMj.js} +2 -2
- package/dist/session-retirement-BujDjXVF.js +97 -0
- package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-C351-5Qw.js} +19 -4
- package/dist/settled-turn-context-D3SNWtUn.js +268 -0
- package/dist/settled-turn-finalizer-BVJx4yoc.js +104 -0
- package/dist/{shared-client-DKz23hHY.js → shared-client-DxcfF9KX.js} +3480 -679
- package/dist/{side-question-DjA_jsNe.js → side-question-BTZAII4l.js} +108 -70
- package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DAcEJ_Om.js} +2188 -1915
- package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-zGokd8i1.js} +25 -14
- package/dist/thread-policy-g4KbJPzp.js +54 -0
- package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-C7pyVKeW.js} +59 -7
- package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-DTUxFWa_.js} +79 -17
- package/dist/transport-stdio-CyPldczm.js +726 -0
- package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
- package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
- package/dist/{usage-ROIaW3wo.js → usage-Bj-rrzkR.js} +3 -3
- package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
- package/openclaw.plugin.json +1 -30
- package/package.json +6 -6
- package/dist/command-rpc-CCeCkf_v.js +0 -90
- package/dist/incognito-session-ET_ufDH-.js +0 -2
- package/dist/model-catalog-i9rNBthX.js +0 -48
- package/dist/protocol-validators-VeJxxJ0U.js +0 -2421
- package/dist/request-DXXGHko0.js +0 -157
- package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
- package/dist/session-retirement-CcNC3YSY.js +0 -36
- package/dist/settled-turn-finalizer-Dc3ApjU4.js +0 -328
- package/dist/thread-resume-BE9qNEW0.js +0 -30
- package/dist/transport-stdio-DhoGy33Z.js +0 -99
- package/dist/upstream-session-fork-DlaHxSLC.js +0 -326
|
@@ -1,40 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity } from "./session-binding-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { n as
|
|
7
|
-
import { r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
12
|
-
import {
|
|
13
|
-
import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-
|
|
1
|
+
import { $t as isJsonObject, Jt as readCodexSessionMeta, K as resolveCodexAppServerClientInstanceId, Nt as withTimeout, Vt as resolveCodexAppServerAuthProfileStore, Wt as resolveCodexAppServerPreparedAuthHandoff, Xt as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, Yt as CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES, Z as assertCodexThreadForkParams, c as getLeasedSharedCodexAppServerClient, h as releaseLeasedSharedCodexAppServerClient, q as CodexAppServerRpcError, qt as isOpenClawManagedCodexThread, y as retireSharedCodexAppServerClientIfCurrent, zt as resolveCodexAppServerAuthProfileId } from "./shared-client-DxcfF9KX.js";
|
|
2
|
+
import { E as readCodexPluginConfig, F as resolveCodexAppServerHomeDir, I as resolveCodexAppServerLocalHomeDir, L as resolveCodexAppServerUserHomeDir, i as resolveCodexAppServerRuntimeOptions, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-BhmfC1s6.js";
|
|
3
|
+
import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity } from "./session-binding-BHuTCRZQ.js";
|
|
4
|
+
import { a as closeCodexStartupClientBestEffort } from "./attempt-client-cleanup-DYHcRQAa.js";
|
|
5
|
+
import { n as listCodexAppServerModels } from "./models-sYnEc0gd.js";
|
|
6
|
+
import { a as replaceCodexCatalogConnectionHomes, n as buildCodexAppServerConnectionFingerprint, t as resumeCodexAppServerThread } from "./thread-resume-C7pyVKeW.js";
|
|
7
|
+
import { g as codexCatalogHomeId, h as canonicalCodexCatalogHome, m as projectCodexUserItemText, r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-DTUxFWa_.js";
|
|
8
|
+
import { i as requestCodexAppServerJson, o as withCodexAppServerJsonClient, r as requestCodexAppServerClientJson } from "./request-DYAI5FQE.js";
|
|
9
|
+
import { n as describeControlFailure, t as CODEX_CONTROL_METHODS } from "./capabilities-dIjNVCYH.js";
|
|
10
|
+
import { a as formatCodexDisplayText } from "./command-formatters-CLHhEB1Q.js";
|
|
11
|
+
import { n as createCodexCliNodeConversationBindingData } from "./conversation-binding-data-BiodpIzq.js";
|
|
12
|
+
import { n as prepareCodexAppServerAuthBinding } from "./auth-binding-Ds_QVm-X.js";
|
|
13
|
+
import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-C351-5Qw.js";
|
|
14
14
|
import { asFiniteNumber, isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
15
15
|
import fs from "node:fs/promises";
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
18
18
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
19
|
+
import { prepareAgentRuntimeAuth } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
20
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
19
21
|
import fsSync from "node:fs";
|
|
20
22
|
import os from "node:os";
|
|
21
|
-
import { listAgentIds, resolveAgentDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent
|
|
23
|
+
import { listAgentIds, resolveAgentDir as resolveAgentDir$1, resolveAgentWorkspaceDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
|
|
22
24
|
import { runCommandBuffered } from "openclaw/plugin-sdk/process-runtime";
|
|
25
|
+
import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
23
26
|
import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
|
|
24
|
-
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
27
|
+
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
25
28
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
26
29
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
27
30
|
import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
|
|
28
31
|
import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
29
|
-
import { root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
30
32
|
import process$1 from "node:process";
|
|
33
|
+
import { resolveSessionModelRef } from "openclaw/plugin-sdk/model-session-runtime";
|
|
34
|
+
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
|
|
31
35
|
import { createSessionCatalogAdoptionCoordinator, listSessionCatalogEntries, sessionCatalogAdoptedSessionKey, sessionCatalogAdoptedSourceKey } from "openclaw/plugin-sdk/session-catalog";
|
|
32
|
-
import { decodeNodePtyResumeParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
|
|
33
|
-
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
34
|
-
import { createZstdDecompress } from "node:zlib";
|
|
36
|
+
import { decodeNodePtyResumeParams, decodeNodePtyStartParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
|
|
35
37
|
//#region extensions/codex/src/jsonl-lines.ts
|
|
36
|
-
const JSONL_STREAM_THRESHOLD_BYTES =
|
|
37
|
-
const JSONL_READ_CHUNK_BYTES =
|
|
38
|
+
const JSONL_STREAM_THRESHOLD_BYTES = 4194304;
|
|
39
|
+
const JSONL_READ_CHUNK_BYTES = 1048576;
|
|
38
40
|
async function visitJsonlLines(file, visitor, chunkBytes = JSONL_READ_CHUNK_BYTES) {
|
|
39
41
|
let size;
|
|
40
42
|
try {
|
|
@@ -134,7 +136,7 @@ const CODEX_CLI_SESSIONS_LIST_COMMAND = "codex.cli.sessions.list";
|
|
|
134
136
|
const CODEX_CLI_SESSION_RESUME_COMMAND = "codex.cli.session.resume";
|
|
135
137
|
const DEFAULT_SESSION_LIMIT = 10;
|
|
136
138
|
const MAX_SESSION_LIMIT = 50;
|
|
137
|
-
const DEFAULT_RESUME_TIMEOUT_MS =
|
|
139
|
+
const DEFAULT_RESUME_TIMEOUT_MS = 12e5;
|
|
138
140
|
const SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
139
141
|
const activeResumeSessions = /* @__PURE__ */ new Set();
|
|
140
142
|
function createCodexCliSessionNodeHostCommands() {
|
|
@@ -525,7 +527,7 @@ function normalizeLimit$1(value) {
|
|
|
525
527
|
return typeof value === "number" && Number.isFinite(value) ? Math.min(MAX_SESSION_LIMIT, Math.max(1, Math.floor(value))) : DEFAULT_SESSION_LIMIT;
|
|
526
528
|
}
|
|
527
529
|
function normalizeTimeoutMs(value) {
|
|
528
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(
|
|
530
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(36e5, Math.floor(value)) : DEFAULT_RESUME_TIMEOUT_MS;
|
|
529
531
|
}
|
|
530
532
|
function truncateText(value, max) {
|
|
531
533
|
if (value.length <= max) return value;
|
|
@@ -546,6 +548,198 @@ function formatNodeLabel(node) {
|
|
|
546
548
|
].filter(Boolean).join(" / ") || "node";
|
|
547
549
|
}
|
|
548
550
|
//#endregion
|
|
551
|
+
//#region extensions/codex/src/command-rpc.ts
|
|
552
|
+
async function prepareControlAuth(options, startOptions) {
|
|
553
|
+
if (!options.onResponse || !options.config || !options.sessionKey || options.authProfileId === null || startOptions.homeScope === "user") return {
|
|
554
|
+
authProfileId: options.authProfileId ?? void 0,
|
|
555
|
+
clientOptions: { authProfileId: options.authProfileId }
|
|
556
|
+
};
|
|
557
|
+
const config = options.config;
|
|
558
|
+
const { sessionAgentId } = resolveSessionAgentIdsStrict({
|
|
559
|
+
config,
|
|
560
|
+
sessionKey: options.sessionKey,
|
|
561
|
+
agentId: options.agentId
|
|
562
|
+
});
|
|
563
|
+
const agentDir = options.agentDir ?? resolveAgentDir$1(config, sessionAgentId);
|
|
564
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, sessionAgentId);
|
|
565
|
+
const entry = getSessionEntry({
|
|
566
|
+
agentId: sessionAgentId,
|
|
567
|
+
storePath: resolveStorePath(config.session?.store, { agentId: sessionAgentId }),
|
|
568
|
+
sessionKey: options.sessionKey,
|
|
569
|
+
hydrateSkillPromptRefs: false,
|
|
570
|
+
readConsistency: "latest"
|
|
571
|
+
});
|
|
572
|
+
const model = resolveSessionModelRef(config, entry, sessionAgentId);
|
|
573
|
+
const store = resolveCodexAppServerAuthProfileStore({
|
|
574
|
+
agentDir,
|
|
575
|
+
config
|
|
576
|
+
});
|
|
577
|
+
const { plan, attempts } = prepareAgentRuntimeAuth({
|
|
578
|
+
provider: model.provider,
|
|
579
|
+
modelId: model.model,
|
|
580
|
+
config,
|
|
581
|
+
agentDir,
|
|
582
|
+
workspaceDir,
|
|
583
|
+
authProfileStore: store,
|
|
584
|
+
sessionAuthProfileId: entry?.authProfileOverride ?? options.authProfileId,
|
|
585
|
+
sessionAuthProfileSource: entry?.authProfileOverrideSource,
|
|
586
|
+
harnessId: "codex",
|
|
587
|
+
harnessAuthBootstrap: "harness"
|
|
588
|
+
});
|
|
589
|
+
const route = plan.modelRoute;
|
|
590
|
+
const resolvedAuth = route ? await resolveApiKeyForProvider({
|
|
591
|
+
provider: route.provider,
|
|
592
|
+
modelId: route.modelId,
|
|
593
|
+
modelApi: route.api,
|
|
594
|
+
cfg: config,
|
|
595
|
+
agentDir,
|
|
596
|
+
workspaceDir,
|
|
597
|
+
store,
|
|
598
|
+
profileId: attempts[0]?.profileId,
|
|
599
|
+
lockedProfile: plan.forwardedAuthProfileSource === "user",
|
|
600
|
+
allowAuthProfileFallback: attempts[0]?.allowAuthProfileFallback,
|
|
601
|
+
skipSetupProviderFallback: true
|
|
602
|
+
}) : void 0;
|
|
603
|
+
const handoff = await resolveCodexAppServerPreparedAuthHandoff({
|
|
604
|
+
authRequirement: route?.authRequirement,
|
|
605
|
+
resolvedApiKey: resolvedAuth?.apiKey,
|
|
606
|
+
authProfileId: route ? plan.forwardedAuthProfileId : resolveCodexAppServerAuthProfileId({
|
|
607
|
+
authProfileId: plan.forwardedAuthProfileId,
|
|
608
|
+
store,
|
|
609
|
+
config
|
|
610
|
+
}),
|
|
611
|
+
authProfileStore: store,
|
|
612
|
+
agentDir,
|
|
613
|
+
homeScope: startOptions.homeScope ?? "agent",
|
|
614
|
+
config,
|
|
615
|
+
subscriptionProfileRequiredError: "Prepared Codex subscription route requires a forwarded OpenAI OAuth or token profile.",
|
|
616
|
+
subscriptionProfileUnusableError: "Prepared Codex subscription auth profile is unusable."
|
|
617
|
+
});
|
|
618
|
+
const binding = handoff.authProfileId ? await prepareCodexAppServerAuthBinding({
|
|
619
|
+
authProfileId: handoff.authProfileId,
|
|
620
|
+
authProfileStore: store,
|
|
621
|
+
agentDir,
|
|
622
|
+
config
|
|
623
|
+
}) : void 0;
|
|
624
|
+
return {
|
|
625
|
+
authProfileId: handoff.authProfileId,
|
|
626
|
+
clientOptions: {
|
|
627
|
+
...handoff.preparedAuth ? { preparedAuth: handoff.preparedAuth } : { authProfileId: handoff.authProfileId },
|
|
628
|
+
authRequirement: route?.authRequirement,
|
|
629
|
+
authProfileStore: binding?.authProfileStore ?? store,
|
|
630
|
+
authBindingFingerprint: binding?.fingerprint,
|
|
631
|
+
agentDir
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function requestOptions(pluginConfig, limit, config, agentDir) {
|
|
636
|
+
const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
637
|
+
return {
|
|
638
|
+
limit,
|
|
639
|
+
timeoutMs: runtime.requestTimeoutMs,
|
|
640
|
+
startOptions: runtime.start,
|
|
641
|
+
config,
|
|
642
|
+
agentDir
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
async function codexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
646
|
+
const runtime = options.startOptions ? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig }) : resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
647
|
+
const startOptions = options.startOptions ?? runtime.start;
|
|
648
|
+
const auth = await prepareControlAuth(options, startOptions);
|
|
649
|
+
const controlRequestOptions = {
|
|
650
|
+
timeoutMs: options.timeoutMs ?? runtime.requestTimeoutMs,
|
|
651
|
+
assertCurrent: options.assertCurrent,
|
|
652
|
+
startOptions,
|
|
653
|
+
config: options.config,
|
|
654
|
+
sessionKey: options.sessionKey,
|
|
655
|
+
sessionId: options.sessionId,
|
|
656
|
+
agentDir: options.agentDir,
|
|
657
|
+
isolated: options.isolated,
|
|
658
|
+
...auth.clientOptions
|
|
659
|
+
};
|
|
660
|
+
if (options.onResponse || options.beforeRequest) return await withCodexAppServerJsonClient(controlRequestOptions, async (request, client, scope) => {
|
|
661
|
+
await options.beforeRequest?.(request, client, scope);
|
|
662
|
+
scope.assertCurrent();
|
|
663
|
+
let response;
|
|
664
|
+
if (method === "thread/resume") {
|
|
665
|
+
if (!isJsonObject(requestParams) || typeof requestParams.threadId !== "string") throw new Error("Codex thread/resume requires a thread id.");
|
|
666
|
+
response = await resumeCodexAppServerThread({
|
|
667
|
+
client,
|
|
668
|
+
request: {
|
|
669
|
+
...requestParams,
|
|
670
|
+
threadId: requestParams.threadId
|
|
671
|
+
},
|
|
672
|
+
requestResume: () => request({
|
|
673
|
+
method,
|
|
674
|
+
requestParams
|
|
675
|
+
}),
|
|
676
|
+
abandonClient: () => closeCodexStartupClientBestEffort(client)
|
|
677
|
+
});
|
|
678
|
+
} else response = await request({
|
|
679
|
+
method,
|
|
680
|
+
requestParams
|
|
681
|
+
});
|
|
682
|
+
await options.onResponse?.(response, client, {
|
|
683
|
+
authProfileId: auth.authProfileId,
|
|
684
|
+
assertCurrent: scope.assertCurrent
|
|
685
|
+
});
|
|
686
|
+
scope.assertCurrent();
|
|
687
|
+
return response;
|
|
688
|
+
});
|
|
689
|
+
return await requestCodexAppServerJson({
|
|
690
|
+
method,
|
|
691
|
+
requestParams,
|
|
692
|
+
...controlRequestOptions
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
async function safeCodexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
696
|
+
return await safeValue(async () => await codexControlRequest(pluginConfig, method, requestParams, options));
|
|
697
|
+
}
|
|
698
|
+
async function safeCodexModelList(pluginConfig, limit, config, agentDir) {
|
|
699
|
+
return await safeValue(async () => await listCodexAppServerModels(requestOptions(pluginConfig, limit, config, agentDir)));
|
|
700
|
+
}
|
|
701
|
+
async function readCodexStatusProbes(pluginConfig, config, agentDir) {
|
|
702
|
+
const [models, account, limits, mcps, skills] = await Promise.all([
|
|
703
|
+
safeCodexModelList(pluginConfig, 20, config, agentDir),
|
|
704
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, {
|
|
705
|
+
config,
|
|
706
|
+
agentDir
|
|
707
|
+
}),
|
|
708
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, {
|
|
709
|
+
config,
|
|
710
|
+
agentDir
|
|
711
|
+
}),
|
|
712
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, {
|
|
713
|
+
config,
|
|
714
|
+
agentDir
|
|
715
|
+
}),
|
|
716
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, {
|
|
717
|
+
config,
|
|
718
|
+
agentDir
|
|
719
|
+
})
|
|
720
|
+
]);
|
|
721
|
+
return {
|
|
722
|
+
models,
|
|
723
|
+
account,
|
|
724
|
+
limits,
|
|
725
|
+
mcps,
|
|
726
|
+
skills
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
async function safeValue(read) {
|
|
730
|
+
try {
|
|
731
|
+
return {
|
|
732
|
+
ok: true,
|
|
733
|
+
value: await read()
|
|
734
|
+
};
|
|
735
|
+
} catch (error) {
|
|
736
|
+
return {
|
|
737
|
+
ok: false,
|
|
738
|
+
error: describeControlFailure(error)
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
//#endregion
|
|
549
743
|
//#region extensions/codex/src/app-server/thread-archive-guard.ts
|
|
550
744
|
const DESCENDANT_PAGE_LIMIT = 100;
|
|
551
745
|
const MAX_DESCENDANT_PAGES = 100;
|
|
@@ -613,7 +807,7 @@ const MAX_SESSION_PREVIEW_LENGTH = 500;
|
|
|
613
807
|
const MAX_SESSION_KEY_LENGTH = 1024;
|
|
614
808
|
const MAX_METADATA_LENGTH = 500;
|
|
615
809
|
const MAX_ACTIVE_FLAGS = 16;
|
|
616
|
-
const MAX_TRANSCRIPT_PAGE_BYTES =
|
|
810
|
+
const MAX_TRANSCRIPT_PAGE_BYTES = 20971520;
|
|
617
811
|
var CatalogParamsError = class extends Error {};
|
|
618
812
|
function readControlCursor(value, label) {
|
|
619
813
|
if (value === void 0 || value === null) return;
|
|
@@ -997,7 +1191,7 @@ async function createOrReuseNodeAdoptedSession(params) {
|
|
|
997
1191
|
key: nodeAdoptionSessionKey(params.hostId, params.record.threadId),
|
|
998
1192
|
agentId: params.agentId,
|
|
999
1193
|
recoverMatchingInitialEntry: true,
|
|
1000
|
-
|
|
1194
|
+
displayName: params.record.name ?? void 0,
|
|
1001
1195
|
...params.record.cwd?.trim() ? { spawnedCwd: params.record.cwd.trim() } : {},
|
|
1002
1196
|
initialEntry: {
|
|
1003
1197
|
agentHarnessId: "codex",
|
|
@@ -1040,182 +1234,6 @@ async function createOrReuseNodeAdoptedSession(params) {
|
|
|
1040
1234
|
}
|
|
1041
1235
|
}
|
|
1042
1236
|
//#endregion
|
|
1043
|
-
//#region extensions/codex/src/session-catalog-terminal.ts
|
|
1044
|
-
const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1";
|
|
1045
|
-
function resolveCodexCatalogTerminalHome(sources) {
|
|
1046
|
-
const runtimeConfig = sources.getRuntimeConfig();
|
|
1047
|
-
if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
|
|
1048
|
-
const agentDir = sources.source?.agentDir ?? (sources.agentId ? resolveAgentDir(runtimeConfig, sources.agentId) : resolveDefaultAgentDir(runtimeConfig));
|
|
1049
|
-
return resolveCodexAppServerLocalHomeDir(sources.source?.appServer.start ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig: sources.getPluginConfig() }).start, agentDir);
|
|
1050
|
-
}
|
|
1051
|
-
function resolveLocalCodexTerminalExecutable(env = process.env) {
|
|
1052
|
-
return resolveLocalCodexTerminalResolution(env)?.executable;
|
|
1053
|
-
}
|
|
1054
|
-
function resolveLocalCodexTerminalResolution(env = process.env) {
|
|
1055
|
-
return resolveNodeHostExecutable("codex", {
|
|
1056
|
-
env,
|
|
1057
|
-
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1058
|
-
strategy: "fallback"
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
function codexNodeTerminalCapability(node) {
|
|
1062
|
-
const commands = node.invocableCommands ?? node.commands;
|
|
1063
|
-
return node.connected === true && commands?.includes("codex.terminal.resume.v1") === true ? { canOpenTerminalCodex: true } : {};
|
|
1064
|
-
}
|
|
1065
|
-
async function requireCatalogEligibleThread(control, threadId) {
|
|
1066
|
-
const cached = await findCatalogEligibleThread(control, threadId, false);
|
|
1067
|
-
if (cached) return cached;
|
|
1068
|
-
const refreshed = await findCatalogEligibleThread(control, threadId, true);
|
|
1069
|
-
if (refreshed) return refreshed;
|
|
1070
|
-
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1071
|
-
}
|
|
1072
|
-
async function findCatalogEligibleThread(control, threadId, forceRefresh) {
|
|
1073
|
-
let cursor;
|
|
1074
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
1075
|
-
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1076
|
-
const page = await control.listPage({
|
|
1077
|
-
limit: 100,
|
|
1078
|
-
...cursor ? { cursor } : {},
|
|
1079
|
-
...forceRefresh ? { forceRefresh: true } : {}
|
|
1080
|
-
});
|
|
1081
|
-
const candidate = page.sessions.find((session) => session.threadId === threadId);
|
|
1082
|
-
if (candidate) {
|
|
1083
|
-
if (isInteractiveThreadSource(candidate.source)) return candidate;
|
|
1084
|
-
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1085
|
-
}
|
|
1086
|
-
const nextCursor = page.nextCursor?.trim();
|
|
1087
|
-
if (!nextCursor) return;
|
|
1088
|
-
if (seenCursors.has(nextCursor)) throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1089
|
-
seenCursors.add(nextCursor);
|
|
1090
|
-
cursor = nextCursor;
|
|
1091
|
-
}
|
|
1092
|
-
throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1093
|
-
}
|
|
1094
|
-
function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
|
|
1095
|
-
return {
|
|
1096
|
-
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1097
|
-
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1098
|
-
dangerous: false,
|
|
1099
|
-
duplex: true,
|
|
1100
|
-
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1101
|
-
env,
|
|
1102
|
-
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1103
|
-
strategy: "direct"
|
|
1104
|
-
})),
|
|
1105
|
-
handle: async (paramsJSON, io) => {
|
|
1106
|
-
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1107
|
-
const request = bindRequest(paramsJSON);
|
|
1108
|
-
const resume = decodeNodePtyResumeParams(request.paramsJSON, (value) => {
|
|
1109
|
-
if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value)) throw new CatalogParamsError("threadId must be a UUID");
|
|
1110
|
-
return value;
|
|
1111
|
-
});
|
|
1112
|
-
const record = await requireCatalogEligibleThread(request.control, resume.threadId);
|
|
1113
|
-
const resolution = resolveNodeHostExecutable("codex", {
|
|
1114
|
-
env: process.env,
|
|
1115
|
-
pathEnv: process.env.PATH ?? process.env.Path ?? "",
|
|
1116
|
-
strategy: "direct"
|
|
1117
|
-
});
|
|
1118
|
-
if (!resolution) throw new Error("Codex CLI is unavailable");
|
|
1119
|
-
return JSON.stringify(await runNodePtyCommand({
|
|
1120
|
-
file: resolution.executable,
|
|
1121
|
-
args: ["resume", resume.threadId],
|
|
1122
|
-
cwd: record.cwd,
|
|
1123
|
-
env: { CODEX_HOME: resolveCodexCatalogTerminalHome({
|
|
1124
|
-
...configSources,
|
|
1125
|
-
agentId: request.agentId
|
|
1126
|
-
}) },
|
|
1127
|
-
cols: resume.cols,
|
|
1128
|
-
rows: resume.rows
|
|
1129
|
-
}, io));
|
|
1130
|
-
}
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
async function resolveNodeCatalogEligibleThread(params) {
|
|
1134
|
-
let cursor;
|
|
1135
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
1136
|
-
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1137
|
-
const raw = await params.runtime.nodes.invoke({
|
|
1138
|
-
nodeId: params.nodeId,
|
|
1139
|
-
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1140
|
-
params: {
|
|
1141
|
-
agentId: params.agentId,
|
|
1142
|
-
limit: 100,
|
|
1143
|
-
...cursor ? { cursor } : {}
|
|
1144
|
-
},
|
|
1145
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1146
|
-
scopes: ["operator.write"]
|
|
1147
|
-
});
|
|
1148
|
-
const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
|
|
1149
|
-
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1150
|
-
if (record) {
|
|
1151
|
-
if (isInteractiveThreadSource(record.source)) return record;
|
|
1152
|
-
break;
|
|
1153
|
-
}
|
|
1154
|
-
const nextCursor = page.nextCursor?.trim();
|
|
1155
|
-
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
1156
|
-
seenCursors.add(nextCursor);
|
|
1157
|
-
cursor = nextCursor;
|
|
1158
|
-
}
|
|
1159
|
-
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1160
|
-
}
|
|
1161
|
-
async function openCodexCatalogTerminal(params) {
|
|
1162
|
-
const title = `codex resume ${params.threadId.slice(0, 8)}…`;
|
|
1163
|
-
if (params.hostId === "gateway:local" || params.hostId.startsWith(`gateway:local:`)) {
|
|
1164
|
-
const record = await requireCatalogEligibleThread(params.control, params.threadId);
|
|
1165
|
-
const resolution = resolveLocalCodexTerminalResolution();
|
|
1166
|
-
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable");
|
|
1167
|
-
return {
|
|
1168
|
-
kind: "local",
|
|
1169
|
-
argv: [
|
|
1170
|
-
resolution.executable,
|
|
1171
|
-
"resume",
|
|
1172
|
-
params.threadId
|
|
1173
|
-
],
|
|
1174
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1175
|
-
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1176
|
-
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1177
|
-
title
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
if (!params.hostId.startsWith("node:")) throw new CatalogParamsError("hostId is invalid");
|
|
1181
|
-
const nodeId = params.hostId.slice(5);
|
|
1182
|
-
if (!(await params.api.runtime.nodes.list()).nodes.find((candidate) => {
|
|
1183
|
-
const commands = candidate.invocableCommands ?? candidate.commands;
|
|
1184
|
-
return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("codex.appServer.threads.list.v1") === true && commands.includes("codex.terminal.resume.v1");
|
|
1185
|
-
})) throw new CatalogParamsError("paired-node Codex terminal is unavailable");
|
|
1186
|
-
const record = await resolveNodeCatalogEligibleThread({
|
|
1187
|
-
agentId: params.agentId,
|
|
1188
|
-
runtime: params.api.runtime,
|
|
1189
|
-
nodeId,
|
|
1190
|
-
threadId: params.threadId,
|
|
1191
|
-
parseCatalogPage: params.parseCatalogPage
|
|
1192
|
-
});
|
|
1193
|
-
return {
|
|
1194
|
-
kind: "node",
|
|
1195
|
-
nodeId,
|
|
1196
|
-
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1197
|
-
paramsJSON: JSON.stringify({
|
|
1198
|
-
agentId: params.agentId,
|
|
1199
|
-
threadId: params.threadId
|
|
1200
|
-
}),
|
|
1201
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1202
|
-
title
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
async function startCodexCatalogTerminal(params) {
|
|
1206
|
-
if (params.nodeId) throw new CatalogParamsError("Paired-node Codex terminal start is unavailable; omit hostId to start on the gateway host");
|
|
1207
|
-
const resolution = resolveLocalCodexTerminalResolution();
|
|
1208
|
-
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable; install Codex or add codex to PATH, then try again");
|
|
1209
|
-
return {
|
|
1210
|
-
kind: "local",
|
|
1211
|
-
argv: [resolution.executable, ...params.initialMessage !== void 0 ? ["--", params.initialMessage] : []],
|
|
1212
|
-
cwd: params.cwd,
|
|
1213
|
-
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1214
|
-
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1215
|
-
title: "codex"
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
//#endregion
|
|
1219
1237
|
//#region extensions/codex/src/session-catalog-adoption.ts
|
|
1220
1238
|
const CODEX_SUPERVISION_SESSION_KEY_PREFIX = "harness:codex:supervision:";
|
|
1221
1239
|
const boundCatalogSessionId = (value) => boundedCatalogString(value, 256);
|
|
@@ -1278,54 +1296,26 @@ async function findAdoptedSessionEntry(params) {
|
|
|
1278
1296
|
const adopted = await listAdoptedSessionEntries(params);
|
|
1279
1297
|
return adopted.get(sessionCatalogAdoptedSourceKey(params.sourceHomeId ?? "gateway:local", params.threadId)) ?? (params.sourceHomeId && params.allowLegacy === true ? adopted.get(sessionCatalogAdoptedSourceKey("gateway:local", params.threadId)) : void 0);
|
|
1280
1298
|
}
|
|
1281
|
-
async function clearCreatedAdoptionBinding(params) {
|
|
1282
|
-
let cleared = false;
|
|
1283
|
-
let clearError;
|
|
1284
|
-
try {
|
|
1285
|
-
cleared = await params.bindingStore.mutate(params.identity, {
|
|
1286
|
-
kind: "clear",
|
|
1287
|
-
threadId: params.sourceThreadId,
|
|
1288
|
-
expectedPendingSupervisionBranch: params.expectedPending
|
|
1289
|
-
});
|
|
1290
|
-
} catch (error) {
|
|
1291
|
-
clearError = error;
|
|
1292
|
-
}
|
|
1293
|
-
if (cleared) return;
|
|
1294
|
-
let current;
|
|
1295
|
-
try {
|
|
1296
|
-
current = await params.bindingStore.read(params.identity);
|
|
1297
|
-
} catch (readError) {
|
|
1298
|
-
throw new AggregateError([
|
|
1299
|
-
params.cause,
|
|
1300
|
-
...clearError ? [clearError] : [],
|
|
1301
|
-
readError
|
|
1302
|
-
], `OpenClaw session creation failed and the Codex binding could not be verified for ${params.sourceThreadId}`, { cause: readError });
|
|
1303
|
-
}
|
|
1304
|
-
if (!matchesPendingSupervisionOwner(current, params.expectedPending)) return;
|
|
1305
|
-
throw new AggregateError([params.cause, ...clearError ? [clearError] : []], `OpenClaw session creation failed and the Codex binding could not be cleared for ${params.sourceThreadId}`, { cause: params.cause });
|
|
1306
|
-
}
|
|
1307
1299
|
function matchesPendingAdoptionBinding(binding, expected) {
|
|
1308
1300
|
const historyCoveredThrough = binding?.historyCoveredThrough;
|
|
1309
1301
|
return binding?.threadId === expected.sourceThreadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && binding.cwd === expected.cwd && binding.conversationSourceTransferComplete === true && binding.preserveNativeModel === true && binding.pendingSupervisionBranch?.sourceThreadId === expected.sourceThreadId && binding.pendingSupervisionBranch.connectionFingerprint === expected.connectionFingerprint && binding.pendingSupervisionBranch.lastTurnId === expected.lastTurnId && (binding.pendingSupervisionBranch.cleanupThreadIds?.length ?? 0) === 0 && typeof historyCoveredThrough === "string" && Number.isFinite(Date.parse(historyCoveredThrough));
|
|
1310
1302
|
}
|
|
1311
|
-
function matchesPendingSupervisionOwner(binding, expected) {
|
|
1312
|
-
const pending = binding?.pendingSupervisionBranch;
|
|
1313
|
-
const cleanupThreadIds = pending?.cleanupThreadIds ?? [];
|
|
1314
|
-
const expectedCleanupThreadIds = expected.cleanupThreadIds ?? [];
|
|
1315
|
-
return binding?.threadId === expected.sourceThreadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && pending?.sourceThreadId === expected.sourceThreadId && pending.connectionFingerprint === expected.connectionFingerprint && pending.lastTurnId === expected.lastTurnId && cleanupThreadIds.length === expectedCleanupThreadIds.length && cleanupThreadIds.every((threadId, index) => threadId === expectedCleanupThreadIds[index]);
|
|
1316
|
-
}
|
|
1317
1303
|
async function ensurePendingAdoptionBinding(params) {
|
|
1318
1304
|
const pending = {
|
|
1319
1305
|
sourceThreadId: params.sourceThreadId,
|
|
1320
1306
|
connectionFingerprint: params.connectionFingerprint,
|
|
1321
1307
|
...params.lastTurnId ? { lastTurnId: params.lastTurnId } : {}
|
|
1322
1308
|
};
|
|
1323
|
-
|
|
1309
|
+
const ownsGeneration = await reclaimCurrentCodexSessionGeneration({
|
|
1310
|
+
assertCurrent: params.initialization.assertCurrent,
|
|
1324
1311
|
bindingStore: params.bindingStore,
|
|
1325
1312
|
identity: params.identity,
|
|
1326
1313
|
config: params.config
|
|
1327
|
-
})
|
|
1314
|
+
});
|
|
1315
|
+
params.initialization.assertCurrent();
|
|
1316
|
+
if (!ownsGeneration) throw new Error(`failed to claim the OpenClaw session generation for ${params.sourceThreadId}`);
|
|
1328
1317
|
const existing = await params.bindingStore.read(params.identity);
|
|
1318
|
+
params.initialization.assertCurrent();
|
|
1329
1319
|
if (existing) {
|
|
1330
1320
|
if (matchesPendingAdoptionBinding(existing, params)) return;
|
|
1331
1321
|
throw new Error(`OpenClaw session is already bound to Codex thread ${existing.threadId}`);
|
|
@@ -1340,19 +1330,7 @@ async function ensurePendingAdoptionBinding(params) {
|
|
|
1340
1330
|
preserveNativeModel: true,
|
|
1341
1331
|
pendingSupervisionBranch: pending
|
|
1342
1332
|
};
|
|
1343
|
-
|
|
1344
|
-
try {
|
|
1345
|
-
stored = await params.bindingStore.mutate(params.identity, {
|
|
1346
|
-
kind: "set",
|
|
1347
|
-
if: { kind: "absent" },
|
|
1348
|
-
binding
|
|
1349
|
-
});
|
|
1350
|
-
} catch (error) {
|
|
1351
|
-
if (matchesPendingAdoptionBinding(await params.bindingStore.read(params.identity), params)) return;
|
|
1352
|
-
throw error;
|
|
1353
|
-
}
|
|
1354
|
-
if (stored) return;
|
|
1355
|
-
if (!matchesPendingAdoptionBinding(await params.bindingStore.read(params.identity), params)) throw new Error(`failed to bind OpenClaw session to Codex thread ${params.sourceThreadId}`);
|
|
1333
|
+
await params.initialization.bind(binding);
|
|
1356
1334
|
}
|
|
1357
1335
|
async function createOrReuseAdoptedSession(params) {
|
|
1358
1336
|
const runtime = params.api.runtime;
|
|
@@ -1363,8 +1341,6 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1363
1341
|
};
|
|
1364
1342
|
const existing = await findAdoptedSessionEntry(lookup);
|
|
1365
1343
|
if (existing) return existing;
|
|
1366
|
-
let createdBindingIdentity;
|
|
1367
|
-
let createdPendingBinding;
|
|
1368
1344
|
try {
|
|
1369
1345
|
const spawnedCwd = params.sourceThread.cwd?.trim() || void 0;
|
|
1370
1346
|
const pendingLastTurnId = codexLastTerminalTurnId(params.sourceThread, boundCatalogSessionId);
|
|
@@ -1374,9 +1350,11 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1374
1350
|
};
|
|
1375
1351
|
const created = await createImportedCodexSession({
|
|
1376
1352
|
runtime: params.api.runtime,
|
|
1353
|
+
bindingStore: params.bindingStore,
|
|
1377
1354
|
config: params.config,
|
|
1378
1355
|
key: adoptionSessionKey(params.sourceThread.id, params.sourceHomeId),
|
|
1379
1356
|
agentId: params.agentId,
|
|
1357
|
+
displayName: params.sourceThread.name ?? void 0,
|
|
1380
1358
|
thread: params.sourceThread,
|
|
1381
1359
|
throughTurnId: pendingLastTurnId ?? null,
|
|
1382
1360
|
recoverMatchingInitialEntry: true,
|
|
@@ -1389,21 +1367,17 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1389
1367
|
modelLocked: true
|
|
1390
1368
|
} } }
|
|
1391
1369
|
},
|
|
1392
|
-
afterImport: async (entry) => {
|
|
1393
|
-
|
|
1370
|
+
afterImport: async (entry, initialization) => {
|
|
1371
|
+
const identity = sessionBindingIdentity({
|
|
1394
1372
|
sessionId: entry.sessionId,
|
|
1395
1373
|
sessionKey: entry.key,
|
|
1396
1374
|
config: params.config
|
|
1397
1375
|
});
|
|
1398
|
-
createdPendingBinding = {
|
|
1399
|
-
sourceThreadId: params.sourceThread.id,
|
|
1400
|
-
connectionFingerprint: params.connectionFingerprint,
|
|
1401
|
-
...pendingLastTurnId ? { lastTurnId: pendingLastTurnId } : {}
|
|
1402
|
-
};
|
|
1403
1376
|
await ensurePendingAdoptionBinding({
|
|
1404
1377
|
bindingStore: params.bindingStore,
|
|
1405
1378
|
config: params.config,
|
|
1406
|
-
identity
|
|
1379
|
+
identity,
|
|
1380
|
+
initialization,
|
|
1407
1381
|
sourceThreadId: params.sourceThread.id,
|
|
1408
1382
|
connectionFingerprint: params.connectionFingerprint,
|
|
1409
1383
|
cwd: spawnedCwd ?? "",
|
|
@@ -1422,24 +1396,13 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1422
1396
|
boundThreadId: params.sourceThread.id
|
|
1423
1397
|
};
|
|
1424
1398
|
} catch (error) {
|
|
1425
|
-
|
|
1399
|
+
const raced = await findAdoptedSessionEntry(lookup);
|
|
1426
1400
|
if (raced) return raced;
|
|
1427
|
-
if (createdBindingIdentity && createdPendingBinding) {
|
|
1428
|
-
await clearCreatedAdoptionBinding({
|
|
1429
|
-
bindingStore: params.bindingStore,
|
|
1430
|
-
identity: createdBindingIdentity,
|
|
1431
|
-
sourceThreadId: params.sourceThread.id,
|
|
1432
|
-
expectedPending: createdPendingBinding,
|
|
1433
|
-
cause: error
|
|
1434
|
-
});
|
|
1435
|
-
raced = await findAdoptedSessionEntry(lookup);
|
|
1436
|
-
if (raced) return raced;
|
|
1437
|
-
}
|
|
1438
1401
|
throw error;
|
|
1439
1402
|
}
|
|
1440
1403
|
}
|
|
1441
1404
|
async function continueLocalCodexSessionInner(params) {
|
|
1442
|
-
await
|
|
1405
|
+
await params.control.requireEligibleThread(params.threadId);
|
|
1443
1406
|
const existing = await findAdoptedSessionEntry({
|
|
1444
1407
|
...params,
|
|
1445
1408
|
runtime: params.api.runtime
|
|
@@ -1532,8 +1495,8 @@ async function archiveLocalCodexSession(params) {
|
|
|
1532
1495
|
return await runSessionActionExclusive(sessionCatalogAdoptedSourceKey(params.hostId ?? "gateway:local", params.threadId), async () => {
|
|
1533
1496
|
return await params.bindingStore.withThreadArchiveFence(async () => {
|
|
1534
1497
|
const run = async (control) => {
|
|
1535
|
-
await requireCatalogEligibleThread(control, params.threadId);
|
|
1536
1498
|
await assertNoPendingSupervisionBranch(params);
|
|
1499
|
+
await control.requireEligibleThread(params.threadId);
|
|
1537
1500
|
const thread = await control.readThread(params.threadId, false);
|
|
1538
1501
|
if (thread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1539
1502
|
requireIdleThread(thread, "archive");
|
|
@@ -1607,7 +1570,8 @@ async function listPairedNode(params) {
|
|
|
1607
1570
|
label: nodeLabel(params.node),
|
|
1608
1571
|
kind: "node",
|
|
1609
1572
|
nodeId: params.node.nodeId,
|
|
1610
|
-
canContinueCodex: canContinueCodexOnNode(params.node)
|
|
1573
|
+
canContinueCodex: canContinueCodexOnNode(params.node),
|
|
1574
|
+
...params.terminalCapabilities
|
|
1611
1575
|
};
|
|
1612
1576
|
if (params.node.connected !== true) {
|
|
1613
1577
|
const host = {
|
|
@@ -1622,6 +1586,15 @@ async function listPairedNode(params) {
|
|
|
1622
1586
|
params.onHost?.(host);
|
|
1623
1587
|
return host;
|
|
1624
1588
|
}
|
|
1589
|
+
if (!params.node.commands?.includes("codex.appServer.threads.list.v1")) {
|
|
1590
|
+
const host = {
|
|
1591
|
+
...common,
|
|
1592
|
+
connected: true,
|
|
1593
|
+
sessions: []
|
|
1594
|
+
};
|
|
1595
|
+
params.onHost?.(host);
|
|
1596
|
+
return host;
|
|
1597
|
+
}
|
|
1625
1598
|
const eventualHost = Promise.resolve().then(async () => {
|
|
1626
1599
|
const page = filterCatalogPageByTitle(parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1627
1600
|
nodeId: params.node.nodeId,
|
|
@@ -1800,7 +1773,7 @@ async function continueNodeCodexSession(params) {
|
|
|
1800
1773
|
if (params.clientScopes?.includes("operator.admin") !== true) throw new CatalogParamsError("continuing a paired-node Codex session requires operator.admin");
|
|
1801
1774
|
const nodeId = params.hostId.slice(5).trim();
|
|
1802
1775
|
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1803
|
-
const agentId =
|
|
1776
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
1804
1777
|
config: params.config,
|
|
1805
1778
|
agentId: params.agentId
|
|
1806
1779
|
}).sessionAgentId;
|
|
@@ -1817,6 +1790,194 @@ async function continueNodeCodexSession(params) {
|
|
|
1817
1790
|
});
|
|
1818
1791
|
}
|
|
1819
1792
|
//#endregion
|
|
1793
|
+
//#region extensions/codex/src/session-catalog-terminal.ts
|
|
1794
|
+
const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1";
|
|
1795
|
+
const CODEX_TERMINAL_START_COMMAND = "codex.terminal.start.v1";
|
|
1796
|
+
function createCodexTerminalStartNodeHostCommand() {
|
|
1797
|
+
return {
|
|
1798
|
+
command: CODEX_TERMINAL_START_COMMAND,
|
|
1799
|
+
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1800
|
+
dangerous: false,
|
|
1801
|
+
duplex: true,
|
|
1802
|
+
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1803
|
+
env,
|
|
1804
|
+
strategy: "direct"
|
|
1805
|
+
})),
|
|
1806
|
+
handle: async (paramsJSON, io) => {
|
|
1807
|
+
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1808
|
+
const params = decodeNodePtyStartParams(paramsJSON);
|
|
1809
|
+
const resolution = resolveNodeHostExecutable("codex", { strategy: "direct" });
|
|
1810
|
+
if (!resolution) throw new Error("Codex CLI is unavailable; install codex on this node and reconnect");
|
|
1811
|
+
return JSON.stringify(await runNodePtyCommand({
|
|
1812
|
+
file: resolution.executable,
|
|
1813
|
+
args: params.initialMessage !== void 0 ? ["--", params.initialMessage] : [],
|
|
1814
|
+
cwd: params.cwd,
|
|
1815
|
+
requiredCwd: true,
|
|
1816
|
+
cols: params.cols,
|
|
1817
|
+
rows: params.rows
|
|
1818
|
+
}, io));
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
function resolveCodexCatalogTerminalHome(sources) {
|
|
1823
|
+
const runtimeConfig = sources.getRuntimeConfig();
|
|
1824
|
+
if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
|
|
1825
|
+
const agentDir = sources.source?.agentDir ?? (sources.agentId ? resolveAgentDir$1(runtimeConfig, sources.agentId) : resolveDefaultAgentDir(runtimeConfig));
|
|
1826
|
+
const startOptions = sources.source?.appServer.start ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig: sources.getPluginConfig() }).start;
|
|
1827
|
+
return resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
|
|
1828
|
+
}
|
|
1829
|
+
function resolveLocalCodexTerminalExecutable(env = process.env) {
|
|
1830
|
+
return resolveLocalCodexTerminalResolution(env)?.executable;
|
|
1831
|
+
}
|
|
1832
|
+
function resolveLocalCodexTerminalResolution(env = process.env) {
|
|
1833
|
+
return resolveNodeHostExecutable("codex", {
|
|
1834
|
+
env,
|
|
1835
|
+
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1836
|
+
strategy: "fallback"
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
function codexNodeTerminalCapability(node) {
|
|
1840
|
+
const commands = node.invocableCommands ?? node.commands;
|
|
1841
|
+
return {
|
|
1842
|
+
canOpenTerminalCodex: node.connected === true && commands?.includes("codex.terminal.resume.v1") === true,
|
|
1843
|
+
canStartTerminal: node.connected === true && node.invocableCommands?.includes("codex.terminal.start.v1") === true
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
|
|
1847
|
+
return {
|
|
1848
|
+
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1849
|
+
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1850
|
+
dangerous: false,
|
|
1851
|
+
duplex: true,
|
|
1852
|
+
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1853
|
+
env,
|
|
1854
|
+
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1855
|
+
strategy: "direct"
|
|
1856
|
+
})),
|
|
1857
|
+
handle: async (paramsJSON, io) => {
|
|
1858
|
+
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1859
|
+
const request = bindRequest(paramsJSON);
|
|
1860
|
+
const resume = decodeNodePtyResumeParams(request.paramsJSON, (value) => {
|
|
1861
|
+
if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value)) throw new CatalogParamsError("threadId must be a UUID");
|
|
1862
|
+
return value;
|
|
1863
|
+
});
|
|
1864
|
+
const record = await request.control.requireEligibleThread(resume.threadId);
|
|
1865
|
+
const resolution = resolveNodeHostExecutable("codex", {
|
|
1866
|
+
env: process.env,
|
|
1867
|
+
pathEnv: process.env.PATH ?? process.env.Path ?? "",
|
|
1868
|
+
strategy: "direct"
|
|
1869
|
+
});
|
|
1870
|
+
if (!resolution) throw new Error("Codex CLI is unavailable");
|
|
1871
|
+
return JSON.stringify(await runNodePtyCommand({
|
|
1872
|
+
file: resolution.executable,
|
|
1873
|
+
args: ["resume", resume.threadId],
|
|
1874
|
+
...record.cwd ? { cwd: record.cwd } : {},
|
|
1875
|
+
env: { CODEX_HOME: resolveCodexCatalogTerminalHome({
|
|
1876
|
+
...configSources,
|
|
1877
|
+
agentId: request.agentId
|
|
1878
|
+
}) },
|
|
1879
|
+
cols: resume.cols,
|
|
1880
|
+
rows: resume.rows
|
|
1881
|
+
}, io));
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
async function resolveNodeCatalogEligibleThread(params) {
|
|
1886
|
+
let cursor;
|
|
1887
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
1888
|
+
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1889
|
+
const raw = await params.runtime.nodes.invoke({
|
|
1890
|
+
nodeId: params.nodeId,
|
|
1891
|
+
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1892
|
+
params: {
|
|
1893
|
+
agentId: params.agentId,
|
|
1894
|
+
limit: 100,
|
|
1895
|
+
...cursor ? { cursor } : {}
|
|
1896
|
+
},
|
|
1897
|
+
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1898
|
+
scopes: ["operator.write"]
|
|
1899
|
+
});
|
|
1900
|
+
const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
|
|
1901
|
+
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1902
|
+
if (record) {
|
|
1903
|
+
if (isInteractiveThreadSource(record.source)) return record;
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
const nextCursor = page.nextCursor?.trim();
|
|
1907
|
+
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
1908
|
+
seenCursors.add(nextCursor);
|
|
1909
|
+
cursor = nextCursor;
|
|
1910
|
+
}
|
|
1911
|
+
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1912
|
+
}
|
|
1913
|
+
async function openCodexCatalogTerminal(params) {
|
|
1914
|
+
const title = `codex resume ${params.threadId.slice(0, 8)}…`;
|
|
1915
|
+
if (params.hostId === "gateway:local" || params.hostId.startsWith(`gateway:local:`)) {
|
|
1916
|
+
const record = await params.control.requireEligibleThread(params.threadId);
|
|
1917
|
+
const resolution = resolveLocalCodexTerminalResolution();
|
|
1918
|
+
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable");
|
|
1919
|
+
return {
|
|
1920
|
+
kind: "local",
|
|
1921
|
+
argv: [
|
|
1922
|
+
resolution.executable,
|
|
1923
|
+
"resume",
|
|
1924
|
+
params.threadId
|
|
1925
|
+
],
|
|
1926
|
+
...record.cwd ? { cwd: record.cwd } : {},
|
|
1927
|
+
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1928
|
+
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1929
|
+
title
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
if (!params.hostId.startsWith("node:")) throw new CatalogParamsError("hostId is invalid");
|
|
1933
|
+
const nodeId = params.hostId.slice(5);
|
|
1934
|
+
if (!(await params.api.runtime.nodes.list()).nodes.find((candidate) => {
|
|
1935
|
+
const commands = candidate.invocableCommands ?? candidate.commands;
|
|
1936
|
+
return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("codex.appServer.threads.list.v1") === true && commands.includes("codex.terminal.resume.v1");
|
|
1937
|
+
})) throw new CatalogParamsError("paired-node Codex terminal is unavailable");
|
|
1938
|
+
const record = await resolveNodeCatalogEligibleThread({
|
|
1939
|
+
agentId: params.agentId,
|
|
1940
|
+
runtime: params.api.runtime,
|
|
1941
|
+
nodeId,
|
|
1942
|
+
threadId: params.threadId,
|
|
1943
|
+
parseCatalogPage: params.parseCatalogPage
|
|
1944
|
+
});
|
|
1945
|
+
return {
|
|
1946
|
+
kind: "node",
|
|
1947
|
+
nodeId,
|
|
1948
|
+
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1949
|
+
paramsJSON: JSON.stringify({
|
|
1950
|
+
agentId: params.agentId,
|
|
1951
|
+
threadId: params.threadId
|
|
1952
|
+
}),
|
|
1953
|
+
...record.cwd ? { cwd: record.cwd } : {},
|
|
1954
|
+
title
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
async function startCodexCatalogTerminal(params) {
|
|
1958
|
+
if (params.nodeId) return {
|
|
1959
|
+
kind: "node",
|
|
1960
|
+
nodeId: params.nodeId,
|
|
1961
|
+
command: CODEX_TERMINAL_START_COMMAND,
|
|
1962
|
+
paramsJSON: JSON.stringify({
|
|
1963
|
+
cwd: params.cwd,
|
|
1964
|
+
initialMessage: params.initialMessage
|
|
1965
|
+
}),
|
|
1966
|
+
cwd: params.cwd,
|
|
1967
|
+
title: "codex"
|
|
1968
|
+
};
|
|
1969
|
+
const resolution = resolveLocalCodexTerminalResolution();
|
|
1970
|
+
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable; install Codex or add codex to PATH, then try again");
|
|
1971
|
+
return {
|
|
1972
|
+
kind: "local",
|
|
1973
|
+
argv: [resolution.executable, ...params.initialMessage !== void 0 ? ["--", params.initialMessage] : []],
|
|
1974
|
+
cwd: params.cwd,
|
|
1975
|
+
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1976
|
+
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1977
|
+
title: "codex"
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
//#endregion
|
|
1820
1981
|
//#region extensions/codex/src/session-catalog-listing.ts
|
|
1821
1982
|
async function listVisiblePage(params) {
|
|
1822
1983
|
const excluded = params.excludedThreadIds;
|
|
@@ -1904,7 +2065,7 @@ async function listGatewayHost(params) {
|
|
|
1904
2065
|
}
|
|
1905
2066
|
/** Lists Gateway-local and paired-node Codex sessions with per-host failures. */
|
|
1906
2067
|
async function listCodexSessionCatalog(params) {
|
|
1907
|
-
const agentId =
|
|
2068
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
1908
2069
|
config: params.config ?? {},
|
|
1909
2070
|
agentId: params.agentId
|
|
1910
2071
|
}).sessionAgentId;
|
|
@@ -1939,13 +2100,14 @@ async function listCodexSessionCatalog(params) {
|
|
|
1939
2100
|
if (!(!requestedHostIds || query.hostIds?.some((hostId) => hostId.startsWith("node:")))) return { hosts: await Promise.all(localHosts) };
|
|
1940
2101
|
let nodes;
|
|
1941
2102
|
try {
|
|
1942
|
-
nodes = (await (params.listNodes?.() ?? params.runtime.nodes.list())).nodes.filter((node) => node.gatewayLocal !== true && node.commands?.includes("codex.appServer.threads.list.v1") && (!requestedHostIds || requestedHostIds.has(`node:${node.nodeId}`))).slice(0, 100 - localHosts.length);
|
|
2103
|
+
nodes = (await (params.listNodes?.() ?? params.runtime.nodes.list())).nodes.filter((node) => node.gatewayLocal !== true && (node.commands?.includes("codex.appServer.threads.list.v1") || codexNodeTerminalCapability(node).canStartTerminal) && (!requestedHostIds || requestedHostIds.has(`node:${node.nodeId}`))).slice(0, 100 - localHosts.length);
|
|
1943
2104
|
} catch (error) {
|
|
1944
2105
|
const registryHost = {
|
|
1945
2106
|
hostId: "node:registry",
|
|
1946
2107
|
label: "Paired nodes",
|
|
1947
2108
|
kind: "node",
|
|
1948
2109
|
connected: false,
|
|
2110
|
+
canStartTerminal: false,
|
|
1949
2111
|
sessions: [],
|
|
1950
2112
|
error: catalogError("NODE_LIST_FAILED", error)
|
|
1951
2113
|
};
|
|
@@ -1958,17 +2120,15 @@ async function listCodexSessionCatalog(params) {
|
|
|
1958
2120
|
runtime: params.runtime,
|
|
1959
2121
|
sessionEntries: params.sessionEntries
|
|
1960
2122
|
});
|
|
1961
|
-
const nodeHosts = nodes.toSorted(compareNodeLabels).map(
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
return Object.assign(host, codexNodeTerminalCapability(node));
|
|
1971
|
-
});
|
|
2123
|
+
const nodeHosts = nodes.toSorted(compareNodeLabels).map((node) => listPairedNode({
|
|
2124
|
+
agentId,
|
|
2125
|
+
runtime: params.runtime,
|
|
2126
|
+
node,
|
|
2127
|
+
query,
|
|
2128
|
+
adoptedSessions: adoptedNodeSessions,
|
|
2129
|
+
terminalCapabilities: codexNodeTerminalCapability(node),
|
|
2130
|
+
...params.onHost ? { onHost: params.onHost } : {}
|
|
2131
|
+
}));
|
|
1972
2132
|
return { hosts: await Promise.all([...localHosts, ...nodeHosts]) };
|
|
1973
2133
|
}
|
|
1974
2134
|
/** Builds the node-local read-only Codex app-server catalog command. */
|
|
@@ -1978,7 +2138,7 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
|
|
|
1978
2138
|
if (!isRecord(parsed)) throw new CatalogParamsError("Codex session catalog parameters must be an object");
|
|
1979
2139
|
const requestedAgentId = readBoundedOptionalString(parsed, "agentId", 256);
|
|
1980
2140
|
const config = configSources.getRuntimeConfig() ?? {};
|
|
1981
|
-
const agentId =
|
|
2141
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
1982
2142
|
config,
|
|
1983
2143
|
agentId: requestedAgentId
|
|
1984
2144
|
}).sessionAgentId;
|
|
@@ -2035,7 +2195,7 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
|
|
|
2035
2195
|
const request = bindRequest(paramsJSON);
|
|
2036
2196
|
const action = readNodeTranscriptParams(request.params);
|
|
2037
2197
|
try {
|
|
2038
|
-
await
|
|
2198
|
+
await request.control.requireEligibleThread(action.threadId);
|
|
2039
2199
|
const page = parseTranscriptPage(await request.control.listTurnPage({
|
|
2040
2200
|
threadId: action.threadId,
|
|
2041
2201
|
limit: action.limit,
|
|
@@ -2050,7 +2210,8 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
|
|
|
2050
2210
|
}
|
|
2051
2211
|
}
|
|
2052
2212
|
},
|
|
2053
|
-
createCodexTerminalNodeHostCommand(bindRequest, configSources)
|
|
2213
|
+
createCodexTerminalNodeHostCommand(bindRequest, configSources),
|
|
2214
|
+
createCodexTerminalStartNodeHostCommand()
|
|
2054
2215
|
];
|
|
2055
2216
|
}
|
|
2056
2217
|
function readNodeTranscriptParams(value) {
|
|
@@ -2080,7 +2241,7 @@ function flattenTranscriptPageDesc(page) {
|
|
|
2080
2241
|
/** Reads the persisted transcript for a Gateway-local or paired-node Codex session. */
|
|
2081
2242
|
async function readCodexSessionTranscript(params) {
|
|
2082
2243
|
if (params.source || params.hostId === "gateway:local") {
|
|
2083
|
-
await
|
|
2244
|
+
await params.control.requireEligibleThread(params.threadId);
|
|
2084
2245
|
const listParams = {
|
|
2085
2246
|
threadId: params.threadId,
|
|
2086
2247
|
limit: params.limit,
|
|
@@ -2146,7 +2307,7 @@ function toGenericTranscriptItem(item) {
|
|
|
2146
2307
|
const fallback = item.title ?? item.name ?? item.tool ?? item.command ?? item.query ?? void 0;
|
|
2147
2308
|
const resultText = item.aggregatedOutput || (item.result === void 0 ? void 0 : JSON.stringify(item.result, null, 2));
|
|
2148
2309
|
const changesText = Array.isArray(item.changes) ? item.changes.map((change) => `${change.kind}: ${change.path}`).join("\n") || void 0 : void 0;
|
|
2149
|
-
const text = item.text || resultText || changesText || fallback;
|
|
2310
|
+
const text = item.type === "userMessage" ? projectCodexUserItemText(item) : item.text || resultText || changesText || fallback;
|
|
2150
2311
|
return {
|
|
2151
2312
|
id: item.id,
|
|
2152
2313
|
type,
|
|
@@ -2311,7 +2472,7 @@ function existingCatalogHomeCandidates(value, label) {
|
|
|
2311
2472
|
/** Resolves every local Codex store the operator already owns, without path disclosure. */
|
|
2312
2473
|
function resolveCodexCatalogHomes(params) {
|
|
2313
2474
|
const { config, env, ownerAgentId, pluginConfig } = params;
|
|
2314
|
-
const ownerAgentDir = resolveAgentDir(config, ownerAgentId, env);
|
|
2475
|
+
const ownerAgentDir = resolveAgentDir$1(config, ownerAgentId, env);
|
|
2315
2476
|
const configuredHomes = readCodexPluginConfig(pluginConfig).sessionCatalog?.homes ?? [];
|
|
2316
2477
|
const base = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
2317
2478
|
pluginConfig,
|
|
@@ -2334,7 +2495,7 @@ function resolveCodexCatalogHomes(params) {
|
|
|
2334
2495
|
usesProcessHomeFallback: !processHomeConfigured
|
|
2335
2496
|
});
|
|
2336
2497
|
const agentIds = listAgentIds(config).toSorted((left, right) => left === ownerAgentId ? -1 : right === ownerAgentId ? 1 : left.localeCompare(right));
|
|
2337
|
-
candidates.push(...agentIds.flatMap((agentId) => existingCatalogHomeCandidates(resolveCodexAppServerHomeDir(resolveAgentDir(config, agentId, env)), agentId)), ...configuredHomes.flatMap((entry) => {
|
|
2498
|
+
candidates.push(...agentIds.flatMap((agentId) => existingCatalogHomeCandidates(resolveCodexAppServerHomeDir(resolveAgentDir$1(config, agentId, env)), agentId)), ...configuredHomes.flatMap((entry) => {
|
|
2338
2499
|
const { path: home, label } = typeof entry === "string" ? { path: entry } : entry;
|
|
2339
2500
|
return existingCatalogHomeCandidates(home, label);
|
|
2340
2501
|
}));
|
|
@@ -2400,96 +2561,6 @@ function createCodexCatalogHomeResolver(params) {
|
|
|
2400
2561
|
} };
|
|
2401
2562
|
}
|
|
2402
2563
|
//#endregion
|
|
2403
|
-
//#region extensions/codex/src/session-catalog-provenance.ts
|
|
2404
|
-
const MAX_SESSION_META_BYTES = 1024 * 1024;
|
|
2405
|
-
const SESSION_META_READ_CHUNK_BYTES = 64 * 1024;
|
|
2406
|
-
const MAX_PROVENANCE_CACHE_ENTRIES = 2e4;
|
|
2407
|
-
const provenanceByPath = /* @__PURE__ */ new Map();
|
|
2408
|
-
function cacheProvenance(key, value) {
|
|
2409
|
-
provenanceByPath.delete(key);
|
|
2410
|
-
provenanceByPath.set(key, value);
|
|
2411
|
-
while (provenanceByPath.size > MAX_PROVENANCE_CACHE_ENTRIES) {
|
|
2412
|
-
const oldest = provenanceByPath.keys().next().value;
|
|
2413
|
-
if (oldest === void 0) break;
|
|
2414
|
-
provenanceByPath.delete(oldest);
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
/** Undefined means the metadata line is not durable enough to cache yet. */
|
|
2418
|
-
async function readOpenClawOriginator(sessionsRoot, rolloutPath, threadId) {
|
|
2419
|
-
let safeRoot;
|
|
2420
|
-
try {
|
|
2421
|
-
safeRoot = await root(sessionsRoot, {
|
|
2422
|
-
hardlinks: "reject",
|
|
2423
|
-
maxBytes: Number.MAX_SAFE_INTEGER,
|
|
2424
|
-
symlinks: "reject"
|
|
2425
|
-
});
|
|
2426
|
-
} catch {
|
|
2427
|
-
return;
|
|
2428
|
-
}
|
|
2429
|
-
const candidates = rolloutPath.endsWith(".zst") ? [rolloutPath, rolloutPath.slice(0, -4)] : [rolloutPath, `${rolloutPath}.zst`];
|
|
2430
|
-
for (const candidate of candidates) {
|
|
2431
|
-
let opened;
|
|
2432
|
-
try {
|
|
2433
|
-
opened = await safeRoot.open(path.relative(sessionsRoot, candidate));
|
|
2434
|
-
} catch {
|
|
2435
|
-
continue;
|
|
2436
|
-
}
|
|
2437
|
-
const input = opened.handle.createReadStream({
|
|
2438
|
-
autoClose: false,
|
|
2439
|
-
highWaterMark: SESSION_META_READ_CHUNK_BYTES
|
|
2440
|
-
});
|
|
2441
|
-
const reader = candidate.endsWith(".zst") ? input.pipe(createZstdDecompress()) : input;
|
|
2442
|
-
try {
|
|
2443
|
-
const chunks = [];
|
|
2444
|
-
let bytesReadTotal = 0;
|
|
2445
|
-
let line;
|
|
2446
|
-
for await (const value of reader) {
|
|
2447
|
-
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
2448
|
-
const remaining = MAX_SESSION_META_BYTES - bytesReadTotal;
|
|
2449
|
-
if (remaining <= 0) break;
|
|
2450
|
-
const bounded = chunk.subarray(0, remaining);
|
|
2451
|
-
bytesReadTotal += bounded.length;
|
|
2452
|
-
const newline = bounded.indexOf(10);
|
|
2453
|
-
chunks.push(newline >= 0 ? bounded.subarray(0, newline) : bounded);
|
|
2454
|
-
if (newline >= 0) {
|
|
2455
|
-
line = Buffer.concat(chunks).toString("utf8");
|
|
2456
|
-
break;
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
if (!line) continue;
|
|
2460
|
-
let parsed;
|
|
2461
|
-
try {
|
|
2462
|
-
parsed = JSON.parse(line);
|
|
2463
|
-
} catch {
|
|
2464
|
-
continue;
|
|
2465
|
-
}
|
|
2466
|
-
if (!isRecord(parsed) || parsed.type !== "session_meta" || !isRecord(parsed.payload)) return false;
|
|
2467
|
-
const payload = parsed.payload;
|
|
2468
|
-
return (payload.id ?? payload.session_id) === threadId && payload.originator === "openclaw";
|
|
2469
|
-
} catch {
|
|
2470
|
-
continue;
|
|
2471
|
-
} finally {
|
|
2472
|
-
reader.destroy();
|
|
2473
|
-
input.destroy();
|
|
2474
|
-
await opened.handle.close().catch(() => void 0);
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
/**
|
|
2479
|
-
* Codex 0.147 reports OpenClaw app-server rollouts as `vscode`, so the rollout's
|
|
2480
|
-
* immutable session metadata is the authoritative historical provenance.
|
|
2481
|
-
*/
|
|
2482
|
-
async function isOpenClawManagedCodexThread(thread, localSessionsRoot) {
|
|
2483
|
-
const rolloutPath = typeof thread.path === "string" ? thread.path.trim() : "";
|
|
2484
|
-
if (!localSessionsRoot || !rolloutPath) return false;
|
|
2485
|
-
const cacheKey = `${localSessionsRoot}\0${rolloutPath}`;
|
|
2486
|
-
const cached = provenanceByPath.get(cacheKey);
|
|
2487
|
-
if (cached !== void 0) return cached;
|
|
2488
|
-
const managed = await readOpenClawOriginator(localSessionsRoot, rolloutPath, thread.id);
|
|
2489
|
-
if (managed !== void 0) cacheProvenance(cacheKey, managed);
|
|
2490
|
-
return managed ?? false;
|
|
2491
|
-
}
|
|
2492
|
-
//#endregion
|
|
2493
2564
|
//#region extensions/codex/src/session-catalog-control.ts
|
|
2494
2565
|
const CODEX_SESSION_CATALOG_LIST_TTL_MS = 32e3;
|
|
2495
2566
|
const CODEX_SESSION_CATALOG_LIST_CACHE_MAX_ENTRIES = 32;
|
|
@@ -2508,21 +2579,75 @@ function createCodexCatalogRequestSnapshot(requestTimeoutMs, request) {
|
|
|
2508
2579
|
requestTimeoutMs,
|
|
2509
2580
|
listThreads: (params, timeoutMs) => request(CODEX_CONTROL_METHODS.listThreads, params, timeoutMs),
|
|
2510
2581
|
listThreadTurns: (params) => request(CODEX_CONTROL_METHODS.listThreadTurns, params),
|
|
2511
|
-
forkThread: (params) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params)),
|
|
2512
|
-
readThread: async (threadId, includeTurns) => (await request(CODEX_CONTROL_METHODS.readThread, {
|
|
2582
|
+
forkThread: (params, assertCurrent) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params), void 0, assertCurrent),
|
|
2583
|
+
readThread: async (threadId, includeTurns, timeoutMs) => (await request(CODEX_CONTROL_METHODS.readThread, {
|
|
2513
2584
|
threadId,
|
|
2514
2585
|
includeTurns
|
|
2515
|
-
})).thread,
|
|
2516
|
-
archiveThread: async (threadId) => {
|
|
2517
|
-
await request(CODEX_CONTROL_METHODS.archiveThread, { threadId });
|
|
2586
|
+
}, timeoutMs)).thread,
|
|
2587
|
+
archiveThread: async (threadId, assertCurrent) => {
|
|
2588
|
+
await request(CODEX_CONTROL_METHODS.archiveThread, { threadId }, void 0, assertCurrent);
|
|
2518
2589
|
}
|
|
2519
2590
|
};
|
|
2520
2591
|
}
|
|
2521
2592
|
function createCodexSessionCatalogControlFromRequests(params) {
|
|
2522
2593
|
return {
|
|
2594
|
+
forkContext: params.forkContext,
|
|
2523
2595
|
...params.clientId ? { clientId: params.clientId } : {},
|
|
2524
2596
|
...params.connectionFingerprint ? { connectionFingerprint: params.connectionFingerprint } : {},
|
|
2525
2597
|
withPinnedConnection: params.withPinnedConnection,
|
|
2598
|
+
async requireEligibleThread(threadId) {
|
|
2599
|
+
const requests = params.createRequestSnapshot();
|
|
2600
|
+
const deadline = params.now() + requests.requestTimeoutMs;
|
|
2601
|
+
const unverified = () => new CatalogParamsError("Codex session eligibility could not be verified. Refresh the catalog and verify the session in its native Codex home before retrying.");
|
|
2602
|
+
const remaining = () => {
|
|
2603
|
+
const timeoutMs = Math.ceil(deadline - params.now());
|
|
2604
|
+
if (timeoutMs <= 0) throw unverified();
|
|
2605
|
+
return timeoutMs;
|
|
2606
|
+
};
|
|
2607
|
+
const verify = async () => {
|
|
2608
|
+
if (params.sourceHomeId && await params.managedThreads?.has(params.sourceHomeId, threadId)) throw unverified();
|
|
2609
|
+
const root = params.localSessionsRoot;
|
|
2610
|
+
const thread = root ? await requests.readThread(threadId, false, remaining()) : void 0;
|
|
2611
|
+
if (root && (!thread || thread.id !== threadId || !isInteractiveThreadSource(thread.source))) throw unverified();
|
|
2612
|
+
let cursor;
|
|
2613
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
2614
|
+
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
2615
|
+
const page = await requests.listThreads({
|
|
2616
|
+
archived: false,
|
|
2617
|
+
limit: 100,
|
|
2618
|
+
modelProviders: [],
|
|
2619
|
+
sortKey: root ? "recency_at" : "updated_at",
|
|
2620
|
+
sortDirection: "desc",
|
|
2621
|
+
...root ? {
|
|
2622
|
+
useStateDbOnly: true,
|
|
2623
|
+
...thread?.cwd ? { cwd: thread.cwd } : {}
|
|
2624
|
+
} : {},
|
|
2625
|
+
...cursor ? { cursor } : {}
|
|
2626
|
+
}, remaining());
|
|
2627
|
+
remaining();
|
|
2628
|
+
const candidate = page.data.find((value) => value.id === threadId);
|
|
2629
|
+
if (candidate) {
|
|
2630
|
+
if (!isInteractiveThreadSource(candidate.source)) throw unverified();
|
|
2631
|
+
if (root && thread) {
|
|
2632
|
+
const rolloutPath = thread.path;
|
|
2633
|
+
if (!rolloutPath || !candidate.path || rolloutPath.replace(/\.zst$/u, "") !== candidate.path.replace(/\.zst$/u, "")) throw unverified();
|
|
2634
|
+
const metadata = await readCodexSessionMeta(root, rolloutPath, threadId);
|
|
2635
|
+
remaining();
|
|
2636
|
+
if (!metadata || !isInteractiveThreadSource(metadata.source) || metadata.originator === "openclaw") throw unverified();
|
|
2637
|
+
return thread;
|
|
2638
|
+
}
|
|
2639
|
+
return candidate;
|
|
2640
|
+
}
|
|
2641
|
+
const nextCursor = readControlCursor(page.nextCursor, "next response");
|
|
2642
|
+
if (!nextCursor || seenCursors.has(nextCursor)) throw unverified();
|
|
2643
|
+
seenCursors.add(nextCursor);
|
|
2644
|
+
cursor = nextCursor;
|
|
2645
|
+
}
|
|
2646
|
+
throw unverified();
|
|
2647
|
+
};
|
|
2648
|
+
return await withTimeout(verify(), requests.requestTimeoutMs, "Codex session eligibility could not be verified", unverified);
|
|
2649
|
+
},
|
|
2650
|
+
retireConnection: params.retireConnection,
|
|
2526
2651
|
async listPage(pageParams) {
|
|
2527
2652
|
const limit = normalizeLimit(pageParams.limit, "limit");
|
|
2528
2653
|
const search = pageParams.searchTerm?.trim().toLocaleLowerCase() || void 0;
|
|
@@ -2584,11 +2709,11 @@ function createCodexSessionCatalogControlFromRequests(params) {
|
|
|
2584
2709
|
async listTurnPage(listParams) {
|
|
2585
2710
|
return await params.createRequestSnapshot().listThreadTurns(listParams);
|
|
2586
2711
|
},
|
|
2587
|
-
async forkThread(forkParams) {
|
|
2588
|
-
return await params.createRequestSnapshot().forkThread(forkParams);
|
|
2712
|
+
async forkThread(forkParams, assertCurrent) {
|
|
2713
|
+
return await params.createRequestSnapshot().forkThread(forkParams, assertCurrent);
|
|
2589
2714
|
},
|
|
2590
|
-
async archiveThread(threadId) {
|
|
2591
|
-
await params.createRequestSnapshot().archiveThread(threadId);
|
|
2715
|
+
async archiveThread(threadId, assertCurrent) {
|
|
2716
|
+
await params.createRequestSnapshot().archiveThread(threadId, assertCurrent);
|
|
2592
2717
|
}
|
|
2593
2718
|
};
|
|
2594
2719
|
}
|
|
@@ -2606,7 +2731,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2606
2731
|
const catalogPagesByConfig = /* @__PURE__ */ new WeakMap();
|
|
2607
2732
|
const resolveRequestOptions = (startOptions, agentId, source) => {
|
|
2608
2733
|
const runtimeConfig = params.getRuntimeConfig();
|
|
2609
|
-
const agentDir = source?.agentDir ?? resolveAgentDir(runtimeConfig ?? {}, agentId);
|
|
2734
|
+
const agentDir = source?.agentDir ?? resolveAgentDir$1(runtimeConfig ?? {}, agentId);
|
|
2610
2735
|
const resolvedStartOptions = source?.appServer.start ?? startOptions;
|
|
2611
2736
|
if (!runtimeConfig) return {
|
|
2612
2737
|
agentDir,
|
|
@@ -2633,8 +2758,9 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2633
2758
|
const pluginConfig = getPluginConfig();
|
|
2634
2759
|
const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
|
|
2635
2760
|
const requestOptions = resolveRequestOptions(runtime.start, agentId, source);
|
|
2636
|
-
return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await codexControlRequest(pluginConfig, method, requestParams, {
|
|
2761
|
+
return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await codexControlRequest(pluginConfig, method, requestParams, {
|
|
2637
2762
|
...requestOptions,
|
|
2763
|
+
assertCurrent,
|
|
2638
2764
|
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
2639
2765
|
}));
|
|
2640
2766
|
};
|
|
@@ -2650,18 +2776,31 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2650
2776
|
timeoutMs: runtime.requestTimeoutMs
|
|
2651
2777
|
});
|
|
2652
2778
|
try {
|
|
2653
|
-
const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await requestCodexAppServerClientJson({
|
|
2779
|
+
const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await requestCodexAppServerClientJson({
|
|
2654
2780
|
client,
|
|
2655
2781
|
method,
|
|
2656
2782
|
requestParams,
|
|
2657
2783
|
config: runtimeConfig,
|
|
2658
|
-
timeoutMs: timeoutMs ?? runtime.requestTimeoutMs
|
|
2784
|
+
timeoutMs: timeoutMs ?? runtime.requestTimeoutMs,
|
|
2785
|
+
assertCurrent
|
|
2659
2786
|
}));
|
|
2660
2787
|
const pinnedControl = createCodexSessionCatalogControlFromRequests({
|
|
2788
|
+
forkContext: {
|
|
2789
|
+
client,
|
|
2790
|
+
appServer: runtime,
|
|
2791
|
+
pluginConfig,
|
|
2792
|
+
agentDir,
|
|
2793
|
+
localSessionsRoot: source?.localSessionsRoot
|
|
2794
|
+
},
|
|
2661
2795
|
clientId: resolveCodexAppServerClientInstanceId(client),
|
|
2796
|
+
retireConnection: () => {
|
|
2797
|
+
retireSharedCodexAppServerClientIfCurrent(client);
|
|
2798
|
+
},
|
|
2662
2799
|
connectionFingerprint: buildCodexAppServerConnectionFingerprint(runtime, agentDir),
|
|
2663
2800
|
createRequestSnapshot: () => requests,
|
|
2664
2801
|
...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
|
|
2802
|
+
sourceHomeId: source?.sourceHomeId,
|
|
2803
|
+
managedThreads: params.managedThreads,
|
|
2665
2804
|
now,
|
|
2666
2805
|
withPinnedConnection: async (nestedRun) => await nestedRun(pinnedControl)
|
|
2667
2806
|
});
|
|
@@ -2678,6 +2817,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2678
2817
|
});
|
|
2679
2818
|
return {
|
|
2680
2819
|
...control,
|
|
2820
|
+
requireEligibleThread: (threadId) => withPinnedConnection((pinned) => pinned.requireEligibleThread(threadId)),
|
|
2681
2821
|
async listPage(pageParams) {
|
|
2682
2822
|
const runtimeConfig = params.getRuntimeConfig();
|
|
2683
2823
|
if (!runtimeConfig) return await control.listPage(pageParams);
|
|
@@ -2688,7 +2828,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2688
2828
|
}
|
|
2689
2829
|
const key = codexCatalogPageCacheKey(pageParams, agentId, source);
|
|
2690
2830
|
const cached = cache.get(key);
|
|
2691
|
-
if (
|
|
2831
|
+
if (cached) {
|
|
2692
2832
|
cache.delete(key);
|
|
2693
2833
|
cache.set(key, cached);
|
|
2694
2834
|
if (cached.expiresAt > now()) return cached.value ?? await cached.page;
|
|
@@ -2719,7 +2859,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2719
2859
|
});
|
|
2720
2860
|
else cache.delete(key);
|
|
2721
2861
|
};
|
|
2722
|
-
if (
|
|
2862
|
+
if (staleValue) {
|
|
2723
2863
|
page.then(settle, restore);
|
|
2724
2864
|
return staleValue;
|
|
2725
2865
|
}
|
|
@@ -2751,14 +2891,21 @@ function createCodexSessionCatalogNodeInvokePolicies() {
|
|
|
2751
2891
|
commands: [
|
|
2752
2892
|
CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
2753
2893
|
CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
2754
|
-
CODEX_TERMINAL_RESUME_COMMAND
|
|
2894
|
+
CODEX_TERMINAL_RESUME_COMMAND,
|
|
2895
|
+
CODEX_TERMINAL_START_COMMAND
|
|
2755
2896
|
],
|
|
2756
2897
|
defaultPlatforms: [
|
|
2757
2898
|
"macos",
|
|
2758
2899
|
"linux",
|
|
2759
2900
|
"windows"
|
|
2760
2901
|
],
|
|
2761
|
-
handle: (context) =>
|
|
2902
|
+
handle: (context) => {
|
|
2903
|
+
if (context.command === "codex.terminal.start.v1") return context.client?.scopes?.includes("operator.admin") && context.config.gateway?.cliAgents?.enabled === true && context.config.gateway?.terminal?.enabled !== false ? { ok: true } : {
|
|
2904
|
+
ok: false,
|
|
2905
|
+
message: "Native terminal start requires operator.admin and enabled CLI agents and terminals"
|
|
2906
|
+
};
|
|
2907
|
+
return context.command === "codex.terminal.resume.v1" ? { ok: true } : context.invokeNode();
|
|
2908
|
+
}
|
|
2762
2909
|
}];
|
|
2763
2910
|
}
|
|
2764
2911
|
function toGenericCatalogHost(host, localTerminalAvailable) {
|
|
@@ -2813,7 +2960,7 @@ function registerCodexSessionCatalog(params) {
|
|
|
2813
2960
|
const homes = params.control.homesForAgent(agentId);
|
|
2814
2961
|
return allowProcessHomeFallback === false ? homes.filter((home) => !home.usesProcessHomeFallback) : homes;
|
|
2815
2962
|
};
|
|
2816
|
-
const resolveRequestAgentId = (agentId) =>
|
|
2963
|
+
const resolveRequestAgentId = (agentId) => resolveSessionAgentIdsStrict({
|
|
2817
2964
|
config: params.getRuntimeConfig() ?? params.api.config,
|
|
2818
2965
|
agentId
|
|
2819
2966
|
}).sessionAgentId;
|
|
@@ -2848,8 +2995,11 @@ function registerCodexSessionCatalog(params) {
|
|
|
2848
2995
|
const localTerminalAvailable = resolveLocalCodexTerminalExecutable() !== void 0;
|
|
2849
2996
|
const { agentId: requestedAgentId, allowProcessHomeFallback, listNodes, onHost, sessionEntries, ...gatewayQuery } = query;
|
|
2850
2997
|
const agentId = resolveRequestAgentId(requestedAgentId);
|
|
2851
|
-
const mapHost = (host) => toGenericCatalogHost(host, localTerminalAvailable);
|
|
2852
2998
|
const localHomes = [...catalogHomes(agentId, allowProcessHomeFallback)];
|
|
2999
|
+
const mapHost = (host) => ({
|
|
3000
|
+
...toGenericCatalogHost(host, localTerminalAvailable),
|
|
3001
|
+
canStartTerminal: host.kind === "gateway" ? localTerminalAvailable && localHomes.some((home) => home.hostId === host.hostId && home.appServer.start.transport === "stdio") : host.canStartTerminal === true
|
|
3002
|
+
});
|
|
2853
3003
|
return (await listCodexSessionCatalog({
|
|
2854
3004
|
agentId,
|
|
2855
3005
|
bindingStore: params.bindingStore,
|
|
@@ -2894,7 +3044,7 @@ function registerCodexSessionCatalog(params) {
|
|
|
2894
3044
|
if (!isLocalCodexCatalogHost(request.hostId)) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
2895
3045
|
const { agentId, source, control } = bindLocalRequest(request);
|
|
2896
3046
|
let upstreamBaseline;
|
|
2897
|
-
|
|
3047
|
+
const continued = await continueLocalCodexSession({
|
|
2898
3048
|
agentId,
|
|
2899
3049
|
api: params.api,
|
|
2900
3050
|
bindingStore: params.bindingStore,
|
|
@@ -2907,7 +3057,8 @@ function registerCodexSessionCatalog(params) {
|
|
|
2907
3057
|
onContinued: (baseline) => {
|
|
2908
3058
|
upstreamBaseline = baseline;
|
|
2909
3059
|
}
|
|
2910
|
-
})
|
|
3060
|
+
});
|
|
3061
|
+
return codexUpstreamContinueResult(continued.sessionKey, request.threadId, upstreamBaseline);
|
|
2911
3062
|
},
|
|
2912
3063
|
checkUpstreamActivity: (probes, policy) => checkUpstreamActivity(probes.filter((probe) => !isLocalCodexCatalogHost(probe.hostId) || policy?.allowProcessHomeFallback !== false || catalogHomes(probe.agentId, false).some((home) => home.hostId === probe.hostId))),
|
|
2913
3064
|
archive: async (request) => {
|
|
@@ -2943,11 +3094,16 @@ function registerCodexSessionCatalog(params) {
|
|
|
2943
3094
|
});
|
|
2944
3095
|
},
|
|
2945
3096
|
startTerminalSession: async (request) => {
|
|
2946
|
-
|
|
3097
|
+
const source = request.nodeId ? void 0 : resolveLocalCatalogHomeForThread({
|
|
3098
|
+
homes: [...catalogHomes(request.agentId, request.allowProcessHomeFallback)],
|
|
3099
|
+
hostId: request.hostId ?? "gateway:local"
|
|
3100
|
+
});
|
|
3101
|
+
if (source && source.appServer.start.transport !== "stdio") throw new CatalogParamsError("Native terminal start requires a local Codex source");
|
|
2947
3102
|
return await startCodexCatalogTerminal({
|
|
2948
3103
|
getPluginConfig: params.getPluginConfig,
|
|
2949
3104
|
getRuntimeConfig: params.getRuntimeConfig,
|
|
2950
|
-
...request
|
|
3105
|
+
...request,
|
|
3106
|
+
source
|
|
2951
3107
|
});
|
|
2952
3108
|
}
|
|
2953
3109
|
});
|
|
@@ -2961,4 +3117,4 @@ const codexSessionCatalogRuntime = {
|
|
|
2961
3117
|
archiveLocal: archiveLocalCodexSession
|
|
2962
3118
|
};
|
|
2963
3119
|
//#endregion
|
|
2964
|
-
export { CODEX_LOCAL_SESSION_HOST_ID as a,
|
|
3120
|
+
export { CODEX_LOCAL_SESSION_HOST_ID as a, readCodexStatusProbes as c, createCodexCliSessionNodeHostCommands as d, createCodexCliSessionNodeInvokePolicies as f, resumeCodexCliSessionOnNode as g, resolveCodexCliSessionForBindingOnNode as h, createCodexSessionCatalogNodeHostCommands as i, requestOptions as l, listCodexCliSessionsOnNode as m, createCodexSessionCatalogNodeInvokePolicies as n, assertCodexArchiveDescendantsUnowned as o, formatCodexCliSessions as p, createCodexSessionCatalogControl as r, codexControlRequest as s, codexSessionCatalogRuntime as t, safeCodexControlRequest as u };
|