@openclaw/codex 2026.9.1-beta.1 → 2026.9.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 +8 -9
- package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-Bp1ZOnk2.js} +105 -75
- package/dist/attempt-context-CSazkWns.js +634 -0
- package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
- package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-DxGJRYbY.js} +53 -57
- package/dist/{capabilities-BRMh0aIq.js → capabilities-B1mgvpP8.js} +2 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-DEsIufkt.js → command-formatters-DSbGMXA0.js} +5 -4
- package/dist/{command-handlers-D_-1_qRp.js → command-handlers-DOJvGv6-.js} +243 -148
- package/dist/{compact-BglNFZrQ.js → compact-Y_xBqrHN.js} +109 -65
- package/dist/{config-giW8X0QA.js → config-CyapYSji.js} +328 -374
- package/dist/config-utils-C57JiJrU.js +69 -0
- 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 +144 -14
- package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BwpvTxaX.js} +1119 -1135
- package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-CY9Jn4bA.js} +4 -3
- package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-B9kp6iGC.js} +15 -6
- package/dist/event-projector-usage-ZqEVbAEY.js +212 -0
- package/dist/{harness-Csz0F-DK.js → harness-Czh2Sdoc.js} +65 -52
- package/dist/harness.js +1 -1
- package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
- package/dist/index.js +495 -457
- package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-CRh-51AK.js} +9 -21
- package/dist/{managed-binary-DFYK4DWq.js → managed-binary-DcpnJF9x.js} +117 -80
- package/dist/media-understanding-provider.js +1 -1
- package/dist/model-catalog-DT5vo6qo.js +101 -0
- package/dist/{models-TS3HBU-q.js → models-CcrgSMQR.js} +26 -29
- package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-B3bvQGcg.js} +63 -122
- package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-nfbUOtUB.js} +10 -7
- package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-aQVdyL5g.js} +30 -3
- package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-B7gHXJ3d.js} +44 -30
- package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BwCLTY_U.js} +6 -6
- package/dist/provider-capabilities-BpG9Ltc5.js +101 -0
- package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-Fx_l4eNa.js} +3 -2
- package/dist/{sandbox-guard-kuWTn9C5.js → request-C8H22rPi.js} +195 -2
- package/dist/{run-attempt-DYc0QCBF.js → run-attempt-BzthFQSM.js} +1616 -2634
- package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-Bh_gf6-t.js} +22 -15
- package/dist/{session-binding-Bcynfd-_.js → session-binding-4Ki04sW-.js} +282 -399
- package/dist/session-binding-record-CyaXZvTe.js +270 -0
- package/dist/{session-catalog-DaLKVtpH.js → session-catalog-B47uwekb.js} +812 -504
- package/dist/{session-cli-DMc4oS2w.js → session-cli-BjLhpmS1.js} +1 -1
- package/dist/session-history-DWmMqiqW.js +93 -0
- package/dist/session-history-read-LHldwvyh.js +36 -0
- package/dist/session-history-worker-runtime.js +48 -0
- package/dist/session-history.worker-zZVcx0ME.js +267 -0
- package/dist/session-history.worker.js +2 -0
- package/dist/session-initialization-BCFVnLL4.js +75 -0
- package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-BaueHmSj.js} +2 -2
- package/dist/session-retirement-DNdmYdFc.js +97 -0
- package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-DReTpGz6.js} +20 -4
- package/dist/settled-turn-context-BOM3Y1pl.js +45 -0
- package/dist/settled-turn-finalizer-BI-b1sry.js +115 -0
- package/dist/{shared-client-DKz23hHY.js → shared-client-CKhX2kFt.js} +3659 -847
- package/dist/{side-question-DjA_jsNe.js → side-question-B6DWAw1A.js} +168 -81
- package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DPK30jad.js} +2412 -2145
- package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-CbucAmmp.js} +27 -16
- package/dist/thread-policy-CTalhWFy.js +105 -0
- package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-BLgqkUc1.js} +61 -9
- package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-_njEB84z.js} +114 -110
- package/dist/transcript-mirror-attestation-Byl34PC-.js +76 -0
- package/dist/transport-stdio-CzTngV0j.js +728 -0
- package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
- package/dist/upstream-session-fork-1fwbNKzQ.js +886 -0
- package/dist/{usage-ROIaW3wo.js → usage-i9fv5-4m.js} +3 -3
- package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-DKQn67A7.js} +3 -3
- package/openclaw.plugin.json +1 -30
- package/package.json +6 -6
- package/dist/binding-connection-BMvPX73o.js +0 -45
- package/dist/command-rpc-CCeCkf_v.js +0 -90
- package/dist/event-projector-usage-Bk1I90Bg.js +0 -328
- 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,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { c as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { n as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { r as
|
|
10
|
-
import { n as
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
1
|
+
import { l as sessionBindingIdentity } from "./session-binding-record-CyaXZvTe.js";
|
|
2
|
+
import { B as isJsonObject, I as CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES, L as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, M as resolveCodexAppServerHomeDir, N as resolveCodexAppServerLocalHomeDir, O as readCodexPluginConfig, P as resolveCodexAppServerUserHomeDir, i as resolveCodexAppServerRuntimeOptions, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-CyapYSji.js";
|
|
3
|
+
import { $ as assertCodexThreadForkParams, Ft as withTimeout, J as isCodexThreadReadMissingError, K as resolveCodexAppServerClientInstanceId, Kt as resolveCodexAppServerPreparedAuthHandoff, Ut as resolveCodexAppServerAuthProfileStore, Vt as resolveCodexAppServerAuthProfileId, Xt as readCodexSessionMeta, Yt as isOpenClawManagedCodexThread, c as getLeasedSharedCodexAppServerClient, h as releaseLeasedSharedCodexAppServerClient, y as retireSharedCodexAppServerClientIfCurrent } from "./shared-client-CKhX2kFt.js";
|
|
4
|
+
import { r as createCodexSessionGenerationSupersededError, s as reclaimCurrentCodexSessionGeneration } from "./session-binding-4Ki04sW-.js";
|
|
5
|
+
import { a as closeCodexStartupClientBestEffort } from "./attempt-client-cleanup-Bp1ZOnk2.js";
|
|
6
|
+
import { n as listCodexAppServerModels } from "./models-CcrgSMQR.js";
|
|
7
|
+
import { a as replaceCodexCatalogConnectionHomes, n as buildCodexAppServerConnectionFingerprint, t as resumeCodexAppServerThread } from "./thread-resume-BLgqkUc1.js";
|
|
8
|
+
import { d as codexCatalogHomeId, l as projectCodexUserItemText, r as importCodexThreadHistoryToTranscript, u as canonicalCodexCatalogHome } from "./transcript-mirror-_njEB84z.js";
|
|
9
|
+
import { i as requestCodexAppServerJson, o as withCodexAppServerJsonClient, r as requestCodexAppServerClientJson } from "./request-C8H22rPi.js";
|
|
10
|
+
import { n as describeControlFailure, t as CODEX_CONTROL_METHODS } from "./capabilities-B1mgvpP8.js";
|
|
11
|
+
import { a as formatCodexDisplayText } from "./command-formatters-DSbGMXA0.js";
|
|
12
|
+
import { n as createCodexCliNodeConversationBindingData } from "./conversation-binding-data-BiodpIzq.js";
|
|
13
|
+
import { n as prepareCodexAppServerAuthBinding } from "./auth-binding-Ds_QVm-X.js";
|
|
14
|
+
import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-DReTpGz6.js";
|
|
14
15
|
import { asFiniteNumber, isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
16
|
+
import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
17
|
+
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
20
|
+
import { runCommandBuffered } from "openclaw/plugin-sdk/process-runtime";
|
|
15
21
|
import fs from "node:fs/promises";
|
|
16
22
|
import path from "node:path";
|
|
17
|
-
import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
18
23
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
24
|
+
import { prepareAgentRuntimeAuth } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
25
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
19
26
|
import fsSync from "node:fs";
|
|
20
27
|
import os from "node:os";
|
|
21
|
-
import { listAgentIds, resolveAgentDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent
|
|
22
|
-
import { runCommandBuffered } from "openclaw/plugin-sdk/process-runtime";
|
|
28
|
+
import { listAgentIds as listAgentIds$1, resolveAgentDir as resolveAgentDir$2, resolveAgentWorkspaceDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId as resolveDefaultAgentId$1, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
|
|
23
29
|
import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
|
|
24
|
-
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
25
30
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
26
31
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
27
32
|
import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
|
|
28
33
|
import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
29
|
-
import { root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
30
34
|
import process$1 from "node:process";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
+
import { resolveSessionModelRef } from "openclaw/plugin-sdk/model-session-runtime";
|
|
36
|
+
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
|
|
37
|
+
import { createSessionCatalogAdoptionCoordinator, listSessionCatalogEntries, publishSessionCatalogHost, sessionCatalogAdoptedSessionKey, sessionCatalogAdoptedSourceKey, sessionCatalogPaging } from "openclaw/plugin-sdk/session-catalog";
|
|
38
|
+
import { decodeNodePtyResumeParams, decodeNodePtyStartParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
|
|
35
39
|
//#region extensions/codex/src/jsonl-lines.ts
|
|
36
|
-
const JSONL_STREAM_THRESHOLD_BYTES =
|
|
37
|
-
const JSONL_READ_CHUNK_BYTES =
|
|
40
|
+
const JSONL_STREAM_THRESHOLD_BYTES = 4194304;
|
|
41
|
+
const JSONL_READ_CHUNK_BYTES = 1048576;
|
|
38
42
|
async function visitJsonlLines(file, visitor, chunkBytes = JSONL_READ_CHUNK_BYTES) {
|
|
39
43
|
let size;
|
|
40
44
|
try {
|
|
@@ -134,7 +138,7 @@ const CODEX_CLI_SESSIONS_LIST_COMMAND = "codex.cli.sessions.list";
|
|
|
134
138
|
const CODEX_CLI_SESSION_RESUME_COMMAND = "codex.cli.session.resume";
|
|
135
139
|
const DEFAULT_SESSION_LIMIT = 10;
|
|
136
140
|
const MAX_SESSION_LIMIT = 50;
|
|
137
|
-
const DEFAULT_RESUME_TIMEOUT_MS =
|
|
141
|
+
const DEFAULT_RESUME_TIMEOUT_MS = 12e5;
|
|
138
142
|
const SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
139
143
|
const activeResumeSessions = /* @__PURE__ */ new Set();
|
|
140
144
|
function createCodexCliSessionNodeHostCommands() {
|
|
@@ -525,7 +529,7 @@ function normalizeLimit$1(value) {
|
|
|
525
529
|
return typeof value === "number" && Number.isFinite(value) ? Math.min(MAX_SESSION_LIMIT, Math.max(1, Math.floor(value))) : DEFAULT_SESSION_LIMIT;
|
|
526
530
|
}
|
|
527
531
|
function normalizeTimeoutMs(value) {
|
|
528
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(
|
|
532
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(36e5, Math.floor(value)) : DEFAULT_RESUME_TIMEOUT_MS;
|
|
529
533
|
}
|
|
530
534
|
function truncateText(value, max) {
|
|
531
535
|
if (value.length <= max) return value;
|
|
@@ -546,6 +550,206 @@ function formatNodeLabel(node) {
|
|
|
546
550
|
].filter(Boolean).join(" / ") || "node";
|
|
547
551
|
}
|
|
548
552
|
//#endregion
|
|
553
|
+
//#region extensions/codex/src/command-rpc.ts
|
|
554
|
+
async function prepareControlAuth(options, startOptions) {
|
|
555
|
+
if (!options.onResponse) return {
|
|
556
|
+
authProfileId: options.authProfileId ?? void 0,
|
|
557
|
+
clientOptions: { authProfileId: options.authProfileId }
|
|
558
|
+
};
|
|
559
|
+
if (!options.config || !options.sessionKey || !options.sessionId) throw new Error("Codex control subscription requires admitted session authority.");
|
|
560
|
+
const config = options.config;
|
|
561
|
+
const { sessionAgentId } = resolveSessionAgentIdsStrict({
|
|
562
|
+
config,
|
|
563
|
+
sessionKey: options.sessionKey,
|
|
564
|
+
agentId: options.agentId
|
|
565
|
+
});
|
|
566
|
+
const agentDir = options.agentDir ?? resolveAgentDir$2(config, sessionAgentId);
|
|
567
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, sessionAgentId);
|
|
568
|
+
const entry = getSessionEntry({
|
|
569
|
+
agentId: sessionAgentId,
|
|
570
|
+
storePath: options.storePath?.trim() || resolveStorePath(config.session?.store, { agentId: sessionAgentId }),
|
|
571
|
+
sessionKey: options.sessionKey,
|
|
572
|
+
hydrateSkillPromptRefs: false,
|
|
573
|
+
readConsistency: "latest"
|
|
574
|
+
});
|
|
575
|
+
if (entry?.sessionId !== options.sessionId) throw createCodexSessionGenerationSupersededError(options.sessionId);
|
|
576
|
+
if (options.authProfileId === null || startOptions.homeScope === "user") return {
|
|
577
|
+
authProfileId: options.authProfileId ?? void 0,
|
|
578
|
+
clientOptions: { authProfileId: options.authProfileId }
|
|
579
|
+
};
|
|
580
|
+
const model = resolveSessionModelRef(config, entry, sessionAgentId);
|
|
581
|
+
const authProfileId = entry?.authProfileOverride ?? options.authProfileId;
|
|
582
|
+
const store = resolveCodexAppServerAuthProfileStore({
|
|
583
|
+
agentDir,
|
|
584
|
+
config,
|
|
585
|
+
authProfileId
|
|
586
|
+
});
|
|
587
|
+
const { plan, attempts } = prepareAgentRuntimeAuth({
|
|
588
|
+
provider: model.provider,
|
|
589
|
+
modelId: model.model,
|
|
590
|
+
config,
|
|
591
|
+
agentDir,
|
|
592
|
+
workspaceDir,
|
|
593
|
+
authProfileStore: store,
|
|
594
|
+
sessionAuthProfileId: authProfileId,
|
|
595
|
+
sessionAuthProfileSource: entry?.authProfileOverrideSource,
|
|
596
|
+
harnessId: "codex",
|
|
597
|
+
harnessAuthBootstrap: "harness"
|
|
598
|
+
});
|
|
599
|
+
const route = plan.modelRoute;
|
|
600
|
+
const resolvedAuth = route ? await resolveApiKeyForProvider({
|
|
601
|
+
provider: route.provider,
|
|
602
|
+
modelId: route.modelId,
|
|
603
|
+
modelApi: route.api,
|
|
604
|
+
cfg: config,
|
|
605
|
+
agentDir,
|
|
606
|
+
workspaceDir,
|
|
607
|
+
store,
|
|
608
|
+
profileId: attempts[0]?.profileId,
|
|
609
|
+
lockedProfile: plan.forwardedAuthProfileSource === "user",
|
|
610
|
+
allowAuthProfileFallback: attempts[0]?.allowAuthProfileFallback,
|
|
611
|
+
skipSetupProviderFallback: true
|
|
612
|
+
}) : void 0;
|
|
613
|
+
const handoff = await resolveCodexAppServerPreparedAuthHandoff({
|
|
614
|
+
authRequirement: route?.authRequirement,
|
|
615
|
+
resolvedApiKey: resolvedAuth?.apiKey,
|
|
616
|
+
authProfileId: route ? plan.forwardedAuthProfileId : resolveCodexAppServerAuthProfileId({
|
|
617
|
+
authProfileId: plan.forwardedAuthProfileId,
|
|
618
|
+
store,
|
|
619
|
+
config
|
|
620
|
+
}),
|
|
621
|
+
authProfileStore: store,
|
|
622
|
+
agentDir,
|
|
623
|
+
homeScope: startOptions.homeScope ?? "agent",
|
|
624
|
+
config,
|
|
625
|
+
subscriptionProfileRequiredError: "Prepared Codex subscription route requires a forwarded OpenAI OAuth or token profile.",
|
|
626
|
+
subscriptionProfileUnusableError: "Prepared Codex subscription auth profile is unusable."
|
|
627
|
+
});
|
|
628
|
+
const binding = handoff.authProfileId ? await prepareCodexAppServerAuthBinding({
|
|
629
|
+
authProfileId: handoff.authProfileId,
|
|
630
|
+
authProfileStore: store,
|
|
631
|
+
agentDir,
|
|
632
|
+
config
|
|
633
|
+
}) : void 0;
|
|
634
|
+
return {
|
|
635
|
+
authProfileId: handoff.authProfileId,
|
|
636
|
+
clientOptions: {
|
|
637
|
+
...handoff.preparedAuth ? { preparedAuth: handoff.preparedAuth } : { authProfileId: handoff.authProfileId },
|
|
638
|
+
authRequirement: route?.authRequirement,
|
|
639
|
+
authProfileStore: binding?.authProfileStore ?? store,
|
|
640
|
+
authBindingFingerprint: binding?.fingerprint,
|
|
641
|
+
agentDir
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function requestOptions(pluginConfig, limit, config, agentDir) {
|
|
646
|
+
const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
647
|
+
return {
|
|
648
|
+
limit,
|
|
649
|
+
timeoutMs: runtime.requestTimeoutMs,
|
|
650
|
+
startOptions: runtime.start,
|
|
651
|
+
config,
|
|
652
|
+
agentDir
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async function codexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
656
|
+
const runtime = options.startOptions ? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig }) : resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
657
|
+
const startOptions = options.startOptions ?? runtime.start;
|
|
658
|
+
const auth = await prepareControlAuth(options, startOptions);
|
|
659
|
+
const controlRequestOptions = {
|
|
660
|
+
timeoutMs: options.timeoutMs ?? runtime.requestTimeoutMs,
|
|
661
|
+
assertCurrent: options.assertCurrent,
|
|
662
|
+
startOptions,
|
|
663
|
+
config: options.config,
|
|
664
|
+
sessionKey: options.sessionKey,
|
|
665
|
+
sessionId: options.sessionId,
|
|
666
|
+
agentDir: options.agentDir,
|
|
667
|
+
isolated: options.isolated,
|
|
668
|
+
...auth.clientOptions
|
|
669
|
+
};
|
|
670
|
+
if (options.onResponse || options.beforeRequest) return await withCodexAppServerJsonClient(controlRequestOptions, async (request, client, scope) => {
|
|
671
|
+
await options.beforeRequest?.(request, client, scope);
|
|
672
|
+
scope.assertCurrent();
|
|
673
|
+
let response;
|
|
674
|
+
if (method === "thread/resume") {
|
|
675
|
+
if (!isJsonObject(requestParams) || typeof requestParams.threadId !== "string") throw new Error("Codex thread/resume requires a thread id.");
|
|
676
|
+
response = await resumeCodexAppServerThread({
|
|
677
|
+
client,
|
|
678
|
+
request: {
|
|
679
|
+
...requestParams,
|
|
680
|
+
threadId: requestParams.threadId
|
|
681
|
+
},
|
|
682
|
+
requestResume: () => request({
|
|
683
|
+
method,
|
|
684
|
+
requestParams
|
|
685
|
+
}),
|
|
686
|
+
abandonClient: () => closeCodexStartupClientBestEffort(client)
|
|
687
|
+
});
|
|
688
|
+
} else response = await request({
|
|
689
|
+
method,
|
|
690
|
+
requestParams
|
|
691
|
+
});
|
|
692
|
+
await options.onResponse?.(response, client, {
|
|
693
|
+
authProfileId: auth.authProfileId,
|
|
694
|
+
assertCurrent: scope.assertCurrent
|
|
695
|
+
});
|
|
696
|
+
scope.assertCurrent();
|
|
697
|
+
return response;
|
|
698
|
+
});
|
|
699
|
+
return await requestCodexAppServerJson({
|
|
700
|
+
method,
|
|
701
|
+
requestParams,
|
|
702
|
+
...controlRequestOptions
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
async function safeCodexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
706
|
+
return await safeValue(async () => await codexControlRequest(pluginConfig, method, requestParams, options));
|
|
707
|
+
}
|
|
708
|
+
async function safeCodexModelList(pluginConfig, limit, config, agentDir) {
|
|
709
|
+
return await safeValue(async () => await listCodexAppServerModels(requestOptions(pluginConfig, limit, config, agentDir)));
|
|
710
|
+
}
|
|
711
|
+
async function readCodexStatusProbes(pluginConfig, config, agentDir) {
|
|
712
|
+
const [models, account, limits, mcps, skills] = await Promise.all([
|
|
713
|
+
safeCodexModelList(pluginConfig, 20, config, agentDir),
|
|
714
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, {
|
|
715
|
+
config,
|
|
716
|
+
agentDir
|
|
717
|
+
}),
|
|
718
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, {
|
|
719
|
+
config,
|
|
720
|
+
agentDir
|
|
721
|
+
}),
|
|
722
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, {
|
|
723
|
+
config,
|
|
724
|
+
agentDir
|
|
725
|
+
}),
|
|
726
|
+
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, {
|
|
727
|
+
config,
|
|
728
|
+
agentDir
|
|
729
|
+
})
|
|
730
|
+
]);
|
|
731
|
+
return {
|
|
732
|
+
models,
|
|
733
|
+
account,
|
|
734
|
+
limits,
|
|
735
|
+
mcps,
|
|
736
|
+
skills
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
async function safeValue(read) {
|
|
740
|
+
try {
|
|
741
|
+
return {
|
|
742
|
+
ok: true,
|
|
743
|
+
value: await read()
|
|
744
|
+
};
|
|
745
|
+
} catch (error) {
|
|
746
|
+
return {
|
|
747
|
+
ok: false,
|
|
748
|
+
error: describeControlFailure(error)
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
//#endregion
|
|
549
753
|
//#region extensions/codex/src/app-server/thread-archive-guard.ts
|
|
550
754
|
const DESCENDANT_PAGE_LIMIT = 100;
|
|
551
755
|
const MAX_DESCENDANT_PAGES = 100;
|
|
@@ -601,6 +805,7 @@ const DEFAULT_PAGE_LIMIT = 50;
|
|
|
601
805
|
const CODEX_APP_SERVER_THREADS_CAPABILITY = "codex-app-server-threads";
|
|
602
806
|
const CODEX_APP_SERVER_THREADS_LIST_COMMAND = "codex.appServer.threads.list.v1";
|
|
603
807
|
const CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND = "codex.appServer.thread.turns.list.v1";
|
|
808
|
+
const CODEX_CATALOG_TRANSCRIPT_READ_COMMAND = "codex.sessionCatalog.transcript.read.v1";
|
|
604
809
|
const CODEX_LOCAL_SESSION_HOST_ID = "gateway:local";
|
|
605
810
|
const NODE_INVOKE_TIMEOUT_MS = 65e3;
|
|
606
811
|
const MAX_SEARCH_LENGTH = 500;
|
|
@@ -613,7 +818,7 @@ const MAX_SESSION_PREVIEW_LENGTH = 500;
|
|
|
613
818
|
const MAX_SESSION_KEY_LENGTH = 1024;
|
|
614
819
|
const MAX_METADATA_LENGTH = 500;
|
|
615
820
|
const MAX_ACTIVE_FLAGS = 16;
|
|
616
|
-
const MAX_TRANSCRIPT_PAGE_BYTES =
|
|
821
|
+
const MAX_TRANSCRIPT_PAGE_BYTES = 20971520;
|
|
617
822
|
var CatalogParamsError = class extends Error {};
|
|
618
823
|
function readControlCursor(value, label) {
|
|
619
824
|
if (value === void 0 || value === null) return;
|
|
@@ -863,7 +1068,7 @@ function parseTranscriptPage(value) {
|
|
|
863
1068
|
...nextCursor ? { nextCursor } : {},
|
|
864
1069
|
...backwardsCursor ? { backwardsCursor } : {}
|
|
865
1070
|
};
|
|
866
|
-
if (Buffer.byteLength(JSON.stringify(page), "utf8") >
|
|
1071
|
+
if (Buffer.byteLength(JSON.stringify(page), "utf8") > 20971520) throw new Error("Codex app-server transcript page exceeds the safe response size");
|
|
867
1072
|
return page;
|
|
868
1073
|
}
|
|
869
1074
|
function requireBoundThread(entry) {
|
|
@@ -948,10 +1153,16 @@ async function finalizeNodeAdoptedSession(params) {
|
|
|
948
1153
|
update: (entry) => {
|
|
949
1154
|
const current = readNodeSessionMarker(entry);
|
|
950
1155
|
if (entry.sessionId?.trim() !== params.adopted.sessionId || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true || !current || current.sourceHostId !== params.marker.sourceHostId || current.sourceThreadId !== params.marker.sourceThreadId || current.nodeId !== params.marker.nodeId) throw changedError();
|
|
951
|
-
if (current.initializing !== true) return {
|
|
1156
|
+
if (current.initializing !== true) return {
|
|
1157
|
+
archivedAt: void 0,
|
|
1158
|
+
archivedBy: void 0,
|
|
1159
|
+
archiveReason: void 0
|
|
1160
|
+
};
|
|
952
1161
|
const codex = isRecord(entry.pluginExtensions?.codex) ? entry.pluginExtensions.codex : {};
|
|
953
1162
|
return {
|
|
954
1163
|
archivedAt: void 0,
|
|
1164
|
+
archivedBy: void 0,
|
|
1165
|
+
archiveReason: void 0,
|
|
955
1166
|
pluginExtensions: {
|
|
956
1167
|
...entry.pluginExtensions,
|
|
957
1168
|
codex: {
|
|
@@ -997,7 +1208,7 @@ async function createOrReuseNodeAdoptedSession(params) {
|
|
|
997
1208
|
key: nodeAdoptionSessionKey(params.hostId, params.record.threadId),
|
|
998
1209
|
agentId: params.agentId,
|
|
999
1210
|
recoverMatchingInitialEntry: true,
|
|
1000
|
-
|
|
1211
|
+
displayName: params.record.name ?? void 0,
|
|
1001
1212
|
...params.record.cwd?.trim() ? { spawnedCwd: params.record.cwd.trim() } : {},
|
|
1002
1213
|
initialEntry: {
|
|
1003
1214
|
agentHarnessId: "codex",
|
|
@@ -1040,182 +1251,6 @@ async function createOrReuseNodeAdoptedSession(params) {
|
|
|
1040
1251
|
}
|
|
1041
1252
|
}
|
|
1042
1253
|
//#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
1254
|
//#region extensions/codex/src/session-catalog-adoption.ts
|
|
1220
1255
|
const CODEX_SUPERVISION_SESSION_KEY_PREFIX = "harness:codex:supervision:";
|
|
1221
1256
|
const boundCatalogSessionId = (value) => boundedCatalogString(value, 256);
|
|
@@ -1255,7 +1290,7 @@ async function listAdoptedSessionEntries(params) {
|
|
|
1255
1290
|
if (!sessionKeyRest.startsWith(CODEX_SUPERVISION_SESSION_KEY_PREFIX) || !marker || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true) continue;
|
|
1256
1291
|
const sessionId = entry.sessionId?.trim();
|
|
1257
1292
|
if (!sessionId) continue;
|
|
1258
|
-
const binding =
|
|
1293
|
+
const binding = params.bindingStore.read(sessionBindingIdentity({
|
|
1259
1294
|
sessionId,
|
|
1260
1295
|
sessionKey,
|
|
1261
1296
|
config: params.config
|
|
@@ -1278,54 +1313,26 @@ async function findAdoptedSessionEntry(params) {
|
|
|
1278
1313
|
const adopted = await listAdoptedSessionEntries(params);
|
|
1279
1314
|
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
1315
|
}
|
|
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
1316
|
function matchesPendingAdoptionBinding(binding, expected) {
|
|
1308
1317
|
const historyCoveredThrough = binding?.historyCoveredThrough;
|
|
1309
1318
|
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
1319
|
}
|
|
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
1320
|
async function ensurePendingAdoptionBinding(params) {
|
|
1318
1321
|
const pending = {
|
|
1319
1322
|
sourceThreadId: params.sourceThreadId,
|
|
1320
1323
|
connectionFingerprint: params.connectionFingerprint,
|
|
1321
1324
|
...params.lastTurnId ? { lastTurnId: params.lastTurnId } : {}
|
|
1322
1325
|
};
|
|
1323
|
-
|
|
1326
|
+
const ownsGeneration = await reclaimCurrentCodexSessionGeneration({
|
|
1327
|
+
assertCurrent: params.initialization.assertCurrent,
|
|
1324
1328
|
bindingStore: params.bindingStore,
|
|
1325
1329
|
identity: params.identity,
|
|
1326
1330
|
config: params.config
|
|
1327
|
-
})
|
|
1328
|
-
|
|
1331
|
+
});
|
|
1332
|
+
params.initialization.assertCurrent();
|
|
1333
|
+
if (!ownsGeneration) throw new Error(`failed to claim the OpenClaw session generation for ${params.sourceThreadId}`);
|
|
1334
|
+
const existing = params.bindingStore.read(params.identity);
|
|
1335
|
+
params.initialization.assertCurrent();
|
|
1329
1336
|
if (existing) {
|
|
1330
1337
|
if (matchesPendingAdoptionBinding(existing, params)) return;
|
|
1331
1338
|
throw new Error(`OpenClaw session is already bound to Codex thread ${existing.threadId}`);
|
|
@@ -1340,19 +1347,7 @@ async function ensurePendingAdoptionBinding(params) {
|
|
|
1340
1347
|
preserveNativeModel: true,
|
|
1341
1348
|
pendingSupervisionBranch: pending
|
|
1342
1349
|
};
|
|
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}`);
|
|
1350
|
+
await params.initialization.bind(binding);
|
|
1356
1351
|
}
|
|
1357
1352
|
async function createOrReuseAdoptedSession(params) {
|
|
1358
1353
|
const runtime = params.api.runtime;
|
|
@@ -1363,8 +1358,6 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1363
1358
|
};
|
|
1364
1359
|
const existing = await findAdoptedSessionEntry(lookup);
|
|
1365
1360
|
if (existing) return existing;
|
|
1366
|
-
let createdBindingIdentity;
|
|
1367
|
-
let createdPendingBinding;
|
|
1368
1361
|
try {
|
|
1369
1362
|
const spawnedCwd = params.sourceThread.cwd?.trim() || void 0;
|
|
1370
1363
|
const pendingLastTurnId = codexLastTerminalTurnId(params.sourceThread, boundCatalogSessionId);
|
|
@@ -1374,9 +1367,11 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1374
1367
|
};
|
|
1375
1368
|
const created = await createImportedCodexSession({
|
|
1376
1369
|
runtime: params.api.runtime,
|
|
1370
|
+
bindingStore: params.bindingStore,
|
|
1377
1371
|
config: params.config,
|
|
1378
1372
|
key: adoptionSessionKey(params.sourceThread.id, params.sourceHomeId),
|
|
1379
1373
|
agentId: params.agentId,
|
|
1374
|
+
displayName: params.sourceThread.name ?? void 0,
|
|
1380
1375
|
thread: params.sourceThread,
|
|
1381
1376
|
throughTurnId: pendingLastTurnId ?? null,
|
|
1382
1377
|
recoverMatchingInitialEntry: true,
|
|
@@ -1389,21 +1384,17 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1389
1384
|
modelLocked: true
|
|
1390
1385
|
} } }
|
|
1391
1386
|
},
|
|
1392
|
-
afterImport: async (entry) => {
|
|
1393
|
-
|
|
1387
|
+
afterImport: async (entry, initialization) => {
|
|
1388
|
+
const identity = sessionBindingIdentity({
|
|
1394
1389
|
sessionId: entry.sessionId,
|
|
1395
1390
|
sessionKey: entry.key,
|
|
1396
1391
|
config: params.config
|
|
1397
1392
|
});
|
|
1398
|
-
createdPendingBinding = {
|
|
1399
|
-
sourceThreadId: params.sourceThread.id,
|
|
1400
|
-
connectionFingerprint: params.connectionFingerprint,
|
|
1401
|
-
...pendingLastTurnId ? { lastTurnId: pendingLastTurnId } : {}
|
|
1402
|
-
};
|
|
1403
1393
|
await ensurePendingAdoptionBinding({
|
|
1404
1394
|
bindingStore: params.bindingStore,
|
|
1405
1395
|
config: params.config,
|
|
1406
|
-
identity
|
|
1396
|
+
identity,
|
|
1397
|
+
initialization,
|
|
1407
1398
|
sourceThreadId: params.sourceThread.id,
|
|
1408
1399
|
connectionFingerprint: params.connectionFingerprint,
|
|
1409
1400
|
cwd: spawnedCwd ?? "",
|
|
@@ -1422,24 +1413,13 @@ async function createOrReuseAdoptedSession(params) {
|
|
|
1422
1413
|
boundThreadId: params.sourceThread.id
|
|
1423
1414
|
};
|
|
1424
1415
|
} catch (error) {
|
|
1425
|
-
|
|
1416
|
+
const raced = await findAdoptedSessionEntry(lookup);
|
|
1426
1417
|
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
1418
|
throw error;
|
|
1439
1419
|
}
|
|
1440
1420
|
}
|
|
1441
1421
|
async function continueLocalCodexSessionInner(params) {
|
|
1442
|
-
await
|
|
1422
|
+
await params.control.requireEligibleThread(params.threadId);
|
|
1443
1423
|
const existing = await findAdoptedSessionEntry({
|
|
1444
1424
|
...params,
|
|
1445
1425
|
runtime: params.api.runtime
|
|
@@ -1455,7 +1435,11 @@ async function continueLocalCodexSessionInner(params) {
|
|
|
1455
1435
|
preserveActivity: true,
|
|
1456
1436
|
update: (entry) => {
|
|
1457
1437
|
if (entry.sessionId?.trim() !== existing.sessionId || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true) throw changedError();
|
|
1458
|
-
return {
|
|
1438
|
+
return {
|
|
1439
|
+
archivedAt: void 0,
|
|
1440
|
+
archivedBy: void 0,
|
|
1441
|
+
archiveReason: void 0
|
|
1442
|
+
};
|
|
1459
1443
|
}
|
|
1460
1444
|
})) throw changedError();
|
|
1461
1445
|
const connectionFingerprint = params.control.connectionFingerprint;
|
|
@@ -1510,8 +1494,8 @@ async function continueLocalCodexSession(params) {
|
|
|
1510
1494
|
}
|
|
1511
1495
|
//#endregion
|
|
1512
1496
|
//#region extensions/codex/src/session-catalog-archive.ts
|
|
1513
|
-
|
|
1514
|
-
const adoptedEntries = [params.agentId, ...listAgentIds(params.config).filter((agentId) => agentId !== params.agentId)].flatMap((agentId) => params.runtime.agent.session.listSessionEntries({
|
|
1497
|
+
function assertNoPendingSupervisionBranch(params) {
|
|
1498
|
+
const adoptedEntries = [params.agentId, ...listAgentIds$1(params.config).filter((agentId) => agentId !== params.agentId)].flatMap((agentId) => params.runtime.agent.session.listSessionEntries({
|
|
1515
1499
|
agentId,
|
|
1516
1500
|
readOnly: true
|
|
1517
1501
|
})).filter((candidate) => isAdoptionSessionKeyForThread(candidate.sessionKey, params.threadId, params.sourceHomeId) || params.sourceHomeId !== void 0 && params.allowLegacy === true && isAdoptionSessionKeyForThread(candidate.sessionKey, params.threadId));
|
|
@@ -1519,7 +1503,7 @@ async function assertNoPendingSupervisionBranch(params) {
|
|
|
1519
1503
|
if (adopted.entry.initializationPending === true) throw new CatalogParamsError("Codex session cannot be archived while its OpenClaw branch is initializing");
|
|
1520
1504
|
const sessionId = adopted.entry.sessionId?.trim();
|
|
1521
1505
|
if (!sessionId) continue;
|
|
1522
|
-
const binding =
|
|
1506
|
+
const binding = params.bindingStore.read(sessionBindingIdentity({
|
|
1523
1507
|
sessionId,
|
|
1524
1508
|
sessionKey: adopted.sessionKey,
|
|
1525
1509
|
config: params.config
|
|
@@ -1532,8 +1516,8 @@ async function archiveLocalCodexSession(params) {
|
|
|
1532
1516
|
return await runSessionActionExclusive(sessionCatalogAdoptedSourceKey(params.hostId ?? "gateway:local", params.threadId), async () => {
|
|
1533
1517
|
return await params.bindingStore.withThreadArchiveFence(async () => {
|
|
1534
1518
|
const run = async (control) => {
|
|
1535
|
-
|
|
1536
|
-
await
|
|
1519
|
+
assertNoPendingSupervisionBranch(params);
|
|
1520
|
+
await control.requireEligibleThread(params.threadId);
|
|
1537
1521
|
const thread = await control.readThread(params.threadId, false);
|
|
1538
1522
|
if (thread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1539
1523
|
requireIdleThread(thread, "archive");
|
|
@@ -1561,7 +1545,7 @@ const CODEX_AGENT_RUNTIME_ID = "codex";
|
|
|
1561
1545
|
const CODEX_CATALOG_DEFAULT_MODEL_REF = "openai/gpt-5.6-sol";
|
|
1562
1546
|
function resolveCodexCatalogCreateSession(config, requestedAgentId) {
|
|
1563
1547
|
if (!config) return;
|
|
1564
|
-
const agentId = requestedAgentId ?? resolveDefaultAgentId(config);
|
|
1548
|
+
const agentId = requestedAgentId ?? resolveDefaultAgentId$1(config);
|
|
1565
1549
|
const defaultModel = resolveDefaultModelForAgent({
|
|
1566
1550
|
cfg: config,
|
|
1567
1551
|
agentId
|
|
@@ -1607,7 +1591,8 @@ async function listPairedNode(params) {
|
|
|
1607
1591
|
label: nodeLabel(params.node),
|
|
1608
1592
|
kind: "node",
|
|
1609
1593
|
nodeId: params.node.nodeId,
|
|
1610
|
-
canContinueCodex: canContinueCodexOnNode(params.node)
|
|
1594
|
+
canContinueCodex: canContinueCodexOnNode(params.node),
|
|
1595
|
+
...params.terminalCapabilities
|
|
1611
1596
|
};
|
|
1612
1597
|
if (params.node.connected !== true) {
|
|
1613
1598
|
const host = {
|
|
@@ -1622,6 +1607,15 @@ async function listPairedNode(params) {
|
|
|
1622
1607
|
params.onHost?.(host);
|
|
1623
1608
|
return host;
|
|
1624
1609
|
}
|
|
1610
|
+
if (!params.node.commands?.includes("codex.appServer.threads.list.v1")) {
|
|
1611
|
+
const host = {
|
|
1612
|
+
...common,
|
|
1613
|
+
connected: true,
|
|
1614
|
+
sessions: []
|
|
1615
|
+
};
|
|
1616
|
+
params.onHost?.(host);
|
|
1617
|
+
return host;
|
|
1618
|
+
}
|
|
1625
1619
|
const eventualHost = Promise.resolve().then(async () => {
|
|
1626
1620
|
const page = filterCatalogPageByTitle(parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1627
1621
|
nodeId: params.node.nodeId,
|
|
@@ -1633,7 +1627,8 @@ async function listPairedNode(params) {
|
|
|
1633
1627
|
searchTerm: params.query.search
|
|
1634
1628
|
},
|
|
1635
1629
|
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1636
|
-
scopes: ["operator.write"]
|
|
1630
|
+
scopes: ["operator.write"],
|
|
1631
|
+
signal: params.signal
|
|
1637
1632
|
}))), params.query.search);
|
|
1638
1633
|
return {
|
|
1639
1634
|
...common,
|
|
@@ -1650,7 +1645,7 @@ async function listPairedNode(params) {
|
|
|
1650
1645
|
sessions: [],
|
|
1651
1646
|
error: catalogError("NODE_INVOKE_FAILED", error)
|
|
1652
1647
|
}));
|
|
1653
|
-
|
|
1648
|
+
publishSessionCatalogHost(params, eventualHost);
|
|
1654
1649
|
try {
|
|
1655
1650
|
return await withTimeout(eventualHost, NODE_CATALOG_LIST_RESPONSE_TIMEOUT_MS, "paired node Codex session catalog timed out");
|
|
1656
1651
|
} catch (error) {
|
|
@@ -1800,7 +1795,7 @@ async function continueNodeCodexSession(params) {
|
|
|
1800
1795
|
if (params.clientScopes?.includes("operator.admin") !== true) throw new CatalogParamsError("continuing a paired-node Codex session requires operator.admin");
|
|
1801
1796
|
const nodeId = params.hostId.slice(5).trim();
|
|
1802
1797
|
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1803
|
-
const agentId =
|
|
1798
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
1804
1799
|
config: params.config,
|
|
1805
1800
|
agentId: params.agentId
|
|
1806
1801
|
}).sessionAgentId;
|
|
@@ -1817,15 +1812,342 @@ async function continueNodeCodexSession(params) {
|
|
|
1817
1812
|
});
|
|
1818
1813
|
}
|
|
1819
1814
|
//#endregion
|
|
1820
|
-
//#region extensions/codex/src/session-catalog-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1815
|
+
//#region extensions/codex/src/session-catalog-terminal.ts
|
|
1816
|
+
const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1";
|
|
1817
|
+
const CODEX_TERMINAL_START_COMMAND = "codex.terminal.start.v1";
|
|
1818
|
+
function createCodexTerminalStartNodeHostCommand() {
|
|
1819
|
+
return {
|
|
1820
|
+
command: CODEX_TERMINAL_START_COMMAND,
|
|
1821
|
+
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1822
|
+
dangerous: false,
|
|
1823
|
+
duplex: true,
|
|
1824
|
+
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1825
|
+
env,
|
|
1826
|
+
strategy: "direct"
|
|
1827
|
+
})),
|
|
1828
|
+
handle: async (paramsJSON, io) => {
|
|
1829
|
+
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1830
|
+
const params = decodeNodePtyStartParams(paramsJSON);
|
|
1831
|
+
const resolution = resolveNodeHostExecutable("codex", { strategy: "direct" });
|
|
1832
|
+
if (!resolution) throw new Error("Codex CLI is unavailable; install codex on this node and reconnect");
|
|
1833
|
+
return JSON.stringify(await runNodePtyCommand({
|
|
1834
|
+
file: resolution.executable,
|
|
1835
|
+
args: params.initialMessage !== void 0 ? ["--", params.initialMessage] : [],
|
|
1836
|
+
cwd: params.cwd,
|
|
1837
|
+
requiredCwd: true,
|
|
1838
|
+
cols: params.cols,
|
|
1839
|
+
rows: params.rows
|
|
1840
|
+
}, io));
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
function resolveCodexCatalogTerminalHome(sources) {
|
|
1845
|
+
const runtimeConfig = sources.getRuntimeConfig();
|
|
1846
|
+
if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
|
|
1847
|
+
const agentDir = sources.source?.agentDir ?? (sources.agentId ? resolveAgentDir$2(runtimeConfig, sources.agentId) : resolveDefaultAgentDir(runtimeConfig));
|
|
1848
|
+
const startOptions = sources.source?.appServer.start ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig: sources.getPluginConfig() }).start;
|
|
1849
|
+
return resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
|
|
1850
|
+
}
|
|
1851
|
+
function resolveLocalCodexTerminalExecutable(env = process.env) {
|
|
1852
|
+
return resolveLocalCodexTerminalResolution(env)?.executable;
|
|
1853
|
+
}
|
|
1854
|
+
function resolveLocalCodexTerminalResolution(env = process.env) {
|
|
1855
|
+
return resolveNodeHostExecutable("codex", {
|
|
1856
|
+
env,
|
|
1857
|
+
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1858
|
+
strategy: "fallback"
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
function codexNodeTerminalCapability(node) {
|
|
1862
|
+
const commands = node.invocableCommands ?? node.commands;
|
|
1863
|
+
return {
|
|
1864
|
+
canOpenTerminalCodex: node.connected === true && commands?.includes("codex.terminal.resume.v1") === true,
|
|
1865
|
+
canStartTerminal: node.connected === true && node.invocableCommands?.includes("codex.terminal.start.v1") === true
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
|
|
1869
|
+
return {
|
|
1870
|
+
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1871
|
+
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1872
|
+
dangerous: false,
|
|
1873
|
+
duplex: true,
|
|
1874
|
+
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1875
|
+
env,
|
|
1876
|
+
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1877
|
+
strategy: "direct"
|
|
1878
|
+
})),
|
|
1879
|
+
handle: async (paramsJSON, io) => {
|
|
1880
|
+
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1881
|
+
const request = bindRequest(paramsJSON);
|
|
1882
|
+
const resume = decodeNodePtyResumeParams(request.paramsJSON, (value) => {
|
|
1883
|
+
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");
|
|
1884
|
+
return value;
|
|
1885
|
+
});
|
|
1886
|
+
const record = await request.control.requireEligibleThread(resume.threadId);
|
|
1887
|
+
const resolution = resolveNodeHostExecutable("codex", {
|
|
1888
|
+
env: process.env,
|
|
1889
|
+
pathEnv: process.env.PATH ?? process.env.Path ?? "",
|
|
1890
|
+
strategy: "direct"
|
|
1891
|
+
});
|
|
1892
|
+
if (!resolution) throw new Error("Codex CLI is unavailable");
|
|
1893
|
+
return JSON.stringify(await runNodePtyCommand({
|
|
1894
|
+
file: resolution.executable,
|
|
1895
|
+
args: ["resume", resume.threadId],
|
|
1896
|
+
...record.cwd ? { cwd: record.cwd } : {},
|
|
1897
|
+
env: { CODEX_HOME: resolveCodexCatalogTerminalHome({
|
|
1898
|
+
...configSources,
|
|
1899
|
+
agentId: request.agentId
|
|
1900
|
+
}) },
|
|
1901
|
+
cols: resume.cols,
|
|
1902
|
+
rows: resume.rows
|
|
1903
|
+
}, io));
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
async function resolveNodeCatalogEligibleThread(params) {
|
|
1908
|
+
let cursor;
|
|
1909
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
1910
|
+
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1911
|
+
const raw = await params.runtime.nodes.invoke({
|
|
1912
|
+
nodeId: params.nodeId,
|
|
1913
|
+
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1914
|
+
params: {
|
|
1915
|
+
agentId: params.agentId,
|
|
1916
|
+
limit: 100,
|
|
1917
|
+
...cursor ? { cursor } : {}
|
|
1918
|
+
},
|
|
1919
|
+
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1920
|
+
scopes: ["operator.write"]
|
|
1921
|
+
});
|
|
1922
|
+
const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
|
|
1923
|
+
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1924
|
+
if (record) {
|
|
1925
|
+
if (isInteractiveThreadSource(record.source)) return record;
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
const nextCursor = page.nextCursor?.trim();
|
|
1929
|
+
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
1930
|
+
seenCursors.add(nextCursor);
|
|
1931
|
+
cursor = nextCursor;
|
|
1932
|
+
}
|
|
1933
|
+
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1934
|
+
}
|
|
1935
|
+
async function openCodexCatalogTerminal(params) {
|
|
1936
|
+
const title = `codex resume ${params.threadId.slice(0, 8)}…`;
|
|
1937
|
+
if (params.hostId === "gateway:local" || params.hostId.startsWith(`gateway:local:`)) {
|
|
1938
|
+
const record = await params.control.requireEligibleThread(params.threadId);
|
|
1939
|
+
const resolution = resolveLocalCodexTerminalResolution();
|
|
1940
|
+
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable");
|
|
1941
|
+
return {
|
|
1942
|
+
kind: "local",
|
|
1943
|
+
argv: [
|
|
1944
|
+
resolution.executable,
|
|
1945
|
+
"resume",
|
|
1946
|
+
params.threadId
|
|
1947
|
+
],
|
|
1948
|
+
...record.cwd ? { cwd: record.cwd } : {},
|
|
1949
|
+
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1950
|
+
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1951
|
+
title
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
if (!params.hostId.startsWith("node:")) throw new CatalogParamsError("hostId is invalid");
|
|
1955
|
+
const nodeId = params.hostId.slice(5);
|
|
1956
|
+
if (!(await params.api.runtime.nodes.list()).nodes.find((candidate) => {
|
|
1957
|
+
const commands = candidate.invocableCommands ?? candidate.commands;
|
|
1958
|
+
return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("codex.appServer.threads.list.v1") === true && commands.includes("codex.terminal.resume.v1");
|
|
1959
|
+
})) throw new CatalogParamsError("paired-node Codex terminal is unavailable");
|
|
1960
|
+
const record = await resolveNodeCatalogEligibleThread({
|
|
1961
|
+
agentId: params.agentId,
|
|
1962
|
+
runtime: params.api.runtime,
|
|
1963
|
+
nodeId,
|
|
1964
|
+
threadId: params.threadId,
|
|
1965
|
+
parseCatalogPage: params.parseCatalogPage
|
|
1966
|
+
});
|
|
1967
|
+
return {
|
|
1968
|
+
kind: "node",
|
|
1969
|
+
nodeId,
|
|
1970
|
+
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1971
|
+
paramsJSON: JSON.stringify({
|
|
1972
|
+
agentId: params.agentId,
|
|
1973
|
+
threadId: params.threadId
|
|
1974
|
+
}),
|
|
1975
|
+
...record.cwd ? { cwd: record.cwd } : {},
|
|
1976
|
+
title
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
async function startCodexCatalogTerminal(params) {
|
|
1980
|
+
if (params.nodeId) return {
|
|
1981
|
+
kind: "node",
|
|
1982
|
+
nodeId: params.nodeId,
|
|
1983
|
+
command: CODEX_TERMINAL_START_COMMAND,
|
|
1984
|
+
paramsJSON: JSON.stringify({
|
|
1985
|
+
cwd: params.cwd,
|
|
1986
|
+
initialMessage: params.initialMessage
|
|
1987
|
+
}),
|
|
1988
|
+
cwd: params.cwd,
|
|
1989
|
+
title: "codex"
|
|
1990
|
+
};
|
|
1991
|
+
const resolution = resolveLocalCodexTerminalResolution();
|
|
1992
|
+
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable; install Codex or add codex to PATH, then try again");
|
|
1993
|
+
return {
|
|
1994
|
+
kind: "local",
|
|
1995
|
+
argv: [resolution.executable, ...params.initialMessage !== void 0 ? ["--", params.initialMessage] : []],
|
|
1996
|
+
cwd: params.cwd,
|
|
1997
|
+
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1998
|
+
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1999
|
+
title: "codex"
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
//#endregion
|
|
2003
|
+
//#region extensions/codex/src/session-catalog-transcript-item.ts
|
|
2004
|
+
const CODEX_MESSAGE_TYPES = /* @__PURE__ */ new Map([
|
|
2005
|
+
["userMessage", "userMessage"],
|
|
2006
|
+
["agentMessage", "agentMessage"],
|
|
2007
|
+
["reasoning", "reasoning"]
|
|
2008
|
+
]);
|
|
2009
|
+
const CODEX_TOOL_TYPES = /* @__PURE__ */ new Set([
|
|
2010
|
+
"commandExecution",
|
|
2011
|
+
"fileChange",
|
|
2012
|
+
"mcpToolCall",
|
|
2013
|
+
"dynamicToolCall",
|
|
2014
|
+
"collabAgentToolCall",
|
|
2015
|
+
"webSearch",
|
|
2016
|
+
"imageView",
|
|
2017
|
+
"imageGeneration"
|
|
2018
|
+
]);
|
|
2019
|
+
function toGenericTranscriptItem(item) {
|
|
2020
|
+
let type = CODEX_MESSAGE_TYPES.get(item.type);
|
|
2021
|
+
if (!type && CODEX_TOOL_TYPES.has(item.type)) type = item.result !== void 0 || Boolean(item.aggregatedOutput) ? "toolResult" : "toolCall";
|
|
2022
|
+
type ??= "other";
|
|
2023
|
+
const fallback = item.title ?? item.name ?? item.tool ?? item.command ?? item.query ?? void 0;
|
|
2024
|
+
const resultText = item.aggregatedOutput || (item.result === void 0 ? void 0 : JSON.stringify(item.result, null, 2));
|
|
2025
|
+
const changesText = Array.isArray(item.changes) ? item.changes.map((change) => `${change.kind}: ${change.path}`).join("\n") || void 0 : void 0;
|
|
2026
|
+
const text = item.type === "userMessage" ? projectCodexUserItemText(item) : item.text || resultText || changesText || fallback;
|
|
2027
|
+
return {
|
|
2028
|
+
id: item.id,
|
|
2029
|
+
type,
|
|
2030
|
+
...text ? { text } : {},
|
|
2031
|
+
raw: item
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
//#endregion
|
|
2035
|
+
//#region extensions/codex/src/session-catalog-transcript.ts
|
|
2036
|
+
const TURN_ITEM_CURSOR_PREFIX = "turn-item:";
|
|
2037
|
+
const transcriptPageSchema = z.strictObject({
|
|
2038
|
+
items: z.array(z.strictObject({
|
|
2039
|
+
id: z.string(),
|
|
2040
|
+
type: z.enum([
|
|
2041
|
+
"userMessage",
|
|
2042
|
+
"agentMessage",
|
|
2043
|
+
"reasoning",
|
|
2044
|
+
"toolCall",
|
|
2045
|
+
"toolResult",
|
|
2046
|
+
"other"
|
|
2047
|
+
]),
|
|
2048
|
+
text: z.string().optional(),
|
|
2049
|
+
raw: z.record(z.string(), z.json()).optional(),
|
|
2050
|
+
truncated: z.boolean().optional()
|
|
2051
|
+
})),
|
|
2052
|
+
nextCursor: z.string().optional()
|
|
2053
|
+
});
|
|
2054
|
+
function parseCodexCatalogTranscriptPage(value) {
|
|
2055
|
+
return transcriptPageSchema.parse(value);
|
|
2056
|
+
}
|
|
2057
|
+
function encodeTurnItemCursor(turnCursor, itemId) {
|
|
2058
|
+
return TURN_ITEM_CURSOR_PREFIX + Buffer.from(JSON.stringify([turnCursor, itemId])).toString("base64url");
|
|
2059
|
+
}
|
|
2060
|
+
function decodeTurnItemCursor(cursor) {
|
|
2061
|
+
if (!cursor?.startsWith(TURN_ITEM_CURSOR_PREFIX)) return { turnCursor: cursor };
|
|
2062
|
+
let value;
|
|
2063
|
+
try {
|
|
2064
|
+
value = JSON.parse(Buffer.from(cursor.slice(10), "base64url").toString());
|
|
2065
|
+
} catch {
|
|
2066
|
+
throw new CatalogParamsError("invalid Codex transcript item cursor");
|
|
2067
|
+
}
|
|
2068
|
+
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "string" || !value[0] || typeof value[1] !== "string" || !value[1]) throw new CatalogParamsError("invalid Codex transcript item cursor");
|
|
2069
|
+
return {
|
|
2070
|
+
turnCursor: value[0],
|
|
2071
|
+
itemId: value[1]
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
function projectTranscriptPage(items, limit) {
|
|
2075
|
+
const projected = items.map(toGenericTranscriptItem);
|
|
2076
|
+
const page = sessionCatalogPaging.boundTranscriptPage(projected.toReversed(), limit, 0).items;
|
|
2077
|
+
for (const [index, item] of page.entries()) if (item.text !== projected[index]?.text && projected[index]?.text) item.truncated = true;
|
|
2078
|
+
return page;
|
|
2079
|
+
}
|
|
2080
|
+
function pageFitsNodeTransport(page) {
|
|
2081
|
+
return Buffer.byteLength(JSON.stringify({ payloadJSON: JSON.stringify(page) }), "utf8") <= MAX_TRANSCRIPT_PAGE_BYTES;
|
|
2082
|
+
}
|
|
2083
|
+
/** The legacy API can anchor a turn, but cannot continue within that turn. */
|
|
2084
|
+
async function readLegacyCodexTranscriptPage(readTurns, request) {
|
|
2085
|
+
const { turnCursor, itemId } = decodeTurnItemCursor(request.cursor);
|
|
2086
|
+
const page = parseTranscriptPage(await readTurns({
|
|
2087
|
+
threadId: request.threadId,
|
|
2088
|
+
limit: 1,
|
|
2089
|
+
sortDirection: "desc",
|
|
2090
|
+
itemsView: "full",
|
|
2091
|
+
...turnCursor ? { cursor: turnCursor } : {}
|
|
2092
|
+
}));
|
|
2093
|
+
const source = page.data.flatMap((turn) => turn.items.toReversed());
|
|
2094
|
+
const anchorIndex = itemId ? source.findIndex((item) => item.id === itemId) : -1;
|
|
2095
|
+
if (itemId && anchorIndex < 0) throw new CatalogParamsError("Codex transcript changed; refresh the session before loading older items");
|
|
2096
|
+
const remaining = source.slice(anchorIndex + 1);
|
|
2097
|
+
const items = projectTranscriptPage(remaining, request.limit);
|
|
2098
|
+
const result = () => {
|
|
2099
|
+
const lastSource = remaining[items.length - 1];
|
|
2100
|
+
const partial = lastSource !== void 0 && items.length < remaining.length;
|
|
2101
|
+
const nativeCursor = partial ? page.backwardsCursor : page.nextCursor;
|
|
2102
|
+
const cursor = nativeCursor ? partial ? encodeTurnItemCursor(nativeCursor, lastSource.id) : nativeCursor : void 0;
|
|
2103
|
+
if (partial && !cursor) throw new Error("Codex app-server did not provide a transcript continuation anchor");
|
|
2104
|
+
return {
|
|
2105
|
+
items,
|
|
2106
|
+
...cursor ? { nextCursor: cursor } : {}
|
|
2107
|
+
};
|
|
2108
|
+
};
|
|
2109
|
+
let bounded = result();
|
|
2110
|
+
while (!pageFitsNodeTransport(bounded)) {
|
|
2111
|
+
if (items.length <= 1) throw new Error("Codex transcript item exceeds the safe response size");
|
|
2112
|
+
items.pop();
|
|
2113
|
+
bounded = result();
|
|
2114
|
+
}
|
|
2115
|
+
return bounded;
|
|
2116
|
+
}
|
|
2117
|
+
/** Uses the native store's item cursor whenever that store supports item history. */
|
|
2118
|
+
async function readCodexCatalogTranscriptPage(control, request) {
|
|
2119
|
+
if ((await control.requireEligibleThread(request.threadId)).historyMode !== "paginated") return readLegacyCodexTranscriptPage((params) => control.listTurnPage(params), request);
|
|
2120
|
+
let limit = request.limit;
|
|
2121
|
+
for (;;) {
|
|
2122
|
+
const page = await control.listItemPage({
|
|
2123
|
+
threadId: request.threadId,
|
|
2124
|
+
limit,
|
|
2125
|
+
sortDirection: "desc",
|
|
2126
|
+
...request.cursor ? { cursor: request.cursor } : {}
|
|
2127
|
+
});
|
|
2128
|
+
const nextCursor = readControlCursor(page.nextCursor, "transcript next response");
|
|
2129
|
+
const items = projectTranscriptPage(page.data.map(({ item }) => item), limit);
|
|
2130
|
+
const result = {
|
|
2131
|
+
items,
|
|
2132
|
+
...nextCursor ? { nextCursor } : {}
|
|
2133
|
+
};
|
|
2134
|
+
const fittingCount = items.length - (pageFitsNodeTransport(result) ? 0 : 1);
|
|
2135
|
+
if (fittingCount === page.data.length) return result;
|
|
2136
|
+
if (fittingCount < 1) throw new Error("Codex transcript item exceeds the safe response size");
|
|
2137
|
+
limit = fittingCount;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
//#endregion
|
|
2141
|
+
//#region extensions/codex/src/session-catalog-listing.ts
|
|
2142
|
+
async function listVisiblePage(params) {
|
|
2143
|
+
const excluded = params.excludedThreadIds;
|
|
2144
|
+
const sessions = [];
|
|
2145
|
+
let cursor = params.cursor;
|
|
2146
|
+
let nextCursor;
|
|
2147
|
+
let backwardsCursor;
|
|
1827
2148
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
1828
2149
|
for (let pageIndex = 0; pageIndex < 20; pageIndex += 1) {
|
|
2150
|
+
params.signal?.throwIfAborted();
|
|
1829
2151
|
let excludedFromPage = false;
|
|
1830
2152
|
const rawPage = await params.control.listPage({
|
|
1831
2153
|
limit: params.limit - sessions.length,
|
|
@@ -1833,10 +2155,12 @@ async function listVisiblePage(params) {
|
|
|
1833
2155
|
...params.searchTerm ? { searchTerm: params.searchTerm } : {},
|
|
1834
2156
|
...params.cwd ? { cwd: params.cwd } : {}
|
|
1835
2157
|
});
|
|
2158
|
+
params.signal?.throwIfAborted();
|
|
1836
2159
|
const page = filterCatalogPageByTitle(parseCatalogPage(rawPage), params.searchTerm);
|
|
1837
2160
|
if (pageIndex === 0) backwardsCursor = page.backwardsCursor;
|
|
1838
2161
|
for (const managed of rawPage.managedThreads ?? []) {
|
|
1839
2162
|
excludedFromPage = true;
|
|
2163
|
+
params.signal?.throwIfAborted();
|
|
1840
2164
|
await params.onExcludedThread?.(managed);
|
|
1841
2165
|
}
|
|
1842
2166
|
for (const session of page.sessions) {
|
|
@@ -1845,6 +2169,7 @@ async function listVisiblePage(params) {
|
|
|
1845
2169
|
continue;
|
|
1846
2170
|
}
|
|
1847
2171
|
excludedFromPage = true;
|
|
2172
|
+
params.signal?.throwIfAborted();
|
|
1848
2173
|
await params.onExcludedThread?.({ threadId: session.threadId });
|
|
1849
2174
|
}
|
|
1850
2175
|
nextCursor = page.nextCursor;
|
|
@@ -1870,8 +2195,10 @@ async function listGatewayHost(params) {
|
|
|
1870
2195
|
excludedThreadIds: params.excludedThreadIds,
|
|
1871
2196
|
limit: params.query.limitPerHost,
|
|
1872
2197
|
onExcludedThread: params.onExcludedThread,
|
|
1873
|
-
searchTerm: params.query.search
|
|
2198
|
+
searchTerm: params.query.search,
|
|
2199
|
+
signal: params.signal
|
|
1874
2200
|
});
|
|
2201
|
+
params.signal?.throwIfAborted();
|
|
1875
2202
|
const adoptedSessions = await listAdoptedSessionEntries({
|
|
1876
2203
|
agentId: params.agentId,
|
|
1877
2204
|
bindingStore: params.bindingStore,
|
|
@@ -1904,7 +2231,7 @@ async function listGatewayHost(params) {
|
|
|
1904
2231
|
}
|
|
1905
2232
|
/** Lists Gateway-local and paired-node Codex sessions with per-host failures. */
|
|
1906
2233
|
async function listCodexSessionCatalog(params) {
|
|
1907
|
-
const agentId =
|
|
2234
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
1908
2235
|
config: params.config ?? {},
|
|
1909
2236
|
agentId: params.agentId
|
|
1910
2237
|
}).sessionAgentId;
|
|
@@ -1912,6 +2239,7 @@ async function listCodexSessionCatalog(params) {
|
|
|
1912
2239
|
const requestedHostIds = query.hostIds ? new Set(query.hostIds) : void 0;
|
|
1913
2240
|
const localSources = params.localHomes?.filter((source) => !requestedHostIds || requestedHostIds.has(source.hostId)) ?? (params.includeLocal !== false && (!requestedHostIds || requestedHostIds.has("gateway:local")) ? [void 0] : []);
|
|
1914
2241
|
const managedThreads = await params.bindingStore.managedThreads?.snapshot();
|
|
2242
|
+
params.signal?.throwIfAborted();
|
|
1915
2243
|
const fallbackSource = params.control.homesForAgent(agentId)[0];
|
|
1916
2244
|
const localHosts = localSources.map((source) => (() => {
|
|
1917
2245
|
const ownershipSource = source ?? fallbackSource;
|
|
@@ -1924,6 +2252,7 @@ async function listCodexSessionCatalog(params) {
|
|
|
1924
2252
|
query,
|
|
1925
2253
|
runtime: params.runtime,
|
|
1926
2254
|
sessionEntries: params.sessionEntries,
|
|
2255
|
+
signal: params.signal,
|
|
1927
2256
|
excludedThreadIds: managedThreadIds,
|
|
1928
2257
|
...ownershipSource && params.bindingStore.managedThreads ? { onExcludedThread: async ({ threadId, rolloutPath }) => {
|
|
1929
2258
|
if (!managedThreadIds?.has(threadId)) await params.bindingStore.managedThreads?.mark({
|
|
@@ -1935,40 +2264,42 @@ async function listCodexSessionCatalog(params) {
|
|
|
1935
2264
|
...source ? { source } : {}
|
|
1936
2265
|
});
|
|
1937
2266
|
})());
|
|
1938
|
-
for (const host of localHosts)
|
|
2267
|
+
for (const host of localHosts) publishSessionCatalogHost(params, host);
|
|
1939
2268
|
if (!(!requestedHostIds || query.hostIds?.some((hostId) => hostId.startsWith("node:")))) return { hosts: await Promise.all(localHosts) };
|
|
1940
2269
|
let nodes;
|
|
1941
2270
|
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);
|
|
2271
|
+
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
2272
|
} catch (error) {
|
|
1944
2273
|
const registryHost = {
|
|
1945
2274
|
hostId: "node:registry",
|
|
1946
2275
|
label: "Paired nodes",
|
|
1947
2276
|
kind: "node",
|
|
1948
2277
|
connected: false,
|
|
2278
|
+
canStartTerminal: false,
|
|
1949
2279
|
sessions: [],
|
|
1950
2280
|
error: catalogError("NODE_LIST_FAILED", error)
|
|
1951
2281
|
};
|
|
1952
2282
|
params.onHost?.(registryHost);
|
|
1953
2283
|
return { hosts: [...await Promise.all(localHosts), registryHost] };
|
|
1954
2284
|
}
|
|
2285
|
+
params.signal?.throwIfAborted();
|
|
1955
2286
|
const adoptedNodeSessions = listNodeAdoptedSessionEntries({
|
|
1956
2287
|
agentId,
|
|
1957
2288
|
config: params.config,
|
|
1958
2289
|
runtime: params.runtime,
|
|
1959
2290
|
sessionEntries: params.sessionEntries
|
|
1960
2291
|
});
|
|
1961
|
-
const nodeHosts = nodes.toSorted(compareNodeLabels).map(
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
});
|
|
2292
|
+
const nodeHosts = nodes.toSorted(compareNodeLabels).map((node) => listPairedNode({
|
|
2293
|
+
agentId,
|
|
2294
|
+
runtime: params.runtime,
|
|
2295
|
+
node,
|
|
2296
|
+
query,
|
|
2297
|
+
adoptedSessions: adoptedNodeSessions,
|
|
2298
|
+
terminalCapabilities: codexNodeTerminalCapability(node),
|
|
2299
|
+
waitUntil: params.waitUntil,
|
|
2300
|
+
signal: params.signal,
|
|
2301
|
+
...params.onHost ? { onHost: params.onHost } : {}
|
|
2302
|
+
}));
|
|
1972
2303
|
return { hosts: await Promise.all([...localHosts, ...nodeHosts]) };
|
|
1973
2304
|
}
|
|
1974
2305
|
/** Builds the node-local read-only Codex app-server catalog command. */
|
|
@@ -1978,11 +2309,11 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
|
|
|
1978
2309
|
if (!isRecord(parsed)) throw new CatalogParamsError("Codex session catalog parameters must be an object");
|
|
1979
2310
|
const requestedAgentId = readBoundedOptionalString(parsed, "agentId", 256);
|
|
1980
2311
|
const config = configSources.getRuntimeConfig() ?? {};
|
|
1981
|
-
const agentId =
|
|
2312
|
+
const agentId = resolveSessionAgentIdsStrict({
|
|
1982
2313
|
config,
|
|
1983
2314
|
agentId: requestedAgentId
|
|
1984
2315
|
}).sessionAgentId;
|
|
1985
|
-
if (!listAgentIds(config).includes(agentId)) throw new CatalogParamsError(`unknown Codex session catalog agent: ${agentId}`);
|
|
2316
|
+
if (!listAgentIds$1(config).includes(agentId)) throw new CatalogParamsError(`unknown Codex session catalog agent: ${agentId}`);
|
|
1986
2317
|
const request = { ...parsed };
|
|
1987
2318
|
delete request.agentId;
|
|
1988
2319
|
const source = controlFactory.homesForAgent(agentId)[0];
|
|
@@ -1994,7 +2325,7 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
|
|
|
1994
2325
|
paramsJSON: JSON.stringify(request)
|
|
1995
2326
|
};
|
|
1996
2327
|
};
|
|
1997
|
-
|
|
2328
|
+
const commands = [
|
|
1998
2329
|
{
|
|
1999
2330
|
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
2000
2331
|
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
@@ -2035,7 +2366,7 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
|
|
|
2035
2366
|
const request = bindRequest(paramsJSON);
|
|
2036
2367
|
const action = readNodeTranscriptParams(request.params);
|
|
2037
2368
|
try {
|
|
2038
|
-
await
|
|
2369
|
+
await request.control.requireEligibleThread(action.threadId);
|
|
2039
2370
|
const page = parseTranscriptPage(await request.control.listTurnPage({
|
|
2040
2371
|
threadId: action.threadId,
|
|
2041
2372
|
limit: action.limit,
|
|
@@ -2050,8 +2381,25 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
|
|
|
2050
2381
|
}
|
|
2051
2382
|
}
|
|
2052
2383
|
},
|
|
2053
|
-
|
|
2384
|
+
{
|
|
2385
|
+
command: CODEX_CATALOG_TRANSCRIPT_READ_COMMAND,
|
|
2386
|
+
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
2387
|
+
dangerous: false,
|
|
2388
|
+
handle: async (paramsJSON) => {
|
|
2389
|
+
const request = bindRequest(paramsJSON);
|
|
2390
|
+
const action = readNodeTranscriptParams(request.params);
|
|
2391
|
+
try {
|
|
2392
|
+
return JSON.stringify(await readCodexCatalogTranscriptPage(request.control, action));
|
|
2393
|
+
} catch (error) {
|
|
2394
|
+
if (error instanceof CatalogParamsError) throw error;
|
|
2395
|
+
throw new Error("Codex app-server transcript is unavailable", { cause: error });
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
},
|
|
2399
|
+
createCodexTerminalNodeHostCommand(bindRequest, configSources),
|
|
2400
|
+
createCodexTerminalStartNodeHostCommand()
|
|
2054
2401
|
];
|
|
2402
|
+
return process.env.OPENCLAW_NODE_EXEC_HOST?.trim().toLowerCase() === "app" ? commands.filter(({ command }) => command !== CODEX_CATALOG_TRANSCRIPT_READ_COMMAND) : commands;
|
|
2055
2403
|
}
|
|
2056
2404
|
function readNodeTranscriptParams(value) {
|
|
2057
2405
|
if (!isRecord(value)) throw new CatalogParamsError("Codex session read parameters must be an object");
|
|
@@ -2074,94 +2422,58 @@ function readBoundedLimit(value, key, fallback, max) {
|
|
|
2074
2422
|
if (!Number.isInteger(value) || value < 1 || value > max) throw new CatalogParamsError(`${key} must be an integer from 1 to ${max}`);
|
|
2075
2423
|
return value;
|
|
2076
2424
|
}
|
|
2077
|
-
function flattenTranscriptPageDesc(page) {
|
|
2078
|
-
return page.data.flatMap((turn) => turn.items.toReversed());
|
|
2079
|
-
}
|
|
2080
2425
|
/** Reads the persisted transcript for a Gateway-local or paired-node Codex session. */
|
|
2081
2426
|
async function readCodexSessionTranscript(params) {
|
|
2427
|
+
const cursor = readControlCursor(params.cursor, "transcript request");
|
|
2428
|
+
const limit = readBoundedLimit(params.limit, "limit", 20, 50);
|
|
2082
2429
|
if (params.source || params.hostId === "gateway:local") {
|
|
2083
|
-
await
|
|
2084
|
-
const listParams = {
|
|
2430
|
+
const page = await readCodexCatalogTranscriptPage(params.control, {
|
|
2085
2431
|
threadId: params.threadId,
|
|
2086
|
-
limit
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
...params.cursor ? { cursor: params.cursor } : {}
|
|
2090
|
-
};
|
|
2091
|
-
const page = parseTranscriptPage(await params.control.listTurnPage(listParams));
|
|
2432
|
+
limit,
|
|
2433
|
+
cursor
|
|
2434
|
+
});
|
|
2092
2435
|
return {
|
|
2093
2436
|
hostId: params.hostId,
|
|
2094
2437
|
label: params.source?.label ?? "Local Codex",
|
|
2095
2438
|
threadId: params.threadId,
|
|
2096
|
-
|
|
2097
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {},
|
|
2098
|
-
...page.backwardsCursor ? { backwardsCursor: page.backwardsCursor } : {}
|
|
2439
|
+
...page
|
|
2099
2440
|
};
|
|
2100
2441
|
}
|
|
2101
2442
|
const nodeId = params.hostId.slice(5);
|
|
2102
2443
|
const node = (await params.runtime.nodes.list()).nodes.find((candidate) => candidate.nodeId === nodeId && candidate.connected === true && candidate.commands?.includes("codex.appServer.thread.turns.list.v1"));
|
|
2103
2444
|
if (!node) throw new CatalogParamsError("paired-node Codex session host is offline or unavailable");
|
|
2104
|
-
const
|
|
2445
|
+
const invoke = async (command, request) => unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
2105
2446
|
nodeId,
|
|
2106
|
-
command
|
|
2447
|
+
command,
|
|
2107
2448
|
params: {
|
|
2108
2449
|
agentId: params.agentId,
|
|
2109
2450
|
threadId: params.threadId,
|
|
2110
|
-
|
|
2111
|
-
...params.cursor ? { cursor: params.cursor } : {}
|
|
2451
|
+
...request
|
|
2112
2452
|
},
|
|
2113
2453
|
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
2114
2454
|
scopes: ["operator.write"]
|
|
2115
|
-
}))
|
|
2455
|
+
}));
|
|
2456
|
+
const page = node.commands?.includes("codex.sessionCatalog.transcript.read.v1") ? parseCodexCatalogTranscriptPage(await invoke(CODEX_CATALOG_TRANSCRIPT_READ_COMMAND, {
|
|
2457
|
+
cursor,
|
|
2458
|
+
limit
|
|
2459
|
+
})) : await readLegacyCodexTranscriptPage(async ({ cursor: turnCursor, limit: turnLimit }) => parseTranscriptPage(await invoke(CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND, {
|
|
2460
|
+
cursor: turnCursor,
|
|
2461
|
+
limit: turnLimit
|
|
2462
|
+
})), {
|
|
2463
|
+
threadId: params.threadId,
|
|
2464
|
+
cursor,
|
|
2465
|
+
limit
|
|
2466
|
+
});
|
|
2116
2467
|
return {
|
|
2117
2468
|
hostId: params.hostId,
|
|
2118
2469
|
label: nodeLabel(node),
|
|
2119
2470
|
threadId: params.threadId,
|
|
2120
|
-
|
|
2121
|
-
...page.nextCursor ? { nextCursor: page.nextCursor } : {},
|
|
2122
|
-
...page.backwardsCursor ? { backwardsCursor: page.backwardsCursor } : {}
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
//#endregion
|
|
2126
|
-
//#region extensions/codex/src/session-catalog-transcript-item.ts
|
|
2127
|
-
const CODEX_MESSAGE_TYPES = /* @__PURE__ */ new Map([
|
|
2128
|
-
["userMessage", "userMessage"],
|
|
2129
|
-
["agentMessage", "agentMessage"],
|
|
2130
|
-
["reasoning", "reasoning"]
|
|
2131
|
-
]);
|
|
2132
|
-
const CODEX_TOOL_TYPES = /* @__PURE__ */ new Set([
|
|
2133
|
-
"commandExecution",
|
|
2134
|
-
"fileChange",
|
|
2135
|
-
"mcpToolCall",
|
|
2136
|
-
"dynamicToolCall",
|
|
2137
|
-
"collabAgentToolCall",
|
|
2138
|
-
"webSearch",
|
|
2139
|
-
"imageView",
|
|
2140
|
-
"imageGeneration"
|
|
2141
|
-
]);
|
|
2142
|
-
function toGenericTranscriptItem(item) {
|
|
2143
|
-
let type = CODEX_MESSAGE_TYPES.get(item.type);
|
|
2144
|
-
if (!type && CODEX_TOOL_TYPES.has(item.type)) type = item.result !== void 0 || Boolean(item.aggregatedOutput) ? "toolResult" : "toolCall";
|
|
2145
|
-
type ??= "other";
|
|
2146
|
-
const fallback = item.title ?? item.name ?? item.tool ?? item.command ?? item.query ?? void 0;
|
|
2147
|
-
const resultText = item.aggregatedOutput || (item.result === void 0 ? void 0 : JSON.stringify(item.result, null, 2));
|
|
2148
|
-
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;
|
|
2150
|
-
return {
|
|
2151
|
-
id: item.id,
|
|
2152
|
-
type,
|
|
2153
|
-
...text ? { text } : {},
|
|
2154
|
-
raw: item
|
|
2471
|
+
...page
|
|
2155
2472
|
};
|
|
2156
2473
|
}
|
|
2157
2474
|
//#endregion
|
|
2158
2475
|
//#region extensions/codex/src/session-upstream-activity.ts
|
|
2159
2476
|
const CODEX_UPSTREAM_TURN_LIMIT = 100;
|
|
2160
|
-
const CODEX_APP_SERVER_INVALID_REQUEST_CODE = -32600;
|
|
2161
|
-
const CODEX_THREAD_NOT_LOADED_MESSAGE_PREFIX = "thread not loaded:";
|
|
2162
|
-
function isCodexThreadGoneError(error) {
|
|
2163
|
-
return error instanceof CodexAppServerRpcError && error.code === CODEX_APP_SERVER_INVALID_REQUEST_CODE && error.message.startsWith(CODEX_THREAD_NOT_LOADED_MESSAGE_PREFIX);
|
|
2164
|
-
}
|
|
2165
2477
|
function readMarker(probe) {
|
|
2166
2478
|
if (!isRecord(probe.marker)) return;
|
|
2167
2479
|
const turnId = probe.marker.turnId;
|
|
@@ -2224,14 +2536,14 @@ function normalizeUserMessageTexts(item) {
|
|
|
2224
2536
|
const contentTexts = typed.content?.filter((input) => input.type === "text").map((input) => input.text.trim().replace(/\s+/g, " ")).filter(Boolean);
|
|
2225
2537
|
return contentTexts?.length ? contentTexts : [(typed.text ?? "").trim().replace(/\s+/g, " ")];
|
|
2226
2538
|
}
|
|
2227
|
-
async function checkCodexUpstreamActivity(probes, control, resolveThreadId =
|
|
2539
|
+
async function checkCodexUpstreamActivity(probes, control, resolveThreadId = (probe) => probe.threadId) {
|
|
2228
2540
|
return await control.withPinnedConnection(async (pinned) => {
|
|
2229
2541
|
const activities = [];
|
|
2230
2542
|
for (const probe of probes) {
|
|
2231
2543
|
const fingerprint = upstreamConnectionFingerprint(probe);
|
|
2232
2544
|
if (probe.upstreamKind !== "codex-app-server" || !fingerprint || fingerprint !== pinned.connectionFingerprint) continue;
|
|
2233
2545
|
try {
|
|
2234
|
-
const threadId =
|
|
2546
|
+
const threadId = resolveThreadId(probe);
|
|
2235
2547
|
const page = await pinned.listTurnPage({
|
|
2236
2548
|
threadId,
|
|
2237
2549
|
limit: CODEX_UPSTREAM_TURN_LIMIT,
|
|
@@ -2243,7 +2555,7 @@ async function checkCodexUpstreamActivity(probes, control, resolveThreadId = asy
|
|
|
2243
2555
|
try {
|
|
2244
2556
|
await pinned.readThread(threadId, false);
|
|
2245
2557
|
} catch (error) {
|
|
2246
|
-
if (
|
|
2558
|
+
if (isCodexThreadReadMissingError(error, threadId)) activities.push({
|
|
2247
2559
|
kind: "missing",
|
|
2248
2560
|
sessionKey: probe.sessionKey
|
|
2249
2561
|
});
|
|
@@ -2261,7 +2573,7 @@ async function checkCodexUpstreamActivity(probes, control, resolveThreadId = asy
|
|
|
2261
2573
|
});
|
|
2262
2574
|
}
|
|
2263
2575
|
function createChecker(params) {
|
|
2264
|
-
const resolveThreadId =
|
|
2576
|
+
const resolveThreadId = (probe) => {
|
|
2265
2577
|
const config = params.getRuntimeConfig();
|
|
2266
2578
|
const sessionId = params.api.runtime.agent.session.getSessionEntry({
|
|
2267
2579
|
agentId: probe.agentId,
|
|
@@ -2269,7 +2581,7 @@ function createChecker(params) {
|
|
|
2269
2581
|
readConsistency: "latest"
|
|
2270
2582
|
})?.sessionId?.trim();
|
|
2271
2583
|
if (!sessionId) return probe.threadId;
|
|
2272
|
-
const binding =
|
|
2584
|
+
const binding = params.bindingStore.read(sessionBindingIdentity({
|
|
2273
2585
|
sessionId,
|
|
2274
2586
|
sessionKey: probe.sessionKey,
|
|
2275
2587
|
config
|
|
@@ -2311,7 +2623,7 @@ function existingCatalogHomeCandidates(value, label) {
|
|
|
2311
2623
|
/** Resolves every local Codex store the operator already owns, without path disclosure. */
|
|
2312
2624
|
function resolveCodexCatalogHomes(params) {
|
|
2313
2625
|
const { config, env, ownerAgentId, pluginConfig } = params;
|
|
2314
|
-
const ownerAgentDir = resolveAgentDir(config, ownerAgentId, env);
|
|
2626
|
+
const ownerAgentDir = resolveAgentDir$2(config, ownerAgentId, env);
|
|
2315
2627
|
const configuredHomes = readCodexPluginConfig(pluginConfig).sessionCatalog?.homes ?? [];
|
|
2316
2628
|
const base = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
2317
2629
|
pluginConfig,
|
|
@@ -2333,8 +2645,8 @@ function resolveCodexCatalogHomes(params) {
|
|
|
2333
2645
|
label: "Local Codex · user",
|
|
2334
2646
|
usesProcessHomeFallback: !processHomeConfigured
|
|
2335
2647
|
});
|
|
2336
|
-
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) => {
|
|
2648
|
+
const agentIds = listAgentIds$1(config).toSorted((left, right) => left === ownerAgentId ? -1 : right === ownerAgentId ? 1 : left.localeCompare(right));
|
|
2649
|
+
candidates.push(...agentIds.flatMap((agentId) => existingCatalogHomeCandidates(resolveCodexAppServerHomeDir(resolveAgentDir$2(config, agentId, env)), agentId)), ...configuredHomes.flatMap((entry) => {
|
|
2338
2650
|
const { path: home, label } = typeof entry === "string" ? { path: entry } : entry;
|
|
2339
2651
|
return existingCatalogHomeCandidates(home, label);
|
|
2340
2652
|
}));
|
|
@@ -2375,7 +2687,7 @@ function createCodexCatalogHomeResolver(params) {
|
|
|
2375
2687
|
const homesByConfig = /* @__PURE__ */ new WeakMap();
|
|
2376
2688
|
const buildSnapshot = (config) => {
|
|
2377
2689
|
const pluginConfig = params.getPluginConfig();
|
|
2378
|
-
const homesByAgent = new Map(listAgentIds(config).map((agentId) => [agentId, resolveCodexCatalogHomes({
|
|
2690
|
+
const homesByAgent = new Map(listAgentIds$1(config).map((agentId) => [agentId, resolveCodexCatalogHomes({
|
|
2379
2691
|
config,
|
|
2380
2692
|
pluginConfig,
|
|
2381
2693
|
ownerAgentId: agentId,
|
|
@@ -2400,96 +2712,6 @@ function createCodexCatalogHomeResolver(params) {
|
|
|
2400
2712
|
} };
|
|
2401
2713
|
}
|
|
2402
2714
|
//#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
2715
|
//#region extensions/codex/src/session-catalog-control.ts
|
|
2494
2716
|
const CODEX_SESSION_CATALOG_LIST_TTL_MS = 32e3;
|
|
2495
2717
|
const CODEX_SESSION_CATALOG_LIST_CACHE_MAX_ENTRIES = 32;
|
|
@@ -2508,21 +2730,76 @@ function createCodexCatalogRequestSnapshot(requestTimeoutMs, request) {
|
|
|
2508
2730
|
requestTimeoutMs,
|
|
2509
2731
|
listThreads: (params, timeoutMs) => request(CODEX_CONTROL_METHODS.listThreads, params, timeoutMs),
|
|
2510
2732
|
listThreadTurns: (params) => request(CODEX_CONTROL_METHODS.listThreadTurns, params),
|
|
2511
|
-
|
|
2512
|
-
|
|
2733
|
+
listThreadItems: (params) => request(CODEX_CONTROL_METHODS.listThreadItems, params),
|
|
2734
|
+
forkThread: (params, assertCurrent) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params), void 0, assertCurrent),
|
|
2735
|
+
readThread: async (threadId, includeTurns, timeoutMs) => (await request(CODEX_CONTROL_METHODS.readThread, {
|
|
2513
2736
|
threadId,
|
|
2514
2737
|
includeTurns
|
|
2515
|
-
})).thread,
|
|
2516
|
-
archiveThread: async (threadId) => {
|
|
2517
|
-
await request(CODEX_CONTROL_METHODS.archiveThread, { threadId });
|
|
2738
|
+
}, timeoutMs)).thread,
|
|
2739
|
+
archiveThread: async (threadId, assertCurrent) => {
|
|
2740
|
+
await request(CODEX_CONTROL_METHODS.archiveThread, { threadId }, void 0, assertCurrent);
|
|
2518
2741
|
}
|
|
2519
2742
|
};
|
|
2520
2743
|
}
|
|
2521
2744
|
function createCodexSessionCatalogControlFromRequests(params) {
|
|
2522
2745
|
return {
|
|
2746
|
+
forkContext: params.forkContext,
|
|
2523
2747
|
...params.clientId ? { clientId: params.clientId } : {},
|
|
2524
2748
|
...params.connectionFingerprint ? { connectionFingerprint: params.connectionFingerprint } : {},
|
|
2525
2749
|
withPinnedConnection: params.withPinnedConnection,
|
|
2750
|
+
async requireEligibleThread(threadId) {
|
|
2751
|
+
const requests = params.createRequestSnapshot();
|
|
2752
|
+
const deadline = params.now() + requests.requestTimeoutMs;
|
|
2753
|
+
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.");
|
|
2754
|
+
const remaining = () => {
|
|
2755
|
+
const timeoutMs = Math.ceil(deadline - params.now());
|
|
2756
|
+
if (timeoutMs <= 0) throw unverified();
|
|
2757
|
+
return timeoutMs;
|
|
2758
|
+
};
|
|
2759
|
+
const verify = async () => {
|
|
2760
|
+
if (params.sourceHomeId && await params.managedThreads?.has(params.sourceHomeId, threadId)) throw unverified();
|
|
2761
|
+
const root = params.localSessionsRoot;
|
|
2762
|
+
const thread = root ? await requests.readThread(threadId, false, remaining()) : void 0;
|
|
2763
|
+
if (root && (!thread || thread.id !== threadId || !isInteractiveThreadSource(thread.source))) throw unverified();
|
|
2764
|
+
let cursor;
|
|
2765
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
2766
|
+
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
2767
|
+
const page = await requests.listThreads({
|
|
2768
|
+
archived: false,
|
|
2769
|
+
limit: 100,
|
|
2770
|
+
modelProviders: [],
|
|
2771
|
+
sortKey: root ? "recency_at" : "updated_at",
|
|
2772
|
+
sortDirection: "desc",
|
|
2773
|
+
...root ? {
|
|
2774
|
+
useStateDbOnly: true,
|
|
2775
|
+
...thread?.cwd ? { cwd: thread.cwd } : {}
|
|
2776
|
+
} : {},
|
|
2777
|
+
...cursor ? { cursor } : {}
|
|
2778
|
+
}, remaining());
|
|
2779
|
+
remaining();
|
|
2780
|
+
const candidate = page.data.find((value) => value.id === threadId);
|
|
2781
|
+
if (candidate) {
|
|
2782
|
+
if (!isInteractiveThreadSource(candidate.source)) throw unverified();
|
|
2783
|
+
if (root && thread) {
|
|
2784
|
+
const rolloutPath = thread.path;
|
|
2785
|
+
if (!rolloutPath || !candidate.path || rolloutPath.replace(/\.zst$/u, "") !== candidate.path.replace(/\.zst$/u, "")) throw unverified();
|
|
2786
|
+
const metadata = await readCodexSessionMeta(root, rolloutPath, threadId);
|
|
2787
|
+
remaining();
|
|
2788
|
+
if (!metadata || !isInteractiveThreadSource(metadata.source) || metadata.originator === "openclaw") throw unverified();
|
|
2789
|
+
return thread;
|
|
2790
|
+
}
|
|
2791
|
+
return candidate;
|
|
2792
|
+
}
|
|
2793
|
+
const nextCursor = readControlCursor(page.nextCursor, "next response");
|
|
2794
|
+
if (!nextCursor || seenCursors.has(nextCursor)) throw unverified();
|
|
2795
|
+
seenCursors.add(nextCursor);
|
|
2796
|
+
cursor = nextCursor;
|
|
2797
|
+
}
|
|
2798
|
+
throw unverified();
|
|
2799
|
+
};
|
|
2800
|
+
return await withTimeout(verify(), requests.requestTimeoutMs, "Codex session eligibility could not be verified", unverified);
|
|
2801
|
+
},
|
|
2802
|
+
retireConnection: params.retireConnection,
|
|
2526
2803
|
async listPage(pageParams) {
|
|
2527
2804
|
const limit = normalizeLimit(pageParams.limit, "limit");
|
|
2528
2805
|
const search = pageParams.searchTerm?.trim().toLocaleLowerCase() || void 0;
|
|
@@ -2584,11 +2861,12 @@ function createCodexSessionCatalogControlFromRequests(params) {
|
|
|
2584
2861
|
async listTurnPage(listParams) {
|
|
2585
2862
|
return await params.createRequestSnapshot().listThreadTurns(listParams);
|
|
2586
2863
|
},
|
|
2587
|
-
|
|
2588
|
-
|
|
2864
|
+
listItemPage: (listParams) => params.createRequestSnapshot().listThreadItems(listParams),
|
|
2865
|
+
async forkThread(forkParams, assertCurrent) {
|
|
2866
|
+
return await params.createRequestSnapshot().forkThread(forkParams, assertCurrent);
|
|
2589
2867
|
},
|
|
2590
|
-
async archiveThread(threadId) {
|
|
2591
|
-
await params.createRequestSnapshot().archiveThread(threadId);
|
|
2868
|
+
async archiveThread(threadId, assertCurrent) {
|
|
2869
|
+
await params.createRequestSnapshot().archiveThread(threadId, assertCurrent);
|
|
2592
2870
|
}
|
|
2593
2871
|
};
|
|
2594
2872
|
}
|
|
@@ -2606,7 +2884,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2606
2884
|
const catalogPagesByConfig = /* @__PURE__ */ new WeakMap();
|
|
2607
2885
|
const resolveRequestOptions = (startOptions, agentId, source) => {
|
|
2608
2886
|
const runtimeConfig = params.getRuntimeConfig();
|
|
2609
|
-
const agentDir = source?.agentDir ?? resolveAgentDir(runtimeConfig ?? {}, agentId);
|
|
2887
|
+
const agentDir = source?.agentDir ?? resolveAgentDir$2(runtimeConfig ?? {}, agentId);
|
|
2610
2888
|
const resolvedStartOptions = source?.appServer.start ?? startOptions;
|
|
2611
2889
|
if (!runtimeConfig) return {
|
|
2612
2890
|
agentDir,
|
|
@@ -2633,8 +2911,9 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2633
2911
|
const pluginConfig = getPluginConfig();
|
|
2634
2912
|
const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
|
|
2635
2913
|
const requestOptions = resolveRequestOptions(runtime.start, agentId, source);
|
|
2636
|
-
return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await codexControlRequest(pluginConfig, method, requestParams, {
|
|
2914
|
+
return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await codexControlRequest(pluginConfig, method, requestParams, {
|
|
2637
2915
|
...requestOptions,
|
|
2916
|
+
assertCurrent,
|
|
2638
2917
|
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
2639
2918
|
}));
|
|
2640
2919
|
};
|
|
@@ -2650,18 +2929,31 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2650
2929
|
timeoutMs: runtime.requestTimeoutMs
|
|
2651
2930
|
});
|
|
2652
2931
|
try {
|
|
2653
|
-
const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await requestCodexAppServerClientJson({
|
|
2932
|
+
const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await requestCodexAppServerClientJson({
|
|
2654
2933
|
client,
|
|
2655
2934
|
method,
|
|
2656
2935
|
requestParams,
|
|
2657
2936
|
config: runtimeConfig,
|
|
2658
|
-
timeoutMs: timeoutMs ?? runtime.requestTimeoutMs
|
|
2937
|
+
timeoutMs: timeoutMs ?? runtime.requestTimeoutMs,
|
|
2938
|
+
assertCurrent
|
|
2659
2939
|
}));
|
|
2660
2940
|
const pinnedControl = createCodexSessionCatalogControlFromRequests({
|
|
2941
|
+
forkContext: {
|
|
2942
|
+
client,
|
|
2943
|
+
appServer: runtime,
|
|
2944
|
+
pluginConfig,
|
|
2945
|
+
agentDir,
|
|
2946
|
+
localSessionsRoot: source?.localSessionsRoot
|
|
2947
|
+
},
|
|
2661
2948
|
clientId: resolveCodexAppServerClientInstanceId(client),
|
|
2949
|
+
retireConnection: () => {
|
|
2950
|
+
retireSharedCodexAppServerClientIfCurrent(client);
|
|
2951
|
+
},
|
|
2662
2952
|
connectionFingerprint: buildCodexAppServerConnectionFingerprint(runtime, agentDir),
|
|
2663
2953
|
createRequestSnapshot: () => requests,
|
|
2664
2954
|
...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
|
|
2955
|
+
sourceHomeId: source?.sourceHomeId,
|
|
2956
|
+
managedThreads: params.managedThreads,
|
|
2665
2957
|
now,
|
|
2666
2958
|
withPinnedConnection: async (nestedRun) => await nestedRun(pinnedControl)
|
|
2667
2959
|
});
|
|
@@ -2678,6 +2970,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2678
2970
|
});
|
|
2679
2971
|
return {
|
|
2680
2972
|
...control,
|
|
2973
|
+
requireEligibleThread: (threadId) => withPinnedConnection((pinned) => pinned.requireEligibleThread(threadId)),
|
|
2681
2974
|
async listPage(pageParams) {
|
|
2682
2975
|
const runtimeConfig = params.getRuntimeConfig();
|
|
2683
2976
|
if (!runtimeConfig) return await control.listPage(pageParams);
|
|
@@ -2688,7 +2981,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2688
2981
|
}
|
|
2689
2982
|
const key = codexCatalogPageCacheKey(pageParams, agentId, source);
|
|
2690
2983
|
const cached = cache.get(key);
|
|
2691
|
-
if (
|
|
2984
|
+
if (cached) {
|
|
2692
2985
|
cache.delete(key);
|
|
2693
2986
|
cache.set(key, cached);
|
|
2694
2987
|
if (cached.expiresAt > now()) return cached.value ?? await cached.page;
|
|
@@ -2719,7 +3012,7 @@ function createCodexSessionCatalogControl(params) {
|
|
|
2719
3012
|
});
|
|
2720
3013
|
else cache.delete(key);
|
|
2721
3014
|
};
|
|
2722
|
-
if (
|
|
3015
|
+
if (staleValue) {
|
|
2723
3016
|
page.then(settle, restore);
|
|
2724
3017
|
return staleValue;
|
|
2725
3018
|
}
|
|
@@ -2751,14 +3044,22 @@ function createCodexSessionCatalogNodeInvokePolicies() {
|
|
|
2751
3044
|
commands: [
|
|
2752
3045
|
CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
2753
3046
|
CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
2754
|
-
|
|
3047
|
+
CODEX_CATALOG_TRANSCRIPT_READ_COMMAND,
|
|
3048
|
+
CODEX_TERMINAL_RESUME_COMMAND,
|
|
3049
|
+
CODEX_TERMINAL_START_COMMAND
|
|
2755
3050
|
],
|
|
2756
3051
|
defaultPlatforms: [
|
|
2757
3052
|
"macos",
|
|
2758
3053
|
"linux",
|
|
2759
3054
|
"windows"
|
|
2760
3055
|
],
|
|
2761
|
-
handle: (context) =>
|
|
3056
|
+
handle: (context) => {
|
|
3057
|
+
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 } : {
|
|
3058
|
+
ok: false,
|
|
3059
|
+
message: "Native terminal start requires operator.admin and enabled CLI agents and terminals"
|
|
3060
|
+
};
|
|
3061
|
+
return context.command === "codex.terminal.resume.v1" ? { ok: true } : context.invokeNode();
|
|
3062
|
+
}
|
|
2762
3063
|
}];
|
|
2763
3064
|
}
|
|
2764
3065
|
function toGenericCatalogHost(host, localTerminalAvailable) {
|
|
@@ -2813,7 +3114,7 @@ function registerCodexSessionCatalog(params) {
|
|
|
2813
3114
|
const homes = params.control.homesForAgent(agentId);
|
|
2814
3115
|
return allowProcessHomeFallback === false ? homes.filter((home) => !home.usesProcessHomeFallback) : homes;
|
|
2815
3116
|
};
|
|
2816
|
-
const resolveRequestAgentId = (agentId) =>
|
|
3117
|
+
const resolveRequestAgentId = (agentId) => resolveSessionAgentIdsStrict({
|
|
2817
3118
|
config: params.getRuntimeConfig() ?? params.api.config,
|
|
2818
3119
|
agentId
|
|
2819
3120
|
}).sessionAgentId;
|
|
@@ -2846,10 +3147,13 @@ function registerCodexSessionCatalog(params) {
|
|
|
2846
3147
|
resolveCreateSession: ({ agentId }) => resolveCodexCatalogCreateSession(params.getRuntimeConfig() ?? params.api.config, agentId),
|
|
2847
3148
|
list: async (query) => {
|
|
2848
3149
|
const localTerminalAvailable = resolveLocalCodexTerminalExecutable() !== void 0;
|
|
2849
|
-
const { agentId: requestedAgentId, allowProcessHomeFallback, listNodes, onHost, sessionEntries, ...gatewayQuery } = query;
|
|
3150
|
+
const { agentId: requestedAgentId, allowProcessHomeFallback, listNodes, onHost, waitUntil, signal, sessionEntries, ...gatewayQuery } = query;
|
|
2850
3151
|
const agentId = resolveRequestAgentId(requestedAgentId);
|
|
2851
|
-
const mapHost = (host) => toGenericCatalogHost(host, localTerminalAvailable);
|
|
2852
3152
|
const localHomes = [...catalogHomes(agentId, allowProcessHomeFallback)];
|
|
3153
|
+
const mapHost = (host) => ({
|
|
3154
|
+
...toGenericCatalogHost(host, localTerminalAvailable),
|
|
3155
|
+
canStartTerminal: host.kind === "gateway" ? localTerminalAvailable && localHomes.some((home) => home.hostId === host.hostId && home.appServer.start.transport === "stdio") : host.canStartTerminal === true
|
|
3156
|
+
});
|
|
2853
3157
|
return (await listCodexSessionCatalog({
|
|
2854
3158
|
agentId,
|
|
2855
3159
|
bindingStore: params.bindingStore,
|
|
@@ -2858,6 +3162,8 @@ function registerCodexSessionCatalog(params) {
|
|
|
2858
3162
|
control: params.control,
|
|
2859
3163
|
query: gatewayQuery,
|
|
2860
3164
|
listNodes,
|
|
3165
|
+
waitUntil,
|
|
3166
|
+
signal,
|
|
2861
3167
|
sessionEntries,
|
|
2862
3168
|
localHomes,
|
|
2863
3169
|
...onHost ? { onHost: (host) => onHost(mapHost(host)) } : {}
|
|
@@ -2865,7 +3171,7 @@ function registerCodexSessionCatalog(params) {
|
|
|
2865
3171
|
},
|
|
2866
3172
|
read: async (request) => {
|
|
2867
3173
|
const { agentId, source, control } = bindRequest(request);
|
|
2868
|
-
|
|
3174
|
+
return await readCodexSessionTranscript({
|
|
2869
3175
|
agentId,
|
|
2870
3176
|
runtime: params.api.runtime,
|
|
2871
3177
|
control,
|
|
@@ -2875,10 +3181,6 @@ function registerCodexSessionCatalog(params) {
|
|
|
2875
3181
|
limit: request.limit ?? 20,
|
|
2876
3182
|
...source ? { source } : {}
|
|
2877
3183
|
});
|
|
2878
|
-
return {
|
|
2879
|
-
...page,
|
|
2880
|
-
items: page.items.map(toGenericTranscriptItem)
|
|
2881
|
-
};
|
|
2882
3184
|
},
|
|
2883
3185
|
continueSession: async (request) => {
|
|
2884
3186
|
const config = params.getRuntimeConfig();
|
|
@@ -2894,7 +3196,7 @@ function registerCodexSessionCatalog(params) {
|
|
|
2894
3196
|
if (!isLocalCodexCatalogHost(request.hostId)) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
2895
3197
|
const { agentId, source, control } = bindLocalRequest(request);
|
|
2896
3198
|
let upstreamBaseline;
|
|
2897
|
-
|
|
3199
|
+
const continued = await continueLocalCodexSession({
|
|
2898
3200
|
agentId,
|
|
2899
3201
|
api: params.api,
|
|
2900
3202
|
bindingStore: params.bindingStore,
|
|
@@ -2907,7 +3209,8 @@ function registerCodexSessionCatalog(params) {
|
|
|
2907
3209
|
onContinued: (baseline) => {
|
|
2908
3210
|
upstreamBaseline = baseline;
|
|
2909
3211
|
}
|
|
2910
|
-
})
|
|
3212
|
+
});
|
|
3213
|
+
return codexUpstreamContinueResult(continued.sessionKey, request.threadId, upstreamBaseline);
|
|
2911
3214
|
},
|
|
2912
3215
|
checkUpstreamActivity: (probes, policy) => checkUpstreamActivity(probes.filter((probe) => !isLocalCodexCatalogHost(probe.hostId) || policy?.allowProcessHomeFallback !== false || catalogHomes(probe.agentId, false).some((home) => home.hostId === probe.hostId))),
|
|
2913
3216
|
archive: async (request) => {
|
|
@@ -2943,11 +3246,16 @@ function registerCodexSessionCatalog(params) {
|
|
|
2943
3246
|
});
|
|
2944
3247
|
},
|
|
2945
3248
|
startTerminalSession: async (request) => {
|
|
2946
|
-
|
|
3249
|
+
const source = request.nodeId ? void 0 : resolveLocalCatalogHomeForThread({
|
|
3250
|
+
homes: [...catalogHomes(request.agentId, request.allowProcessHomeFallback)],
|
|
3251
|
+
hostId: request.hostId ?? "gateway:local"
|
|
3252
|
+
});
|
|
3253
|
+
if (source && source.appServer.start.transport !== "stdio") throw new CatalogParamsError("Native terminal start requires a local Codex source");
|
|
2947
3254
|
return await startCodexCatalogTerminal({
|
|
2948
3255
|
getPluginConfig: params.getPluginConfig,
|
|
2949
3256
|
getRuntimeConfig: params.getRuntimeConfig,
|
|
2950
|
-
...request
|
|
3257
|
+
...request,
|
|
3258
|
+
source
|
|
2951
3259
|
});
|
|
2952
3260
|
}
|
|
2953
3261
|
});
|
|
@@ -2961,4 +3269,4 @@ const codexSessionCatalogRuntime = {
|
|
|
2961
3269
|
archiveLocal: archiveLocalCodexSession
|
|
2962
3270
|
};
|
|
2963
3271
|
//#endregion
|
|
2964
|
-
export { CODEX_LOCAL_SESSION_HOST_ID as a,
|
|
3272
|
+
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 };
|