@openclaw/codex 2026.8.1-beta.2 → 2026.9.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +147 -0
- package/dist/{attempt-client-cleanup-DMLXUfa8.js → attempt-client-cleanup-bfsmcQ6m.js} +21 -2
- package/dist/{binding-connection-I5Bo-AsX.js → binding-connection-BMvPX73o.js} +15 -3
- package/dist/{bounded-turn-qgZmzy_4.js → bounded-turn-B7iBUcuP.js} +22 -16
- package/dist/{capabilities-CybGI1oz.js → capabilities-BRMh0aIq.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-B1AEz8ZA.js → command-formatters-DEsIufkt.js} +2 -2
- package/dist/{command-handlers-Db8pu_ds.js → command-handlers-D_-1_qRp.js} +65 -37
- package/dist/command-rpc-CCeCkf_v.js +90 -0
- package/dist/{compact-LspQaGfl.js → compact-BglNFZrQ.js} +35 -29
- package/dist/{session-binding-DPxIvMgE.js → config-giW8X0QA.js} +108 -914
- package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
- package/dist/doctor-contract-api.js +23 -21
- package/dist/{dynamic-tools-BVdyWkUD.js → dynamic-tools-Dnlfa--w.js} +1345 -1424
- package/dist/{effective-mcp-catalog-DIIhAtOZ.js → effective-mcp-catalog-CTeGrnfO.js} +2 -2
- package/dist/elicitation-input-DMkeki8d.js +71 -0
- package/dist/{event-projector-assistant-message-BwxV4d6N.js → event-projector-assistant-message-C5-Ds8mA.js} +18 -1
- package/dist/{event-projector-usage-e5huIQWu.js → event-projector-usage-Bk1I90Bg.js} +18 -18
- package/dist/harness-Csz0F-DK.js +258 -0
- package/dist/harness.js +2 -226
- package/dist/index.js +262 -181
- package/dist/{isolated-completion-C58NbL40.js → isolated-completion-Cv6FqygM.js} +4 -4
- package/dist/managed-binary-DFYK4DWq.js +248 -0
- package/dist/media-understanding-provider.js +2 -2
- package/dist/model-catalog-i9rNBthX.js +48 -0
- package/dist/{models-BFlpn1Ex.js → models-TS3HBU-q.js} +4 -3
- package/dist/{native-subagent-monitor-DyTGmTUx.js → native-subagent-monitor-4Mfq2HDs.js} +354 -83
- package/dist/node-exec-server.runtime-BpWqJO1V.js +192 -0
- package/dist/{plugin-app-cache-key-C3H2CVp6.js → plugin-app-cache-key-C1CAdfcP.js} +19 -4
- package/dist/plugin-approval-roundtrip-bUm777tR.js +95 -0
- package/dist/{plugin-marketplace-discovery-CzrGJdCR.js → plugin-marketplace-discovery-B9DvaAG-.js} +59 -54
- package/dist/{plugin-metadata-cache-CNJPj4_b.js → plugin-metadata-cache-CTCT3w5L.js} +164 -134
- package/dist/{protocol-validators-B4F7L8Rf.js → protocol-validators-VeJxxJ0U.js} +80 -5
- package/dist/{rate-limits-DZXmOL8-.js → rate-limits-C5_D8Gmb.js} +1 -1
- package/dist/{request-BRl5_UfX.js → request-DXXGHko0.js} +2 -2
- package/dist/{run-attempt-P8BdxC1I.js → run-attempt-DYc0QCBF.js} +1276 -765
- package/dist/{runtime-artifact-BVSPdd6g.js → runtime-artifact-BvyT1uyd.js} +4 -7
- package/dist/{sandbox-guard-Cmwc9eIk.js → sandbox-guard-kuWTn9C5.js} +25 -19
- package/dist/session-binding-Bcynfd-_.js +874 -0
- package/dist/{session-catalog-xwNi_SDM.js → session-catalog-DaLKVtpH.js} +1534 -1288
- package/dist/session-catalog-home-id-BNwAJ9D7.js +18 -0
- package/dist/{session-cli-7hgXZDV7.js → session-cli-DMc4oS2w.js} +20 -7
- package/dist/session-permission-policy-BrNH1bjy.js +160 -0
- package/dist/{session-retirement-DoMMJFKm.js → session-retirement-CcNC3YSY.js} +3 -3
- package/dist/{session-upstream-marker-CrIFX06J.js → session-upstream-marker-BIkusb95.js} +1 -1
- package/dist/{settled-turn-finalizer-BRWBO4MO.js → settled-turn-finalizer-Dc3ApjU4.js} +24 -20
- package/dist/{shared-client-kIBbGjk4.js → shared-client-DKz23hHY.js} +1632 -372
- package/dist/{side-question-9HAL26l9.js → side-question-DjA_jsNe.js} +105 -54
- package/dist/{thread-lifecycle-Bu1BTDXr.js → thread-lifecycle-L7oEkTlq.js} +1147 -114
- package/dist/{thread-ownership-6kOQHlyC.js → thread-ownership-DZeuKH1j.js} +2 -2
- package/dist/{thread-resume-CPrJNQVM.js → thread-resume-BE9qNEW0.js} +3 -3
- package/dist/{transcript-mirror-B9gGgnJS.js → transcript-mirror-DsCMWBca.js} +137 -38
- package/dist/{transport-stdio-DIImwDVS.js → transport-stdio-DhoGy33Z.js} +2 -2
- package/dist/{upstream-prompt-provenance-ClRBije0.js → upstream-prompt-provenance-CFmsKwr9.js} +12 -14
- package/dist/{upstream-session-fork-CE-_LdnP.js → upstream-session-fork-DlaHxSLC.js} +12 -6
- package/dist/{usage-CHlV0e-o.js → usage-ROIaW3wo.js} +3 -3
- package/dist/{web-search-provider.runtime-DRw2Vl42.js → web-search-provider.runtime-ClaNPV7N.js} +3 -3
- package/openclaw.plugin.json +49 -2
- package/package.json +9 -8
- package/dist/app-server-policy-zcBDc1JU.js +0 -49
|
@@ -1,32 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { n as
|
|
5
|
-
import { t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { S as resolveCodexAppServerUserHomeDir, h as readCodexPluginConfig, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-giW8X0QA.js";
|
|
2
|
+
import { G as CodexAppServerRpcError, St as resolveCodexAppServerLocalHomeDir, U as resolveCodexAppServerClientInstanceId, ht as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, mt as CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES, o as getLeasedSharedCodexAppServerClient, p as releaseLeasedSharedCodexAppServerClient, rt as withTimeout, vt as isJsonObject, xt as resolveCodexAppServerHomeDir } from "./shared-client-DKz23hHY.js";
|
|
3
|
+
import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity } from "./session-binding-Bcynfd-_.js";
|
|
4
|
+
import { n as assertCodexThreadForkParams } from "./protocol-validators-VeJxxJ0U.js";
|
|
5
|
+
import { i as replaceCodexCatalogConnectionHomes, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-C1CAdfcP.js";
|
|
6
|
+
import { n as codexCatalogHomeId, t as canonicalCodexCatalogHome } from "./session-catalog-home-id-BNwAJ9D7.js";
|
|
7
|
+
import { r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-DsCMWBca.js";
|
|
8
|
+
import { t as CODEX_CONTROL_METHODS } from "./capabilities-BRMh0aIq.js";
|
|
9
|
+
import { r as formatCodexDisplayText } from "./command-formatters-DEsIufkt.js";
|
|
9
10
|
import { n as createCodexCliNodeConversationBindingData } from "./conversation-binding-data-CW2GuupV.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { n as requestCodexAppServerClientJson } from "./request-DXXGHko0.js";
|
|
12
|
+
import { t as codexControlRequest } from "./command-rpc-CCeCkf_v.js";
|
|
13
|
+
import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-BIkusb95.js";
|
|
12
14
|
import { asFiniteNumber, isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
15
|
import fs from "node:fs/promises";
|
|
14
16
|
import path from "node:path";
|
|
15
17
|
import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
16
18
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
17
|
-
import
|
|
19
|
+
import fsSync from "node:fs";
|
|
18
20
|
import os from "node:os";
|
|
19
|
-
import { listAgentIds, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
|
|
20
|
-
import {
|
|
21
|
+
import { listAgentIds, resolveAgentDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
22
|
+
import { runCommandBuffered } from "openclaw/plugin-sdk/process-runtime";
|
|
21
23
|
import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
|
|
22
24
|
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
23
|
-
import { spawn } from "node:child_process";
|
|
24
25
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
25
26
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
26
27
|
import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
|
|
28
|
+
import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
29
|
+
import { root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
27
30
|
import process$1 from "node:process";
|
|
28
|
-
import { listSessionCatalogEntries } from "openclaw/plugin-sdk/session-catalog";
|
|
31
|
+
import { createSessionCatalogAdoptionCoordinator, listSessionCatalogEntries, sessionCatalogAdoptedSessionKey, sessionCatalogAdoptedSourceKey } from "openclaw/plugin-sdk/session-catalog";
|
|
29
32
|
import { decodeNodePtyResumeParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
|
|
33
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
34
|
+
import { createZstdDecompress } from "node:zlib";
|
|
30
35
|
//#region extensions/codex/src/jsonl-lines.ts
|
|
31
36
|
const JSONL_STREAM_THRESHOLD_BYTES = 4 * 1024 * 1024;
|
|
32
37
|
const JSONL_READ_CHUNK_BYTES = 1024 * 1024;
|
|
@@ -132,11 +137,6 @@ const MAX_SESSION_LIMIT = 50;
|
|
|
132
137
|
const DEFAULT_RESUME_TIMEOUT_MS = 20 * 6e4;
|
|
133
138
|
const SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
134
139
|
const activeResumeSessions = /* @__PURE__ */ new Set();
|
|
135
|
-
const DEFAULT_RESUME_SPAWN_RUNTIME = {
|
|
136
|
-
platform: process$1.platform,
|
|
137
|
-
env: process$1.env,
|
|
138
|
-
execPath: process$1.execPath
|
|
139
|
-
};
|
|
140
140
|
function createCodexCliSessionNodeHostCommands() {
|
|
141
141
|
return [{
|
|
142
142
|
command: CODEX_CLI_SESSIONS_LIST_COMMAND,
|
|
@@ -272,7 +272,7 @@ async function resumeLocalCodexCliSession(paramsJSON) {
|
|
|
272
272
|
async function runCodexExecResume(params) {
|
|
273
273
|
const outputPath = path.join(await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-codex-cli-")), "last-message.txt");
|
|
274
274
|
try {
|
|
275
|
-
const
|
|
275
|
+
const args = [
|
|
276
276
|
"exec",
|
|
277
277
|
"resume",
|
|
278
278
|
"--skip-git-repo-check",
|
|
@@ -280,45 +280,27 @@ async function runCodexExecResume(params) {
|
|
|
280
280
|
outputPath,
|
|
281
281
|
params.sessionId,
|
|
282
282
|
"-"
|
|
283
|
-
]
|
|
283
|
+
];
|
|
284
|
+
const invocation = materializeWindowsSpawnProgram(resolveWindowsSpawnProgram({
|
|
285
|
+
command: "codex",
|
|
284
286
|
platform: process$1.platform,
|
|
285
287
|
env: process$1.env,
|
|
286
|
-
execPath: process$1.execPath
|
|
287
|
-
|
|
288
|
-
|
|
288
|
+
execPath: process$1.execPath,
|
|
289
|
+
packageName: "@openai/codex"
|
|
290
|
+
}), args);
|
|
291
|
+
const result = await runCommandBuffered([invocation.command, ...invocation.argv], {
|
|
289
292
|
cwd: params.cwd || process$1.cwd(),
|
|
290
|
-
|
|
291
|
-
"pipe",
|
|
292
|
-
"pipe",
|
|
293
|
-
"pipe"
|
|
294
|
-
],
|
|
293
|
+
input: params.prompt,
|
|
295
294
|
env: process$1.env,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const stderr = [];
|
|
301
|
-
let timedOut = false;
|
|
302
|
-
let forceKillTimeout;
|
|
303
|
-
const timeout = setTimeout(() => {
|
|
304
|
-
timedOut = true;
|
|
305
|
-
child.kill("SIGTERM");
|
|
306
|
-
forceKillTimeout = setTimeout(() => child.kill("SIGKILL"), 2e3);
|
|
307
|
-
forceKillTimeout.unref?.();
|
|
308
|
-
}, params.timeoutMs);
|
|
309
|
-
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
310
|
-
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
311
|
-
child.stdin.end(params.prompt);
|
|
312
|
-
const exitCode = await new Promise((resolve, reject) => {
|
|
313
|
-
child.on("error", reject);
|
|
314
|
-
child.on("exit", (code) => resolve(code));
|
|
315
|
-
}).finally(() => {
|
|
316
|
-
clearTimeout(timeout);
|
|
317
|
-
if (forceKillTimeout) clearTimeout(forceKillTimeout);
|
|
295
|
+
killGraceMs: 2e3,
|
|
296
|
+
killProcessTree: false,
|
|
297
|
+
terminateOnOutputError: true,
|
|
298
|
+
timeoutMs: params.timeoutMs
|
|
318
299
|
});
|
|
319
|
-
if (
|
|
320
|
-
if (
|
|
321
|
-
|
|
300
|
+
if (result.termination === "timeout") throw new Error(`codex exec resume timed out after ${String(params.timeoutMs)}ms`);
|
|
301
|
+
if (result.termination === "error" && result.error) throw result.error;
|
|
302
|
+
if (result.code !== 0) {
|
|
303
|
+
const message = result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim() || `codex exec resume exited with code ${String(result.code)}`;
|
|
322
304
|
throw new Error(message);
|
|
323
305
|
}
|
|
324
306
|
return await fs.readFile(outputPath, "utf8");
|
|
@@ -329,21 +311,6 @@ async function runCodexExecResume(params) {
|
|
|
329
311
|
});
|
|
330
312
|
}
|
|
331
313
|
}
|
|
332
|
-
function resolveCodexCliResumeSpawnInvocation(args, runtime = DEFAULT_RESUME_SPAWN_RUNTIME) {
|
|
333
|
-
const resolved = materializeWindowsSpawnProgram(resolveWindowsSpawnProgram({
|
|
334
|
-
command: "codex",
|
|
335
|
-
platform: runtime.platform,
|
|
336
|
-
env: runtime.env,
|
|
337
|
-
execPath: runtime.execPath,
|
|
338
|
-
packageName: "@openai/codex"
|
|
339
|
-
}), args);
|
|
340
|
-
return {
|
|
341
|
-
command: resolved.command,
|
|
342
|
-
args: resolved.argv,
|
|
343
|
-
shell: resolved.shell,
|
|
344
|
-
windowsHide: resolved.windowsHide
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
314
|
async function readHistorySessions(codexHome) {
|
|
348
315
|
const summaries = /* @__PURE__ */ new Map();
|
|
349
316
|
if (!(await visitJsonlLines(path.join(codexHome, "history.jsonl"), (line) => {
|
|
@@ -579,91 +546,6 @@ function formatNodeLabel(node) {
|
|
|
579
546
|
].filter(Boolean).join(" / ") || "node";
|
|
580
547
|
}
|
|
581
548
|
//#endregion
|
|
582
|
-
//#region extensions/codex/src/command-rpc.ts
|
|
583
|
-
function requestOptions(pluginConfig, limit, config, agentDir) {
|
|
584
|
-
const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
585
|
-
return {
|
|
586
|
-
limit,
|
|
587
|
-
timeoutMs: runtime.requestTimeoutMs,
|
|
588
|
-
startOptions: runtime.start,
|
|
589
|
-
config,
|
|
590
|
-
agentDir
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
async function codexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
594
|
-
const runtime = options.startOptions ? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig }) : resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
595
|
-
const controlRequestOptions = {
|
|
596
|
-
timeoutMs: options.timeoutMs ?? runtime.requestTimeoutMs,
|
|
597
|
-
startOptions: options.startOptions ?? runtime.start,
|
|
598
|
-
config: options.config,
|
|
599
|
-
sessionKey: options.sessionKey,
|
|
600
|
-
sessionId: options.sessionId,
|
|
601
|
-
authProfileId: options.authProfileId,
|
|
602
|
-
agentDir: options.agentDir,
|
|
603
|
-
isolated: options.isolated
|
|
604
|
-
};
|
|
605
|
-
if (options.onResponse) return await withCodexAppServerJsonClient(controlRequestOptions, async (request, client) => {
|
|
606
|
-
const response = await request({
|
|
607
|
-
method,
|
|
608
|
-
requestParams
|
|
609
|
-
});
|
|
610
|
-
await options.onResponse(response, client);
|
|
611
|
-
return response;
|
|
612
|
-
});
|
|
613
|
-
return await requestCodexAppServerJson({
|
|
614
|
-
method,
|
|
615
|
-
requestParams,
|
|
616
|
-
...controlRequestOptions
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
async function safeCodexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
620
|
-
return await safeValue(async () => await codexControlRequest(pluginConfig, method, requestParams, options));
|
|
621
|
-
}
|
|
622
|
-
async function safeCodexModelList(pluginConfig, limit, config, agentDir) {
|
|
623
|
-
return await safeValue(async () => await listCodexAppServerModels(requestOptions(pluginConfig, limit, config, agentDir)));
|
|
624
|
-
}
|
|
625
|
-
async function readCodexStatusProbes(pluginConfig, config, agentDir) {
|
|
626
|
-
const [models, account, limits, mcps, skills] = await Promise.all([
|
|
627
|
-
safeCodexModelList(pluginConfig, 20, config, agentDir),
|
|
628
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, {
|
|
629
|
-
config,
|
|
630
|
-
agentDir
|
|
631
|
-
}),
|
|
632
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, {
|
|
633
|
-
config,
|
|
634
|
-
agentDir
|
|
635
|
-
}),
|
|
636
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, {
|
|
637
|
-
config,
|
|
638
|
-
agentDir
|
|
639
|
-
}),
|
|
640
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, {
|
|
641
|
-
config,
|
|
642
|
-
agentDir
|
|
643
|
-
})
|
|
644
|
-
]);
|
|
645
|
-
return {
|
|
646
|
-
models,
|
|
647
|
-
account,
|
|
648
|
-
limits,
|
|
649
|
-
mcps,
|
|
650
|
-
skills
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
async function safeValue(read) {
|
|
654
|
-
try {
|
|
655
|
-
return {
|
|
656
|
-
ok: true,
|
|
657
|
-
value: await read()
|
|
658
|
-
};
|
|
659
|
-
} catch (error) {
|
|
660
|
-
return {
|
|
661
|
-
ok: false,
|
|
662
|
-
error: describeControlFailure(error)
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
//#endregion
|
|
667
549
|
//#region extensions/codex/src/app-server/thread-archive-guard.ts
|
|
668
550
|
const DESCENDANT_PAGE_LIMIT = 100;
|
|
669
551
|
const MAX_DESCENDANT_PAGES = 100;
|
|
@@ -714,29 +596,6 @@ async function assertCodexArchiveDescendantsUnowned(params) {
|
|
|
714
596
|
throw new Error("Codex descendant enumeration exceeded its safety limit");
|
|
715
597
|
}
|
|
716
598
|
//#endregion
|
|
717
|
-
//#region extensions/codex/src/session-catalog-create.ts
|
|
718
|
-
const CODEX_AGENT_RUNTIME_ID = "codex";
|
|
719
|
-
const CODEX_CATALOG_DEFAULT_MODEL_REF = "openai/gpt-5.6-sol";
|
|
720
|
-
function resolveCodexCatalogCreateSession(config, requestedAgentId) {
|
|
721
|
-
if (!config) return;
|
|
722
|
-
const agentId = requestedAgentId ?? resolveDefaultAgentId(config);
|
|
723
|
-
const defaultModel = resolveDefaultModelForAgent({
|
|
724
|
-
cfg: config,
|
|
725
|
-
agentId
|
|
726
|
-
});
|
|
727
|
-
return "error" in resolveAllowedModelRef({
|
|
728
|
-
cfg: config,
|
|
729
|
-
catalog: [],
|
|
730
|
-
raw: CODEX_CATALOG_DEFAULT_MODEL_REF,
|
|
731
|
-
defaultProvider: defaultModel.provider,
|
|
732
|
-
defaultModel: defaultModel.model,
|
|
733
|
-
agentId
|
|
734
|
-
}) ? void 0 : {
|
|
735
|
-
model: CODEX_CATALOG_DEFAULT_MODEL_REF,
|
|
736
|
-
agentRuntime: CODEX_AGENT_RUNTIME_ID
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
//#endregion
|
|
740
599
|
//#region extensions/codex/src/session-catalog-parsing.ts
|
|
741
600
|
const DEFAULT_PAGE_LIMIT = 50;
|
|
742
601
|
const CODEX_APP_SERVER_THREADS_CAPABILITY = "codex-app-server-threads";
|
|
@@ -962,7 +821,7 @@ function filterCatalogPageByTitle(page, searchTerm) {
|
|
|
962
821
|
if (!searchTerm) return page;
|
|
963
822
|
return {
|
|
964
823
|
...page,
|
|
965
|
-
sessions: page.sessions.filter((session) => session.name?.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()))
|
|
824
|
+
sessions: page.sessions.filter((session) => (session.name ?? session.fallbackName)?.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()))
|
|
966
825
|
};
|
|
967
826
|
}
|
|
968
827
|
function unwrapNodeInvokePayload(value) {
|
|
@@ -1030,24 +889,9 @@ function adoptionSessionKeyRest(sessionKey) {
|
|
|
1030
889
|
const trimmed = sessionKey.trim();
|
|
1031
890
|
return parseAgentSessionKey(trimmed)?.rest ?? trimmed;
|
|
1032
891
|
}
|
|
1033
|
-
function listSupervisionAgentIds(config) {
|
|
1034
|
-
const defaultAgentId = resolveDefaultAgentId(config);
|
|
1035
|
-
return [defaultAgentId, ...listAgentIds(config).filter((agentId) => agentId !== defaultAgentId)];
|
|
1036
|
-
}
|
|
1037
|
-
function adoptedSourceKey(hostId, threadId) {
|
|
1038
|
-
return `${hostId}\u0000${threadId}`;
|
|
1039
|
-
}
|
|
1040
|
-
function lastTerminalTurnId(thread) {
|
|
1041
|
-
for (let index = (thread.turns?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
1042
|
-
const turn = thread.turns?.[index];
|
|
1043
|
-
const turnId = boundedCatalogString(turn?.id, 256);
|
|
1044
|
-
if (!turnId) continue;
|
|
1045
|
-
if (turn?.status === "completed" || turn?.status === "interrupted" || turn?.status === "failed") return turnId;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
892
|
function nodeAdoptionSessionKey(hostId, threadId) {
|
|
1049
|
-
const
|
|
1050
|
-
return
|
|
893
|
+
const source = JSON.stringify([hostId, threadId]);
|
|
894
|
+
return sessionCatalogAdoptedSessionKey(CODEX_NODE_SESSION_KEY_PREFIX, source);
|
|
1051
895
|
}
|
|
1052
896
|
function readNodeSessionMarker(entry) {
|
|
1053
897
|
const codex = isRecord(entry.pluginExtensions?.codex) ? entry.pluginExtensions.codex : void 0;
|
|
@@ -1063,6 +907,7 @@ function readNodeSessionMarker(entry) {
|
|
|
1063
907
|
function listNodeAdoptedSessionEntries(params) {
|
|
1064
908
|
const adopted = /* @__PURE__ */ new Map();
|
|
1065
909
|
for (const { agentId, entry, sessionKey } of listSessionCatalogEntries({
|
|
910
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
1066
911
|
config: params.config ?? {},
|
|
1067
912
|
runtime: params.runtime,
|
|
1068
913
|
sessionEntries: params.sessionEntries
|
|
@@ -1070,7 +915,7 @@ function listNodeAdoptedSessionEntries(params) {
|
|
|
1070
915
|
const marker = readNodeSessionMarker(entry);
|
|
1071
916
|
const sessionId = entry.sessionId?.trim();
|
|
1072
917
|
if (!marker || marker.initializing === true && params.includeInitializing !== true || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true || !sessionId || adoptionSessionKeyRest(sessionKey) !== nodeAdoptionSessionKey(marker.sourceHostId, marker.sourceThreadId) || marker.sourceHostId !== `node:${marker.nodeId}`) continue;
|
|
1073
|
-
const sourceKey =
|
|
918
|
+
const sourceKey = sessionCatalogAdoptedSourceKey(marker.sourceHostId, marker.sourceThreadId);
|
|
1074
919
|
if (adopted.has(sourceKey)) throw new Error(`multiple OpenClaw sessions adopt Codex thread ${marker.sourceThreadId} on ${marker.sourceHostId}`);
|
|
1075
920
|
adopted.set(sourceKey, {
|
|
1076
921
|
key: sessionKey,
|
|
@@ -1082,7 +927,7 @@ function listNodeAdoptedSessionEntries(params) {
|
|
|
1082
927
|
return adopted;
|
|
1083
928
|
}
|
|
1084
929
|
function findNodeAdoptedSessionEntry(params) {
|
|
1085
|
-
return listNodeAdoptedSessionEntries(params).get(
|
|
930
|
+
return listNodeAdoptedSessionEntries(params).get(sessionCatalogAdoptedSourceKey(params.hostId, params.threadId));
|
|
1086
931
|
}
|
|
1087
932
|
function nodeSessionMarker(params) {
|
|
1088
933
|
return {
|
|
@@ -1130,6 +975,7 @@ async function finalizeNodeAdoptedSession(params) {
|
|
|
1130
975
|
}
|
|
1131
976
|
async function createOrReuseNodeAdoptedSession(params) {
|
|
1132
977
|
const existing = findNodeAdoptedSessionEntry({
|
|
978
|
+
agentId: params.agentId,
|
|
1133
979
|
config: params.config,
|
|
1134
980
|
runtime: params.api.runtime,
|
|
1135
981
|
hostId: params.hostId,
|
|
@@ -1149,7 +995,7 @@ async function createOrReuseNodeAdoptedSession(params) {
|
|
|
1149
995
|
const created = await params.api.runtime.agent.session.createSessionEntry({
|
|
1150
996
|
cfg: params.config,
|
|
1151
997
|
key: nodeAdoptionSessionKey(params.hostId, params.record.threadId),
|
|
1152
|
-
agentId:
|
|
998
|
+
agentId: params.agentId,
|
|
1153
999
|
recoverMatchingInitialEntry: true,
|
|
1154
1000
|
...params.record.name?.trim() ? { label: params.record.name.trim() } : {},
|
|
1155
1001
|
...params.record.cwd?.trim() ? { spawnedCwd: params.record.cwd.trim() } : {},
|
|
@@ -1182,6 +1028,7 @@ async function createOrReuseNodeAdoptedSession(params) {
|
|
|
1182
1028
|
};
|
|
1183
1029
|
} catch (error) {
|
|
1184
1030
|
const raced = findNodeAdoptedSessionEntry({
|
|
1031
|
+
agentId: params.agentId,
|
|
1185
1032
|
config: params.config,
|
|
1186
1033
|
runtime: params.api.runtime,
|
|
1187
1034
|
hostId: params.hostId,
|
|
@@ -1193,346 +1040,127 @@ async function createOrReuseNodeAdoptedSession(params) {
|
|
|
1193
1040
|
}
|
|
1194
1041
|
}
|
|
1195
1042
|
//#endregion
|
|
1196
|
-
//#region extensions/codex/src/session-catalog-
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
function nodeLabel(node) {
|
|
1204
|
-
return node.displayName?.trim() || node.remoteIp?.trim() || node.nodeId;
|
|
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);
|
|
1205
1050
|
}
|
|
1206
|
-
function
|
|
1207
|
-
|
|
1208
|
-
const rightLabel = nodeLabel(right);
|
|
1209
|
-
if (leftLabel < rightLabel) return -1;
|
|
1210
|
-
if (leftLabel > rightLabel) return 1;
|
|
1211
|
-
return 0;
|
|
1051
|
+
function resolveLocalCodexTerminalExecutable(env = process.env) {
|
|
1052
|
+
return resolveLocalCodexTerminalResolution(env)?.executable;
|
|
1212
1053
|
}
|
|
1213
|
-
function
|
|
1214
|
-
return
|
|
1054
|
+
function resolveLocalCodexTerminalResolution(env = process.env) {
|
|
1055
|
+
return resolveNodeHostExecutable("codex", {
|
|
1056
|
+
env,
|
|
1057
|
+
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1058
|
+
strategy: "fallback"
|
|
1059
|
+
});
|
|
1215
1060
|
}
|
|
1216
|
-
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
},
|
|
1247
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1248
|
-
scopes: ["operator.write"]
|
|
1249
|
-
}))), params.query.search);
|
|
1250
|
-
return {
|
|
1251
|
-
...common,
|
|
1252
|
-
connected: true,
|
|
1253
|
-
...page,
|
|
1254
|
-
sessions: page.sessions.map((session) => {
|
|
1255
|
-
const adopted = params.adoptedSessions.get(adoptedSourceKey(hostId, session.threadId));
|
|
1256
|
-
return adopted ? Object.assign({}, session, { sessionKey: adopted.key }) : session;
|
|
1257
|
-
})
|
|
1258
|
-
};
|
|
1259
|
-
}).catch((error) => ({
|
|
1260
|
-
...common,
|
|
1261
|
-
connected: true,
|
|
1262
|
-
sessions: [],
|
|
1263
|
-
error: catalogError("NODE_INVOKE_FAILED", error)
|
|
1264
|
-
}));
|
|
1265
|
-
if (params.onHost) eventualHost.then(params.onHost).catch(() => void 0);
|
|
1266
|
-
try {
|
|
1267
|
-
return await withTimeout(eventualHost, NODE_CATALOG_LIST_RESPONSE_TIMEOUT_MS, "paired node Codex session catalog timed out");
|
|
1268
|
-
} catch (error) {
|
|
1269
|
-
return {
|
|
1270
|
-
...common,
|
|
1271
|
-
connected: true,
|
|
1272
|
-
sessions: [],
|
|
1273
|
-
error: catalogError("NODE_INVOKE_FAILED", error)
|
|
1274
|
-
};
|
|
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;
|
|
1275
1091
|
}
|
|
1092
|
+
throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1276
1093
|
}
|
|
1277
|
-
|
|
1278
|
-
const nodeId = params.hostId.slice(5).trim();
|
|
1279
|
-
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1280
|
-
const node = (await params.runtime.nodes.list()).nodes.find((candidate) => candidate.nodeId === nodeId);
|
|
1281
|
-
if (!node || !canContinueCodexOnNode(node)) throw new CatalogParamsError("paired node does not permit Codex session continuation");
|
|
1094
|
+
function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
|
|
1282
1095
|
return {
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
+
}
|
|
1285
1131
|
};
|
|
1286
1132
|
}
|
|
1287
|
-
async function
|
|
1133
|
+
async function resolveNodeCatalogEligibleThread(params) {
|
|
1288
1134
|
let cursor;
|
|
1289
1135
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
1290
1136
|
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1291
|
-
const
|
|
1137
|
+
const raw = await params.runtime.nodes.invoke({
|
|
1292
1138
|
nodeId: params.nodeId,
|
|
1293
1139
|
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1294
1140
|
params: {
|
|
1141
|
+
agentId: params.agentId,
|
|
1295
1142
|
limit: 100,
|
|
1296
1143
|
...cursor ? { cursor } : {}
|
|
1297
1144
|
},
|
|
1298
1145
|
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1299
1146
|
scopes: ["operator.write"]
|
|
1300
|
-
})
|
|
1147
|
+
});
|
|
1148
|
+
const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
|
|
1301
1149
|
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1302
|
-
if (record)
|
|
1150
|
+
if (record) {
|
|
1151
|
+
if (isInteractiveThreadSource(record.source)) return record;
|
|
1152
|
+
break;
|
|
1153
|
+
}
|
|
1303
1154
|
const nextCursor = page.nextCursor?.trim();
|
|
1304
|
-
if (!nextCursor) break;
|
|
1305
|
-
if (seenCursors.has(nextCursor)) throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1155
|
+
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
1306
1156
|
seenCursors.add(nextCursor);
|
|
1307
1157
|
cursor = nextCursor;
|
|
1308
1158
|
}
|
|
1309
|
-
throw new CatalogParamsError("Codex session is
|
|
1310
|
-
}
|
|
1311
|
-
function requireContinuableNodeRecord(record) {
|
|
1312
|
-
if (record.archived) throw new CatalogParamsError("Codex session is archived on the paired node");
|
|
1313
|
-
if (!isInteractiveThreadSource(record.source)) throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1314
|
-
if (record.status === "idle" || record.status === "notLoaded") return;
|
|
1315
|
-
if (record.status === "active") throw new CatalogParamsError("Codex session is active on the paired node; wait for it to finish before continuing");
|
|
1316
|
-
throw new CatalogParamsError("Codex session cannot be continued in its current state");
|
|
1317
|
-
}
|
|
1318
|
-
async function readNodeCodexHistory(params) {
|
|
1319
|
-
const page = parseTranscriptPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1320
|
-
nodeId: params.nodeId,
|
|
1321
|
-
command: CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
1322
|
-
params: {
|
|
1323
|
-
threadId: params.record.threadId,
|
|
1324
|
-
limit: 50
|
|
1325
|
-
},
|
|
1326
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1327
|
-
scopes: ["operator.write"]
|
|
1328
|
-
})));
|
|
1329
|
-
const thread = {
|
|
1330
|
-
id: params.record.threadId,
|
|
1331
|
-
createdAt: params.record.createdAt ?? 0,
|
|
1332
|
-
modelProvider: params.record.modelProvider ?? "openai",
|
|
1333
|
-
turns: page.data.toReversed()
|
|
1334
|
-
};
|
|
1335
|
-
return {
|
|
1336
|
-
thread,
|
|
1337
|
-
throughTurnId: lastTerminalTurnId(thread) ?? null
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
async function continueNodeCodexSessionInner(params) {
|
|
1341
|
-
const { nodeId } = await requireNodeForCodexContinue({
|
|
1342
|
-
runtime: params.api.runtime,
|
|
1343
|
-
hostId: params.hostId
|
|
1344
|
-
});
|
|
1345
|
-
const record = await resolveNodeCodexRecord({
|
|
1346
|
-
runtime: params.api.runtime,
|
|
1347
|
-
nodeId,
|
|
1348
|
-
threadId: params.threadId
|
|
1349
|
-
});
|
|
1350
|
-
requireContinuableNodeRecord(record);
|
|
1351
|
-
const existing = findNodeAdoptedSessionEntry({
|
|
1352
|
-
config: params.config,
|
|
1353
|
-
runtime: params.api.runtime,
|
|
1354
|
-
hostId: params.hostId,
|
|
1355
|
-
threadId: params.threadId,
|
|
1356
|
-
includeInitializing: true
|
|
1357
|
-
});
|
|
1358
|
-
let adopted;
|
|
1359
|
-
let disposition;
|
|
1360
|
-
if (existing) {
|
|
1361
|
-
adopted = existing;
|
|
1362
|
-
disposition = "existing";
|
|
1363
|
-
} else {
|
|
1364
|
-
const history = await readNodeCodexHistory({
|
|
1365
|
-
runtime: params.api.runtime,
|
|
1366
|
-
nodeId,
|
|
1367
|
-
record
|
|
1368
|
-
});
|
|
1369
|
-
adopted = await createOrReuseNodeAdoptedSession({
|
|
1370
|
-
api: params.api,
|
|
1371
|
-
config: params.config,
|
|
1372
|
-
hostId: params.hostId,
|
|
1373
|
-
nodeId,
|
|
1374
|
-
record,
|
|
1375
|
-
history
|
|
1376
|
-
});
|
|
1377
|
-
disposition = "forked";
|
|
1378
|
-
}
|
|
1379
|
-
const marker = nodeSessionMarker({
|
|
1380
|
-
hostId: params.hostId,
|
|
1381
|
-
threadId: params.threadId,
|
|
1382
|
-
nodeId
|
|
1383
|
-
});
|
|
1384
|
-
return {
|
|
1385
|
-
sessionKey: adopted.key,
|
|
1386
|
-
disposition,
|
|
1387
|
-
conversationBinding: {
|
|
1388
|
-
summary: "Continue this Codex session on its paired node.",
|
|
1389
|
-
detachHint: "Start a new chat to leave the paired-node Codex session.",
|
|
1390
|
-
data: createCodexCliNodeConversationBindingData({
|
|
1391
|
-
nodeId,
|
|
1392
|
-
sessionId: record.sessionId?.trim() || params.threadId,
|
|
1393
|
-
agentId: adopted.agentId,
|
|
1394
|
-
cwd: record.cwd
|
|
1395
|
-
})
|
|
1396
|
-
},
|
|
1397
|
-
afterConversationBound: async () => await finalizeNodeAdoptedSession({
|
|
1398
|
-
api: params.api,
|
|
1399
|
-
adopted,
|
|
1400
|
-
marker
|
|
1401
|
-
})
|
|
1402
|
-
};
|
|
1403
|
-
}
|
|
1404
|
-
async function continueNodeCodexSession(params) {
|
|
1405
|
-
if (params.clientScopes?.includes("operator.admin") !== true) throw new CatalogParamsError("continuing a paired-node Codex session requires operator.admin");
|
|
1406
|
-
const nodeId = params.hostId.slice(5).trim();
|
|
1407
|
-
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1408
|
-
const sourceKey = adoptedSourceKey(`node:${nodeId}`, params.threadId);
|
|
1409
|
-
const current = continueOperations.get(sourceKey);
|
|
1410
|
-
if (current) return await current;
|
|
1411
|
-
const operation = runSessionActionExclusive(sourceKey, async () => continueNodeCodexSessionInner(params));
|
|
1412
|
-
continueOperations.set(sourceKey, operation);
|
|
1413
|
-
try {
|
|
1414
|
-
return await operation;
|
|
1415
|
-
} finally {
|
|
1416
|
-
if (continueOperations.get(sourceKey) === operation) continueOperations.delete(sourceKey);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
//#endregion
|
|
1420
|
-
//#region extensions/codex/src/session-catalog-terminal.ts
|
|
1421
|
-
const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1";
|
|
1422
|
-
function resolveCodexCatalogTerminalHome(sources) {
|
|
1423
|
-
const runtimeConfig = sources.getRuntimeConfig();
|
|
1424
|
-
if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
|
|
1425
|
-
const startOptions = resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig: sources.getPluginConfig() }).start;
|
|
1426
|
-
return resolveCodexAppServerLocalHomeDir(startOptions, resolveDefaultAgentDir(runtimeConfig));
|
|
1427
|
-
}
|
|
1428
|
-
function resolveLocalCodexTerminalExecutable(env = process.env) {
|
|
1429
|
-
return resolveLocalCodexTerminalResolution(env)?.executable;
|
|
1430
|
-
}
|
|
1431
|
-
function resolveLocalCodexTerminalResolution(env = process.env) {
|
|
1432
|
-
return resolveNodeHostExecutable("codex", {
|
|
1433
|
-
env,
|
|
1434
|
-
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1435
|
-
strategy: "fallback"
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
function codexNodeTerminalCapability(node) {
|
|
1439
|
-
const commands = node.invocableCommands ?? node.commands;
|
|
1440
|
-
return node.connected === true && commands?.includes("codex.terminal.resume.v1") === true ? { canOpenTerminalCodex: true } : {};
|
|
1441
|
-
}
|
|
1442
|
-
async function requireCatalogEligibleThread(control, threadId) {
|
|
1443
|
-
const cached = await findCatalogEligibleThread(control, threadId, false);
|
|
1444
|
-
if (cached) return cached;
|
|
1445
|
-
const refreshed = await findCatalogEligibleThread(control, threadId, true);
|
|
1446
|
-
if (refreshed) return refreshed;
|
|
1447
|
-
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1448
|
-
}
|
|
1449
|
-
async function findCatalogEligibleThread(control, threadId, forceRefresh) {
|
|
1450
|
-
let cursor;
|
|
1451
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
1452
|
-
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1453
|
-
const page = await control.listPage({
|
|
1454
|
-
limit: 100,
|
|
1455
|
-
...cursor ? { cursor } : {},
|
|
1456
|
-
...forceRefresh ? { forceRefresh: true } : {}
|
|
1457
|
-
});
|
|
1458
|
-
const candidate = page.sessions.find((session) => session.threadId === threadId);
|
|
1459
|
-
if (candidate) {
|
|
1460
|
-
if (isInteractiveThreadSource(candidate.source)) return candidate;
|
|
1461
|
-
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1462
|
-
}
|
|
1463
|
-
const nextCursor = page.nextCursor?.trim();
|
|
1464
|
-
if (!nextCursor) return;
|
|
1465
|
-
if (seenCursors.has(nextCursor)) throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1466
|
-
seenCursors.add(nextCursor);
|
|
1467
|
-
cursor = nextCursor;
|
|
1468
|
-
}
|
|
1469
|
-
throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1470
|
-
}
|
|
1471
|
-
function createCodexTerminalNodeHostCommand(control, configSources) {
|
|
1472
|
-
return {
|
|
1473
|
-
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1474
|
-
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1475
|
-
dangerous: false,
|
|
1476
|
-
duplex: true,
|
|
1477
|
-
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1478
|
-
env,
|
|
1479
|
-
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1480
|
-
strategy: "direct"
|
|
1481
|
-
})),
|
|
1482
|
-
handle: async (paramsJSON, io) => {
|
|
1483
|
-
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1484
|
-
const resume = decodeNodePtyResumeParams(paramsJSON, (value) => {
|
|
1485
|
-
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");
|
|
1486
|
-
return value;
|
|
1487
|
-
});
|
|
1488
|
-
const record = await requireCatalogEligibleThread(control, resume.threadId);
|
|
1489
|
-
const resolution = resolveNodeHostExecutable("codex", {
|
|
1490
|
-
env: process.env,
|
|
1491
|
-
pathEnv: process.env.PATH ?? process.env.Path ?? "",
|
|
1492
|
-
strategy: "direct"
|
|
1493
|
-
});
|
|
1494
|
-
if (!resolution) throw new Error("Codex CLI is unavailable");
|
|
1495
|
-
return JSON.stringify(await runNodePtyCommand({
|
|
1496
|
-
file: resolution.executable,
|
|
1497
|
-
args: ["resume", resume.threadId],
|
|
1498
|
-
cwd: record.cwd,
|
|
1499
|
-
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(configSources) },
|
|
1500
|
-
cols: resume.cols,
|
|
1501
|
-
rows: resume.rows
|
|
1502
|
-
}, io));
|
|
1503
|
-
}
|
|
1504
|
-
};
|
|
1505
|
-
}
|
|
1506
|
-
async function resolveNodeCatalogEligibleThread(params) {
|
|
1507
|
-
let cursor;
|
|
1508
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
1509
|
-
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1510
|
-
const raw = await params.runtime.nodes.invoke({
|
|
1511
|
-
nodeId: params.nodeId,
|
|
1512
|
-
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1513
|
-
params: {
|
|
1514
|
-
limit: 100,
|
|
1515
|
-
...cursor ? { cursor } : {}
|
|
1516
|
-
},
|
|
1517
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1518
|
-
scopes: ["operator.write"]
|
|
1519
|
-
});
|
|
1520
|
-
const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
|
|
1521
|
-
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1522
|
-
if (record) {
|
|
1523
|
-
if (isInteractiveThreadSource(record.source)) return record;
|
|
1524
|
-
break;
|
|
1525
|
-
}
|
|
1526
|
-
const nextCursor = page.nextCursor?.trim();
|
|
1527
|
-
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
1528
|
-
seenCursors.add(nextCursor);
|
|
1529
|
-
cursor = nextCursor;
|
|
1530
|
-
}
|
|
1531
|
-
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1159
|
+
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1532
1160
|
}
|
|
1533
1161
|
async function openCodexCatalogTerminal(params) {
|
|
1534
1162
|
const title = `codex resume ${params.threadId.slice(0, 8)}…`;
|
|
1535
|
-
if (params.hostId === "gateway:local") {
|
|
1163
|
+
if (params.hostId === "gateway:local" || params.hostId.startsWith(`gateway:local:`)) {
|
|
1536
1164
|
const record = await requireCatalogEligibleThread(params.control, params.threadId);
|
|
1537
1165
|
const resolution = resolveLocalCodexTerminalResolution();
|
|
1538
1166
|
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable");
|
|
@@ -1556,6 +1184,7 @@ async function openCodexCatalogTerminal(params) {
|
|
|
1556
1184
|
return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("codex.appServer.threads.list.v1") === true && commands.includes("codex.terminal.resume.v1");
|
|
1557
1185
|
})) throw new CatalogParamsError("paired-node Codex terminal is unavailable");
|
|
1558
1186
|
const record = await resolveNodeCatalogEligibleThread({
|
|
1187
|
+
agentId: params.agentId,
|
|
1559
1188
|
runtime: params.api.runtime,
|
|
1560
1189
|
nodeId,
|
|
1561
1190
|
threadId: params.threadId,
|
|
@@ -1565,7 +1194,10 @@ async function openCodexCatalogTerminal(params) {
|
|
|
1565
1194
|
kind: "node",
|
|
1566
1195
|
nodeId,
|
|
1567
1196
|
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1568
|
-
paramsJSON: JSON.stringify({
|
|
1197
|
+
paramsJSON: JSON.stringify({
|
|
1198
|
+
agentId: params.agentId,
|
|
1199
|
+
threadId: params.threadId
|
|
1200
|
+
}),
|
|
1569
1201
|
...record.cwd ? { cwd: record.cwd } : {},
|
|
1570
1202
|
title
|
|
1571
1203
|
};
|
|
@@ -1584,480 +1216,685 @@ async function startCodexCatalogTerminal(params) {
|
|
|
1584
1216
|
};
|
|
1585
1217
|
}
|
|
1586
1218
|
//#endregion
|
|
1587
|
-
//#region extensions/codex/src/session-catalog-
|
|
1588
|
-
const
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
"commandExecution",
|
|
1595
|
-
"fileChange",
|
|
1596
|
-
"mcpToolCall",
|
|
1597
|
-
"dynamicToolCall",
|
|
1598
|
-
"collabAgentToolCall",
|
|
1599
|
-
"webSearch",
|
|
1600
|
-
"imageView",
|
|
1601
|
-
"imageGeneration"
|
|
1602
|
-
]);
|
|
1603
|
-
function toGenericTranscriptItem(item) {
|
|
1604
|
-
let type = CODEX_MESSAGE_TYPES.get(item.type);
|
|
1605
|
-
if (!type && CODEX_TOOL_TYPES.has(item.type)) type = item.result !== void 0 || Boolean(item.aggregatedOutput) ? "toolResult" : "toolCall";
|
|
1606
|
-
type ??= "other";
|
|
1607
|
-
const fallback = item.title ?? item.name ?? item.tool ?? item.command ?? item.query ?? void 0;
|
|
1608
|
-
const resultText = item.aggregatedOutput || (item.result === void 0 ? void 0 : JSON.stringify(item.result, null, 2));
|
|
1609
|
-
const changesText = Array.isArray(item.changes) ? item.changes.map((change) => `${change.kind}: ${change.path}`).join("\n") || void 0 : void 0;
|
|
1610
|
-
const text = item.text || resultText || changesText || fallback;
|
|
1611
|
-
return {
|
|
1612
|
-
id: item.id,
|
|
1613
|
-
type,
|
|
1614
|
-
...text ? { text } : {},
|
|
1615
|
-
raw: item
|
|
1616
|
-
};
|
|
1219
|
+
//#region extensions/codex/src/session-catalog-adoption.ts
|
|
1220
|
+
const CODEX_SUPERVISION_SESSION_KEY_PREFIX = "harness:codex:supervision:";
|
|
1221
|
+
const boundCatalogSessionId = (value) => boundedCatalogString(value, 256);
|
|
1222
|
+
function requireIdleThread(thread, action) {
|
|
1223
|
+
if (thread.status?.type === "idle" || action === "archive" && thread.status?.type === "notLoaded") return;
|
|
1224
|
+
if (thread.status?.type === "active") throw new CatalogParamsError(`Codex session is active in this App Server; wait for it to finish before ${action === "continue" ? "starting a branch" : "archiving"}`);
|
|
1225
|
+
throw new CatalogParamsError(action === "archive" ? "Codex session cannot be archived in its current state" : "Codex session cannot start a branch in its current state");
|
|
1617
1226
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
const CODEX_APP_SERVER_INVALID_REQUEST_CODE = -32600;
|
|
1622
|
-
const CODEX_THREAD_NOT_LOADED_MESSAGE_PREFIX = "thread not loaded:";
|
|
1623
|
-
function isCodexThreadGoneError(error) {
|
|
1624
|
-
return error instanceof CodexAppServerRpcError && error.code === CODEX_APP_SERVER_INVALID_REQUEST_CODE && error.message.startsWith(CODEX_THREAD_NOT_LOADED_MESSAGE_PREFIX);
|
|
1227
|
+
function adoptionSessionKey(threadId, sourceHomeId) {
|
|
1228
|
+
const source = sourceHomeId ? JSON.stringify([sourceHomeId, threadId]) : threadId;
|
|
1229
|
+
return sessionCatalogAdoptedSessionKey(CODEX_SUPERVISION_SESSION_KEY_PREFIX, source);
|
|
1625
1230
|
}
|
|
1626
|
-
function
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1231
|
+
function isAdoptionSessionKeyForThread(sessionKey, threadId, sourceHomeId) {
|
|
1232
|
+
return adoptionSessionKeyRest(sessionKey) === adoptionSessionKey(threadId, sourceHomeId);
|
|
1233
|
+
}
|
|
1234
|
+
function readCodexSupervisionMarker(entry) {
|
|
1235
|
+
const codex = isRecord(entry.pluginExtensions?.codex) ? entry.pluginExtensions.codex : void 0;
|
|
1236
|
+
const marker = codex && isRecord(codex.supervision) ? codex.supervision : void 0;
|
|
1237
|
+
const sourceThreadId = marker?.sourceThreadId;
|
|
1238
|
+
const sourceHomeId = marker?.sourceHomeId;
|
|
1239
|
+
if (typeof sourceThreadId !== "string" || !sourceThreadId.trim() || sourceHomeId !== void 0 && (typeof sourceHomeId !== "string" || !sourceHomeId.trim())) return;
|
|
1632
1240
|
return {
|
|
1633
|
-
|
|
1634
|
-
...
|
|
1241
|
+
sourceThreadId: sourceThreadId.trim(),
|
|
1242
|
+
...typeof sourceHomeId === "string" ? { sourceHomeId: sourceHomeId.trim() } : {}
|
|
1635
1243
|
};
|
|
1636
1244
|
}
|
|
1637
|
-
function
|
|
1638
|
-
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1245
|
+
async function listAdoptedSessionEntries(params) {
|
|
1246
|
+
const adopted = /* @__PURE__ */ new Map();
|
|
1247
|
+
for (const { agentId, entry, sessionKey } of listSessionCatalogEntries({
|
|
1248
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
1249
|
+
config: params.config ?? {},
|
|
1250
|
+
runtime: params.runtime,
|
|
1251
|
+
sessionEntries: params.sessionEntries
|
|
1252
|
+
})) {
|
|
1253
|
+
const sessionKeyRest = adoptionSessionKeyRest(sessionKey);
|
|
1254
|
+
const marker = readCodexSupervisionMarker(entry);
|
|
1255
|
+
if (!sessionKeyRest.startsWith(CODEX_SUPERVISION_SESSION_KEY_PREFIX) || !marker || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true) continue;
|
|
1256
|
+
const sessionId = entry.sessionId?.trim();
|
|
1257
|
+
if (!sessionId) continue;
|
|
1258
|
+
const binding = await params.bindingStore.read(sessionBindingIdentity({
|
|
1259
|
+
sessionId,
|
|
1260
|
+
sessionKey,
|
|
1261
|
+
config: params.config
|
|
1262
|
+
}));
|
|
1263
|
+
const sourceThreadId = binding?.supervisionSourceThreadId?.trim();
|
|
1264
|
+
const boundThreadId = binding?.threadId.trim();
|
|
1265
|
+
if (binding?.connectionScope !== "supervision" || !sourceThreadId || !boundThreadId || sessionKeyRest !== adoptionSessionKey(sourceThreadId, marker.sourceHomeId)) continue;
|
|
1266
|
+
const sourceKey = sessionCatalogAdoptedSourceKey(marker.sourceHomeId ?? "gateway:local", sourceThreadId);
|
|
1267
|
+
if (adopted.has(sourceKey)) throw new Error(`multiple OpenClaw sessions adopt Codex thread ${sourceThreadId} from the same home`);
|
|
1268
|
+
adopted.set(sourceKey, {
|
|
1269
|
+
key: sessionKey,
|
|
1270
|
+
sessionId,
|
|
1271
|
+
agentId,
|
|
1272
|
+
boundThreadId
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
return adopted;
|
|
1276
|
+
}
|
|
1277
|
+
async function findAdoptedSessionEntry(params) {
|
|
1278
|
+
const adopted = await listAdoptedSessionEntries(params);
|
|
1279
|
+
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
|
+
}
|
|
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
|
+
function matchesPendingAdoptionBinding(binding, expected) {
|
|
1308
|
+
const historyCoveredThrough = binding?.historyCoveredThrough;
|
|
1309
|
+
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
|
+
}
|
|
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
|
+
async function ensurePendingAdoptionBinding(params) {
|
|
1318
|
+
const pending = {
|
|
1319
|
+
sourceThreadId: params.sourceThreadId,
|
|
1320
|
+
connectionFingerprint: params.connectionFingerprint,
|
|
1321
|
+
...params.lastTurnId ? { lastTurnId: params.lastTurnId } : {}
|
|
1322
|
+
};
|
|
1323
|
+
if (!await reclaimCurrentCodexSessionGeneration({
|
|
1324
|
+
bindingStore: params.bindingStore,
|
|
1325
|
+
identity: params.identity,
|
|
1326
|
+
config: params.config
|
|
1327
|
+
})) throw new Error(`failed to claim the OpenClaw session generation for ${params.sourceThreadId}`);
|
|
1328
|
+
const existing = await params.bindingStore.read(params.identity);
|
|
1329
|
+
if (existing) {
|
|
1330
|
+
if (matchesPendingAdoptionBinding(existing, params)) return;
|
|
1331
|
+
throw new Error(`OpenClaw session is already bound to Codex thread ${existing.threadId}`);
|
|
1332
|
+
}
|
|
1333
|
+
const binding = {
|
|
1334
|
+
threadId: params.sourceThreadId,
|
|
1335
|
+
connectionScope: "supervision",
|
|
1336
|
+
supervisionSourceThreadId: params.sourceThreadId,
|
|
1337
|
+
cwd: params.cwd,
|
|
1338
|
+
historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1339
|
+
conversationSourceTransferComplete: true,
|
|
1340
|
+
preserveNativeModel: true,
|
|
1341
|
+
pendingSupervisionBranch: pending
|
|
1342
|
+
};
|
|
1343
|
+
let stored;
|
|
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}`);
|
|
1356
|
+
}
|
|
1357
|
+
async function createOrReuseAdoptedSession(params) {
|
|
1358
|
+
const runtime = params.api.runtime;
|
|
1359
|
+
const lookup = {
|
|
1360
|
+
...params,
|
|
1361
|
+
runtime,
|
|
1362
|
+
threadId: params.sourceThread.id
|
|
1363
|
+
};
|
|
1364
|
+
const existing = await findAdoptedSessionEntry(lookup);
|
|
1365
|
+
if (existing) return existing;
|
|
1366
|
+
let createdBindingIdentity;
|
|
1367
|
+
let createdPendingBinding;
|
|
1368
|
+
try {
|
|
1369
|
+
const spawnedCwd = params.sourceThread.cwd?.trim() || void 0;
|
|
1370
|
+
const pendingLastTurnId = codexLastTerminalTurnId(params.sourceThread, boundCatalogSessionId);
|
|
1371
|
+
const marker = {
|
|
1372
|
+
sourceThreadId: params.sourceThread.id,
|
|
1373
|
+
...params.sourceHomeId ? { sourceHomeId: params.sourceHomeId } : {}
|
|
1374
|
+
};
|
|
1375
|
+
const created = await createImportedCodexSession({
|
|
1376
|
+
runtime: params.api.runtime,
|
|
1377
|
+
config: params.config,
|
|
1378
|
+
key: adoptionSessionKey(params.sourceThread.id, params.sourceHomeId),
|
|
1379
|
+
agentId: params.agentId,
|
|
1380
|
+
thread: params.sourceThread,
|
|
1381
|
+
throughTurnId: pendingLastTurnId ?? null,
|
|
1382
|
+
recoverMatchingInitialEntry: true,
|
|
1383
|
+
initialEntry: {
|
|
1384
|
+
agentHarnessId: "codex",
|
|
1385
|
+
modelSelectionLocked: true,
|
|
1386
|
+
pluginExtensions: { codex: { supervision: {
|
|
1387
|
+
...marker,
|
|
1388
|
+
initializing: true,
|
|
1389
|
+
modelLocked: true
|
|
1390
|
+
} } }
|
|
1391
|
+
},
|
|
1392
|
+
afterImport: async (entry) => {
|
|
1393
|
+
createdBindingIdentity = sessionBindingIdentity({
|
|
1394
|
+
sessionId: entry.sessionId,
|
|
1395
|
+
sessionKey: entry.key,
|
|
1396
|
+
config: params.config
|
|
1397
|
+
});
|
|
1398
|
+
createdPendingBinding = {
|
|
1399
|
+
sourceThreadId: params.sourceThread.id,
|
|
1400
|
+
connectionFingerprint: params.connectionFingerprint,
|
|
1401
|
+
...pendingLastTurnId ? { lastTurnId: pendingLastTurnId } : {}
|
|
1402
|
+
};
|
|
1403
|
+
await ensurePendingAdoptionBinding({
|
|
1404
|
+
bindingStore: params.bindingStore,
|
|
1405
|
+
config: params.config,
|
|
1406
|
+
identity: createdBindingIdentity,
|
|
1407
|
+
sourceThreadId: params.sourceThread.id,
|
|
1408
|
+
connectionFingerprint: params.connectionFingerprint,
|
|
1409
|
+
cwd: spawnedCwd ?? "",
|
|
1410
|
+
...pendingLastTurnId ? { lastTurnId: pendingLastTurnId } : {}
|
|
1411
|
+
});
|
|
1412
|
+
return { pluginExtensions: { codex: { supervision: {
|
|
1413
|
+
...marker,
|
|
1414
|
+
modelLocked: true
|
|
1415
|
+
} } } };
|
|
1662
1416
|
}
|
|
1417
|
+
});
|
|
1418
|
+
return {
|
|
1419
|
+
key: created.key,
|
|
1420
|
+
sessionId: created.sessionId,
|
|
1421
|
+
agentId: created.agentId,
|
|
1422
|
+
boundThreadId: params.sourceThread.id
|
|
1423
|
+
};
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
let raced = await findAdoptedSessionEntry(lookup);
|
|
1426
|
+
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;
|
|
1663
1437
|
}
|
|
1438
|
+
throw error;
|
|
1664
1439
|
}
|
|
1665
|
-
|
|
1440
|
+
}
|
|
1441
|
+
async function continueLocalCodexSessionInner(params) {
|
|
1442
|
+
await requireCatalogEligibleThread(params.control, params.threadId);
|
|
1443
|
+
const existing = await findAdoptedSessionEntry({
|
|
1444
|
+
...params,
|
|
1445
|
+
runtime: params.api.runtime
|
|
1446
|
+
});
|
|
1447
|
+
if (existing) {
|
|
1448
|
+
const boundThreadId = requireBoundThread(existing);
|
|
1449
|
+
const boundThread = await params.control.readThread(boundThreadId, true);
|
|
1450
|
+
if (boundThread.id !== boundThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1451
|
+
const changedError = () => new CatalogParamsError("Codex OpenClaw session changed before it could be opened. Retry.");
|
|
1452
|
+
if (!await params.api.runtime.agent.session.patchSessionEntry({
|
|
1453
|
+
sessionKey: existing.key,
|
|
1454
|
+
readConsistency: "latest",
|
|
1455
|
+
preserveActivity: true,
|
|
1456
|
+
update: (entry) => {
|
|
1457
|
+
if (entry.sessionId?.trim() !== existing.sessionId || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true) throw changedError();
|
|
1458
|
+
return { archivedAt: void 0 };
|
|
1459
|
+
}
|
|
1460
|
+
})) throw changedError();
|
|
1461
|
+
const connectionFingerprint = params.control.connectionFingerprint;
|
|
1462
|
+
if (connectionFingerprint) params.onContinued?.({
|
|
1463
|
+
connectionFingerprint,
|
|
1464
|
+
...codexUpstreamBaseline(boundThread, boundCatalogSessionId)
|
|
1465
|
+
});
|
|
1466
|
+
return {
|
|
1467
|
+
sessionKey: existing.key,
|
|
1468
|
+
disposition: "existing"
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
const sourceThread = await params.control.readThread(params.threadId, true);
|
|
1472
|
+
if (sourceThread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1473
|
+
if (sourceThread.status?.type !== "notLoaded") requireIdleThread(sourceThread, "continue");
|
|
1474
|
+
const connectionFingerprint = params.control.connectionFingerprint;
|
|
1475
|
+
if (!connectionFingerprint) throw new Error("Codex Continue requires a pinned app-server connection");
|
|
1476
|
+
const adopted = await createOrReuseAdoptedSession({
|
|
1477
|
+
...params,
|
|
1478
|
+
sourceThread,
|
|
1479
|
+
connectionFingerprint
|
|
1480
|
+
});
|
|
1481
|
+
const boundThreadId = requireBoundThread(adopted);
|
|
1482
|
+
const baselineThread = boundThreadId === sourceThread.id ? sourceThread : await params.control.readThread(boundThreadId, true);
|
|
1483
|
+
if (baselineThread.id !== boundThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1484
|
+
params.onContinued?.({
|
|
1485
|
+
connectionFingerprint,
|
|
1486
|
+
...codexUpstreamBaseline(baselineThread, boundCatalogSessionId)
|
|
1487
|
+
});
|
|
1666
1488
|
return {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
humanTurns,
|
|
1670
|
-
nextMarker: {
|
|
1671
|
-
turnId: newest.id,
|
|
1672
|
-
userMessageCount: newestUserMessageCount
|
|
1673
|
-
},
|
|
1674
|
-
...humanTurns > 0 ? {
|
|
1675
|
-
occurredAt: occurredAt ?? params.now ?? Date.now(),
|
|
1676
|
-
dedupeId: activityId
|
|
1677
|
-
} : {}
|
|
1489
|
+
sessionKey: adopted.key,
|
|
1490
|
+
disposition: "forked"
|
|
1678
1491
|
};
|
|
1679
1492
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1493
|
+
/** Creates one locked OpenClaw branch whose first harness run forks the Codex source. */
|
|
1494
|
+
async function continueLocalCodexSession(params) {
|
|
1495
|
+
const sourceKey = sessionCatalogAdoptedSourceKey(params.hostId ?? "gateway:local", params.threadId);
|
|
1496
|
+
const operationKey = sessionCatalogAdoptedSourceKey(params.agentId, sourceKey);
|
|
1497
|
+
const current = continueOperations.get(operationKey);
|
|
1498
|
+
if (current) return await current;
|
|
1499
|
+
const run = async (control) => await continueLocalCodexSessionInner({
|
|
1500
|
+
...params,
|
|
1501
|
+
control
|
|
1502
|
+
});
|
|
1503
|
+
const operation = runSessionActionExclusive(sourceKey, async () => params.control.withPinnedConnection(run));
|
|
1504
|
+
continueOperations.set(operationKey, operation);
|
|
1505
|
+
try {
|
|
1506
|
+
return await operation;
|
|
1507
|
+
} finally {
|
|
1508
|
+
if (continueOperations.get(operationKey) === operation) continueOperations.delete(operationKey);
|
|
1509
|
+
}
|
|
1682
1510
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1511
|
+
//#endregion
|
|
1512
|
+
//#region extensions/codex/src/session-catalog-archive.ts
|
|
1513
|
+
async function assertNoPendingSupervisionBranch(params) {
|
|
1514
|
+
const adoptedEntries = [params.agentId, ...listAgentIds(params.config).filter((agentId) => agentId !== params.agentId)].flatMap((agentId) => params.runtime.agent.session.listSessionEntries({
|
|
1515
|
+
agentId,
|
|
1516
|
+
readOnly: true
|
|
1517
|
+
})).filter((candidate) => isAdoptionSessionKeyForThread(candidate.sessionKey, params.threadId, params.sourceHomeId) || params.sourceHomeId !== void 0 && params.allowLegacy === true && isAdoptionSessionKeyForThread(candidate.sessionKey, params.threadId));
|
|
1518
|
+
for (const adopted of adoptedEntries) {
|
|
1519
|
+
if (adopted.entry.initializationPending === true) throw new CatalogParamsError("Codex session cannot be archived while its OpenClaw branch is initializing");
|
|
1520
|
+
const sessionId = adopted.entry.sessionId?.trim();
|
|
1521
|
+
if (!sessionId) continue;
|
|
1522
|
+
const binding = await params.bindingStore.read(sessionBindingIdentity({
|
|
1523
|
+
sessionId,
|
|
1524
|
+
sessionKey: adopted.sessionKey,
|
|
1525
|
+
config: params.config
|
|
1526
|
+
}));
|
|
1527
|
+
if (binding?.connectionScope === "supervision" && binding.supervisionSourceThreadId === params.threadId && binding.pendingSupervisionBranch?.sourceThreadId === params.threadId) throw new CatalogParamsError("Codex session cannot be archived until its OpenClaw branch starts");
|
|
1528
|
+
}
|
|
1687
1529
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
const
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
if (isCodexThreadGoneError(error)) activities.push({
|
|
1708
|
-
kind: "missing",
|
|
1709
|
-
sessionKey: probe.sessionKey
|
|
1710
|
-
});
|
|
1530
|
+
/** Archives one inactive Gateway-local Codex thread after a fresh status read. */
|
|
1531
|
+
async function archiveLocalCodexSession(params) {
|
|
1532
|
+
return await runSessionActionExclusive(sessionCatalogAdoptedSourceKey(params.hostId ?? "gateway:local", params.threadId), async () => {
|
|
1533
|
+
return await params.bindingStore.withThreadArchiveFence(async () => {
|
|
1534
|
+
const run = async (control) => {
|
|
1535
|
+
await requireCatalogEligibleThread(control, params.threadId);
|
|
1536
|
+
await assertNoPendingSupervisionBranch(params);
|
|
1537
|
+
const thread = await control.readThread(params.threadId, false);
|
|
1538
|
+
if (thread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1539
|
+
requireIdleThread(thread, "archive");
|
|
1540
|
+
if (await params.bindingStore.hasOtherThreadOwner(params.threadId)) throw new CatalogParamsError("Codex session cannot be archived while it is attached to an OpenClaw session");
|
|
1541
|
+
await assertCodexArchiveDescendantsUnowned({
|
|
1542
|
+
bindingStore: params.bindingStore,
|
|
1543
|
+
threadId: params.threadId,
|
|
1544
|
+
listPage: (request) => control.listDescendantPage(request),
|
|
1545
|
+
assertDescendantIdle: async (descendantThreadId) => {
|
|
1546
|
+
const descendant = await control.readThread(descendantThreadId, false);
|
|
1547
|
+
if (descendant.id !== descendantThreadId) throw new Error("Codex app-server returned a different descendant than requested");
|
|
1548
|
+
requireIdleThread(descendant, "archive");
|
|
1711
1549
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
if (activity) activities.push(activity);
|
|
1719
|
-
} catch {}
|
|
1720
|
-
}
|
|
1721
|
-
return activities;
|
|
1550
|
+
});
|
|
1551
|
+
await control.archiveThread(params.threadId);
|
|
1552
|
+
return { archived: true };
|
|
1553
|
+
};
|
|
1554
|
+
return await params.control.withPinnedConnection(run);
|
|
1555
|
+
});
|
|
1722
1556
|
});
|
|
1723
1557
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
sessionId,
|
|
1735
|
-
sessionKey: probe.sessionKey,
|
|
1736
|
-
config
|
|
1737
|
-
}));
|
|
1738
|
-
return binding?.connectionScope === "supervision" && binding.supervisionSourceThreadId === probe.threadId ? binding.threadId : probe.threadId;
|
|
1558
|
+
//#endregion
|
|
1559
|
+
//#region extensions/codex/src/session-catalog-create.ts
|
|
1560
|
+
const CODEX_AGENT_RUNTIME_ID = "codex";
|
|
1561
|
+
const CODEX_CATALOG_DEFAULT_MODEL_REF = "openai/gpt-5.6-sol";
|
|
1562
|
+
function resolveCodexCatalogCreateSession(config, requestedAgentId) {
|
|
1563
|
+
if (!config) return;
|
|
1564
|
+
const agentId = requestedAgentId ?? resolveDefaultAgentId(config);
|
|
1565
|
+
const defaultModel = resolveDefaultModelForAgent({
|
|
1566
|
+
cfg: config,
|
|
1567
|
+
agentId
|
|
1739
1568
|
});
|
|
1569
|
+
return "error" in resolveAllowedModelRef({
|
|
1570
|
+
cfg: config,
|
|
1571
|
+
catalog: [],
|
|
1572
|
+
raw: CODEX_CATALOG_DEFAULT_MODEL_REF,
|
|
1573
|
+
defaultProvider: defaultModel.provider,
|
|
1574
|
+
defaultModel: defaultModel.model,
|
|
1575
|
+
agentId
|
|
1576
|
+
}) ? void 0 : {
|
|
1577
|
+
model: CODEX_CATALOG_DEFAULT_MODEL_REF,
|
|
1578
|
+
agentRuntime: CODEX_AGENT_RUNTIME_ID
|
|
1579
|
+
};
|
|
1740
1580
|
}
|
|
1741
1581
|
//#endregion
|
|
1742
|
-
//#region extensions/codex/src/session-catalog.ts
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
params.cwd?.trim() || null
|
|
1753
|
-
]);
|
|
1582
|
+
//#region extensions/codex/src/session-catalog-node-continue.ts
|
|
1583
|
+
const CODEX_NODE_CONTINUE_COMMANDS = [
|
|
1584
|
+
CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1585
|
+
CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
1586
|
+
CODEX_CLI_SESSION_RESUME_COMMAND
|
|
1587
|
+
];
|
|
1588
|
+
const NODE_CATALOG_LIST_RESPONSE_TIMEOUT_MS = 8e3;
|
|
1589
|
+
const continueNodeAdoption = createSessionCatalogAdoptionCoordinator();
|
|
1590
|
+
function nodeLabel(node) {
|
|
1591
|
+
return node.displayName?.trim() || node.remoteIp?.trim() || node.nodeId;
|
|
1754
1592
|
}
|
|
1755
|
-
function
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
const limit = normalizeLimit(pageParams.limit, "limit");
|
|
1762
|
-
const search = pageParams.searchTerm?.trim().toLocaleLowerCase() || void 0;
|
|
1763
|
-
const cwd = pageParams.cwd?.trim() || void 0;
|
|
1764
|
-
const maxPages = search ? 20 : 1;
|
|
1765
|
-
const sessions = [];
|
|
1766
|
-
let cursor = readControlCursor(pageParams.cursor, "request");
|
|
1767
|
-
let nextCursor;
|
|
1768
|
-
let backwardsCursor;
|
|
1769
|
-
const seenCursors = new Set(cursor ? [cursor] : []);
|
|
1770
|
-
const requests = params.createRequestSnapshot();
|
|
1771
|
-
const deadline = params.now() + requests.requestTimeoutMs;
|
|
1772
|
-
for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {
|
|
1773
|
-
const remaining = limit - sessions.length;
|
|
1774
|
-
const remainingTimeoutMs = Math.ceil(deadline - params.now());
|
|
1775
|
-
if (remainingTimeoutMs <= 0) throw new Error("Codex session catalog listing timed out");
|
|
1776
|
-
const response = await requests.listThreads({
|
|
1777
|
-
archived: false,
|
|
1778
|
-
limit: remaining,
|
|
1779
|
-
modelProviders: [],
|
|
1780
|
-
sortKey: "updated_at",
|
|
1781
|
-
sortDirection: "desc",
|
|
1782
|
-
...cwd ? { cwd } : {},
|
|
1783
|
-
...cursor ? { cursor } : {}
|
|
1784
|
-
}, remainingTimeoutMs);
|
|
1785
|
-
if (pageIndex === 0) backwardsCursor = readControlCursor(response.backwardsCursor, "backwards response");
|
|
1786
|
-
sessions.push(...response.data.flatMap((thread) => {
|
|
1787
|
-
const session = toCatalogSession(thread, false);
|
|
1788
|
-
return session ? [session] : [];
|
|
1789
|
-
}).filter((session) => !search || session.name?.toLocaleLowerCase().includes(search)));
|
|
1790
|
-
nextCursor = readControlCursor(response.nextCursor, "next response");
|
|
1791
|
-
if (!nextCursor || sessions.length >= limit) break;
|
|
1792
|
-
if (seenCursors.has(nextCursor)) throw new Error("Codex session catalog returned a repeated search cursor");
|
|
1793
|
-
seenCursors.add(nextCursor);
|
|
1794
|
-
cursor = nextCursor;
|
|
1795
|
-
}
|
|
1796
|
-
return {
|
|
1797
|
-
sessions,
|
|
1798
|
-
...nextCursor ? { nextCursor } : {},
|
|
1799
|
-
...backwardsCursor ? { backwardsCursor } : {}
|
|
1800
|
-
};
|
|
1801
|
-
},
|
|
1802
|
-
async listDescendantPage(listParams) {
|
|
1803
|
-
const requests = params.createRequestSnapshot();
|
|
1804
|
-
return await requests.listThreads(listParams, requests.requestTimeoutMs);
|
|
1805
|
-
},
|
|
1806
|
-
async readThread(threadId, includeTurns = false) {
|
|
1807
|
-
return await params.createRequestSnapshot().readThread(threadId, includeTurns);
|
|
1808
|
-
},
|
|
1809
|
-
async listTurnPage(listParams) {
|
|
1810
|
-
return await params.createRequestSnapshot().listThreadTurns(listParams);
|
|
1811
|
-
},
|
|
1812
|
-
async forkThread(forkParams) {
|
|
1813
|
-
return await params.createRequestSnapshot().forkThread(forkParams);
|
|
1814
|
-
},
|
|
1815
|
-
async archiveThread(threadId) {
|
|
1816
|
-
await params.createRequestSnapshot().archiveThread(threadId);
|
|
1817
|
-
}
|
|
1818
|
-
};
|
|
1593
|
+
function compareNodeLabels(left, right) {
|
|
1594
|
+
const leftLabel = nodeLabel(left);
|
|
1595
|
+
const rightLabel = nodeLabel(right);
|
|
1596
|
+
if (leftLabel < rightLabel) return -1;
|
|
1597
|
+
if (leftLabel > rightLabel) return 1;
|
|
1598
|
+
return 0;
|
|
1819
1599
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
const
|
|
1825
|
-
const
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1600
|
+
function canContinueCodexOnNode(node) {
|
|
1601
|
+
return node.connected === true && CODEX_NODE_CONTINUE_COMMANDS.every((command) => node.commands?.includes(command) === true && node.invocableCommands?.includes(command) === true);
|
|
1602
|
+
}
|
|
1603
|
+
async function listPairedNode(params) {
|
|
1604
|
+
const hostId = `node:${params.node.nodeId}`;
|
|
1605
|
+
const common = {
|
|
1606
|
+
hostId,
|
|
1607
|
+
label: nodeLabel(params.node),
|
|
1608
|
+
kind: "node",
|
|
1609
|
+
nodeId: params.node.nodeId,
|
|
1610
|
+
canContinueCodex: canContinueCodexOnNode(params.node)
|
|
1611
|
+
};
|
|
1612
|
+
if (params.node.connected !== true) {
|
|
1613
|
+
const host = {
|
|
1614
|
+
...common,
|
|
1615
|
+
connected: false,
|
|
1616
|
+
sessions: [],
|
|
1617
|
+
error: {
|
|
1618
|
+
code: "NODE_OFFLINE",
|
|
1619
|
+
message: "Paired node is offline"
|
|
1620
|
+
}
|
|
1831
1621
|
};
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1622
|
+
params.onHost?.(host);
|
|
1623
|
+
return host;
|
|
1624
|
+
}
|
|
1625
|
+
const eventualHost = Promise.resolve().then(async () => {
|
|
1626
|
+
const page = filterCatalogPageByTitle(parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1627
|
+
nodeId: params.node.nodeId,
|
|
1628
|
+
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1629
|
+
params: {
|
|
1630
|
+
agentId: params.agentId,
|
|
1631
|
+
cursor: params.query.cursors?.[hostId],
|
|
1632
|
+
limit: params.query.limitPerHost,
|
|
1633
|
+
searchTerm: params.query.search
|
|
1634
|
+
},
|
|
1635
|
+
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1636
|
+
scopes: ["operator.write"]
|
|
1637
|
+
}))), params.query.search);
|
|
1638
|
+
return {
|
|
1639
|
+
...common,
|
|
1640
|
+
connected: true,
|
|
1641
|
+
...page,
|
|
1642
|
+
sessions: page.sessions.map((session) => {
|
|
1643
|
+
const adopted = params.adoptedSessions.get(sessionCatalogAdoptedSourceKey(hostId, session.threadId));
|
|
1644
|
+
return adopted ? Object.assign({}, session, { sessionKey: adopted.key }) : session;
|
|
1645
|
+
})
|
|
1837
1646
|
};
|
|
1838
|
-
|
|
1839
|
-
|
|
1647
|
+
}).catch((error) => ({
|
|
1648
|
+
...common,
|
|
1649
|
+
connected: true,
|
|
1650
|
+
sessions: [],
|
|
1651
|
+
error: catalogError("NODE_INVOKE_FAILED", error)
|
|
1652
|
+
}));
|
|
1653
|
+
if (params.onHost) eventualHost.then(params.onHost).catch(() => void 0);
|
|
1654
|
+
try {
|
|
1655
|
+
return await withTimeout(eventualHost, NODE_CATALOG_LIST_RESPONSE_TIMEOUT_MS, "paired node Codex session catalog timed out");
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
return {
|
|
1658
|
+
...common,
|
|
1659
|
+
connected: true,
|
|
1660
|
+
sessions: [],
|
|
1661
|
+
error: catalogError("NODE_INVOKE_FAILED", error)
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
async function requireNodeForCodexContinue(params) {
|
|
1666
|
+
const nodeId = params.hostId.slice(5).trim();
|
|
1667
|
+
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1668
|
+
const node = (await params.runtime.nodes.list()).nodes.find((candidate) => candidate.nodeId === nodeId);
|
|
1669
|
+
if (!node || !canContinueCodexOnNode(node)) throw new CatalogParamsError("paired node does not permit Codex session continuation");
|
|
1670
|
+
return {
|
|
1671
|
+
node,
|
|
1672
|
+
nodeId
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
async function resolveNodeCodexRecord(params) {
|
|
1676
|
+
let cursor;
|
|
1677
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
1678
|
+
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1679
|
+
const page = parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1680
|
+
nodeId: params.nodeId,
|
|
1681
|
+
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1682
|
+
params: {
|
|
1683
|
+
agentId: params.agentId,
|
|
1684
|
+
limit: 100,
|
|
1685
|
+
...cursor ? { cursor } : {}
|
|
1686
|
+
},
|
|
1687
|
+
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1688
|
+
scopes: ["operator.write"]
|
|
1689
|
+
})));
|
|
1690
|
+
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1691
|
+
if (record) return record;
|
|
1692
|
+
const nextCursor = page.nextCursor?.trim();
|
|
1693
|
+
if (!nextCursor) break;
|
|
1694
|
+
if (seenCursors.has(nextCursor)) throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1695
|
+
seenCursors.add(nextCursor);
|
|
1696
|
+
cursor = nextCursor;
|
|
1697
|
+
}
|
|
1698
|
+
throw new CatalogParamsError("Codex session is unavailable on the paired node");
|
|
1699
|
+
}
|
|
1700
|
+
function requireContinuableNodeRecord(record) {
|
|
1701
|
+
if (record.archived) throw new CatalogParamsError("Codex session is archived on the paired node");
|
|
1702
|
+
if (!isInteractiveThreadSource(record.source)) throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1703
|
+
if (record.status === "idle" || record.status === "notLoaded") return;
|
|
1704
|
+
if (record.status === "active") throw new CatalogParamsError("Codex session is active on the paired node; wait for it to finish before continuing");
|
|
1705
|
+
throw new CatalogParamsError("Codex session cannot be continued in its current state");
|
|
1706
|
+
}
|
|
1707
|
+
async function readNodeCodexHistory(params) {
|
|
1708
|
+
const page = parseTranscriptPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1709
|
+
nodeId: params.nodeId,
|
|
1710
|
+
command: CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
1711
|
+
params: {
|
|
1712
|
+
agentId: params.agentId,
|
|
1713
|
+
threadId: params.record.threadId,
|
|
1714
|
+
limit: 50
|
|
1715
|
+
},
|
|
1716
|
+
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1717
|
+
scopes: ["operator.write"]
|
|
1718
|
+
})));
|
|
1719
|
+
const thread = {
|
|
1720
|
+
id: params.record.threadId,
|
|
1721
|
+
createdAt: params.record.createdAt ?? 0,
|
|
1722
|
+
modelProvider: params.record.modelProvider ?? "openai",
|
|
1723
|
+
projectId: null,
|
|
1724
|
+
turns: page.data.toReversed()
|
|
1840
1725
|
};
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
const requestOptions = resolveRequestOptions(runtime.start);
|
|
1845
|
-
return {
|
|
1846
|
-
requestTimeoutMs: runtime.requestTimeoutMs,
|
|
1847
|
-
listThreads: async (listParams, timeoutMs) => await codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listThreads, listParams, {
|
|
1848
|
-
...requestOptions,
|
|
1849
|
-
timeoutMs
|
|
1850
|
-
}),
|
|
1851
|
-
readThread: async (threadId, includeTurns) => (await codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
1852
|
-
threadId,
|
|
1853
|
-
includeTurns
|
|
1854
|
-
}, requestOptions)).thread,
|
|
1855
|
-
listThreadTurns: async (listParams) => await codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listThreadTurns, listParams, requestOptions),
|
|
1856
|
-
forkThread: async (forkParams) => await codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(forkParams), requestOptions),
|
|
1857
|
-
archiveThread: async (threadId) => {
|
|
1858
|
-
await codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.archiveThread, { threadId }, requestOptions);
|
|
1859
|
-
}
|
|
1860
|
-
};
|
|
1726
|
+
return {
|
|
1727
|
+
thread,
|
|
1728
|
+
throughTurnId: codexLastTerminalTurnId(thread, (value) => boundedCatalogString(value, 256)) ?? null
|
|
1861
1729
|
};
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1730
|
+
}
|
|
1731
|
+
async function continueNodeCodexSessionInner(params) {
|
|
1732
|
+
const { nodeId } = await requireNodeForCodexContinue({
|
|
1733
|
+
runtime: params.api.runtime,
|
|
1734
|
+
hostId: params.hostId
|
|
1735
|
+
});
|
|
1736
|
+
const record = await resolveNodeCodexRecord({
|
|
1737
|
+
agentId: params.agentId,
|
|
1738
|
+
runtime: params.api.runtime,
|
|
1739
|
+
nodeId,
|
|
1740
|
+
threadId: params.threadId
|
|
1741
|
+
});
|
|
1742
|
+
requireContinuableNodeRecord(record);
|
|
1743
|
+
const existing = findNodeAdoptedSessionEntry({
|
|
1744
|
+
agentId: params.agentId,
|
|
1745
|
+
config: params.config,
|
|
1746
|
+
runtime: params.api.runtime,
|
|
1747
|
+
hostId: params.hostId,
|
|
1748
|
+
threadId: params.threadId,
|
|
1749
|
+
includeInitializing: true
|
|
1750
|
+
});
|
|
1751
|
+
let adopted;
|
|
1752
|
+
let disposition;
|
|
1753
|
+
if (existing) {
|
|
1754
|
+
adopted = existing;
|
|
1755
|
+
disposition = "existing";
|
|
1756
|
+
} else {
|
|
1757
|
+
const history = await readNodeCodexHistory({
|
|
1758
|
+
agentId: params.agentId,
|
|
1759
|
+
runtime: params.api.runtime,
|
|
1760
|
+
nodeId,
|
|
1761
|
+
record
|
|
1869
1762
|
});
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
includeTurns
|
|
1886
|
-
},
|
|
1887
|
-
config: runtimeConfig,
|
|
1888
|
-
timeoutMs: runtime.requestTimeoutMs
|
|
1889
|
-
})).thread,
|
|
1890
|
-
listThreadTurns: async (listParams) => await requestCodexAppServerClientJson({
|
|
1891
|
-
client,
|
|
1892
|
-
method: CODEX_CONTROL_METHODS.listThreadTurns,
|
|
1893
|
-
requestParams: listParams,
|
|
1894
|
-
config: runtimeConfig,
|
|
1895
|
-
timeoutMs: runtime.requestTimeoutMs
|
|
1896
|
-
}),
|
|
1897
|
-
forkThread: async (forkParams) => await requestCodexAppServerClientJson({
|
|
1898
|
-
client,
|
|
1899
|
-
method: CODEX_CONTROL_METHODS.forkThread,
|
|
1900
|
-
requestParams: assertCodexThreadForkParams(forkParams),
|
|
1901
|
-
config: runtimeConfig,
|
|
1902
|
-
timeoutMs: runtime.requestTimeoutMs
|
|
1903
|
-
}),
|
|
1904
|
-
archiveThread: async (threadId) => {
|
|
1905
|
-
await requestCodexAppServerClientJson({
|
|
1906
|
-
client,
|
|
1907
|
-
method: CODEX_CONTROL_METHODS.archiveThread,
|
|
1908
|
-
requestParams: { threadId },
|
|
1909
|
-
config: runtimeConfig,
|
|
1910
|
-
timeoutMs: runtime.requestTimeoutMs
|
|
1911
|
-
});
|
|
1912
|
-
}
|
|
1913
|
-
};
|
|
1914
|
-
const pinnedControl = createCodexSessionCatalogControlFromRequests({
|
|
1915
|
-
clientId: resolveCodexAppServerClientInstanceId(client),
|
|
1916
|
-
connectionFingerprint: buildCodexAppServerConnectionFingerprint(runtime, resolveDefaultAgentDir(runtimeConfig ?? {})),
|
|
1917
|
-
createRequestSnapshot: () => requests,
|
|
1918
|
-
now,
|
|
1919
|
-
withPinnedConnection: async (nestedRun) => await nestedRun(pinnedControl)
|
|
1920
|
-
});
|
|
1921
|
-
return await run(pinnedControl);
|
|
1922
|
-
} finally {
|
|
1923
|
-
releaseLeasedSharedCodexAppServerClient(client);
|
|
1924
|
-
}
|
|
1925
|
-
};
|
|
1926
|
-
const control = createCodexSessionCatalogControlFromRequests({
|
|
1927
|
-
createRequestSnapshot,
|
|
1928
|
-
now,
|
|
1929
|
-
withPinnedConnection
|
|
1763
|
+
adopted = await createOrReuseNodeAdoptedSession({
|
|
1764
|
+
agentId: params.agentId,
|
|
1765
|
+
api: params.api,
|
|
1766
|
+
config: params.config,
|
|
1767
|
+
hostId: params.hostId,
|
|
1768
|
+
nodeId,
|
|
1769
|
+
record,
|
|
1770
|
+
history
|
|
1771
|
+
});
|
|
1772
|
+
disposition = "forked";
|
|
1773
|
+
}
|
|
1774
|
+
const marker = nodeSessionMarker({
|
|
1775
|
+
hostId: params.hostId,
|
|
1776
|
+
threadId: params.threadId,
|
|
1777
|
+
nodeId
|
|
1930
1778
|
});
|
|
1931
1779
|
return {
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
cache.delete(oldest.value);
|
|
1999
|
-
}
|
|
2000
|
-
try {
|
|
2001
|
-
const result = await page;
|
|
2002
|
-
if (cache.get(key) === entry) {
|
|
2003
|
-
delete entry.stalePage;
|
|
2004
|
-
entry.settledPage = page;
|
|
2005
|
-
entry.expiresAt = now() + CODEX_SESSION_CATALOG_LIST_TTL_MS;
|
|
2006
|
-
}
|
|
2007
|
-
return result;
|
|
2008
|
-
} catch (error) {
|
|
2009
|
-
if (stalePage) {
|
|
2010
|
-
let stale;
|
|
2011
|
-
try {
|
|
2012
|
-
stale = await stalePage;
|
|
2013
|
-
} catch {}
|
|
2014
|
-
if (stale) {
|
|
2015
|
-
if (cache.get(key) === entry) {
|
|
2016
|
-
cache.delete(key);
|
|
2017
|
-
const settledPage = Promise.resolve(stale);
|
|
2018
|
-
cache.set(key, {
|
|
2019
|
-
expiresAt: now(),
|
|
2020
|
-
page: settledPage,
|
|
2021
|
-
settledPage
|
|
2022
|
-
});
|
|
2023
|
-
}
|
|
2024
|
-
if (serveStaleOnError) return stale;
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
if (cache.get(key) === entry) cache.delete(key);
|
|
2028
|
-
throw error;
|
|
1780
|
+
sessionKey: adopted.key,
|
|
1781
|
+
disposition,
|
|
1782
|
+
conversationBinding: {
|
|
1783
|
+
summary: "Continue this Codex session on its paired node.",
|
|
1784
|
+
detachHint: "Start a new chat to leave the paired-node Codex session.",
|
|
1785
|
+
data: createCodexCliNodeConversationBindingData({
|
|
1786
|
+
nodeId,
|
|
1787
|
+
sessionId: params.threadId,
|
|
1788
|
+
agentId: adopted.agentId,
|
|
1789
|
+
cwd: record.cwd
|
|
1790
|
+
})
|
|
1791
|
+
},
|
|
1792
|
+
afterConversationBound: async () => await finalizeNodeAdoptedSession({
|
|
1793
|
+
api: params.api,
|
|
1794
|
+
adopted,
|
|
1795
|
+
marker
|
|
1796
|
+
})
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
async function continueNodeCodexSession(params) {
|
|
1800
|
+
if (params.clientScopes?.includes("operator.admin") !== true) throw new CatalogParamsError("continuing a paired-node Codex session requires operator.admin");
|
|
1801
|
+
const nodeId = params.hostId.slice(5).trim();
|
|
1802
|
+
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1803
|
+
const agentId = resolveSessionAgentIds({
|
|
1804
|
+
config: params.config,
|
|
1805
|
+
agentId: params.agentId
|
|
1806
|
+
}).sessionAgentId;
|
|
1807
|
+
const sourceKey = sessionCatalogAdoptedSourceKey(`node:${nodeId}`, params.threadId);
|
|
1808
|
+
const operationKey = sessionCatalogAdoptedSourceKey(agentId, sourceKey);
|
|
1809
|
+
return await continueNodeAdoption({
|
|
1810
|
+
sourceKey: operationKey,
|
|
1811
|
+
findExisting: () => void 0,
|
|
1812
|
+
create: () => runSessionActionExclusive(sourceKey, async () => continueNodeCodexSessionInner({
|
|
1813
|
+
...params,
|
|
1814
|
+
agentId
|
|
1815
|
+
})),
|
|
1816
|
+
complete: async (continued) => continued
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
//#endregion
|
|
1820
|
+
//#region extensions/codex/src/session-catalog-listing.ts
|
|
1821
|
+
async function listVisiblePage(params) {
|
|
1822
|
+
const excluded = params.excludedThreadIds;
|
|
1823
|
+
const sessions = [];
|
|
1824
|
+
let cursor = params.cursor;
|
|
1825
|
+
let nextCursor;
|
|
1826
|
+
let backwardsCursor;
|
|
1827
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
1828
|
+
for (let pageIndex = 0; pageIndex < 20; pageIndex += 1) {
|
|
1829
|
+
let excludedFromPage = false;
|
|
1830
|
+
const rawPage = await params.control.listPage({
|
|
1831
|
+
limit: params.limit - sessions.length,
|
|
1832
|
+
...cursor ? { cursor } : {},
|
|
1833
|
+
...params.searchTerm ? { searchTerm: params.searchTerm } : {},
|
|
1834
|
+
...params.cwd ? { cwd: params.cwd } : {}
|
|
1835
|
+
});
|
|
1836
|
+
const page = filterCatalogPageByTitle(parseCatalogPage(rawPage), params.searchTerm);
|
|
1837
|
+
if (pageIndex === 0) backwardsCursor = page.backwardsCursor;
|
|
1838
|
+
for (const managed of rawPage.managedThreads ?? []) {
|
|
1839
|
+
excludedFromPage = true;
|
|
1840
|
+
await params.onExcludedThread?.(managed);
|
|
1841
|
+
}
|
|
1842
|
+
for (const session of page.sessions) {
|
|
1843
|
+
if (!excluded?.has(session.threadId)) {
|
|
1844
|
+
sessions.push(session);
|
|
1845
|
+
continue;
|
|
2029
1846
|
}
|
|
1847
|
+
excludedFromPage = true;
|
|
1848
|
+
await params.onExcludedThread?.({ threadId: session.threadId });
|
|
2030
1849
|
}
|
|
1850
|
+
nextCursor = page.nextCursor;
|
|
1851
|
+
if (!nextCursor || sessions.length >= params.limit || !excludedFromPage) break;
|
|
1852
|
+
if (seenCursors.has(nextCursor)) throw new Error("Codex session catalog returned a repeated exclusion cursor");
|
|
1853
|
+
seenCursors.add(nextCursor);
|
|
1854
|
+
cursor = nextCursor;
|
|
1855
|
+
}
|
|
1856
|
+
return {
|
|
1857
|
+
sessions: sessions.slice(0, params.limit),
|
|
1858
|
+
...nextCursor ? { nextCursor } : {},
|
|
1859
|
+
...backwardsCursor ? { backwardsCursor } : {}
|
|
2031
1860
|
};
|
|
2032
1861
|
}
|
|
2033
1862
|
async function listGatewayHost(params) {
|
|
1863
|
+
const hostId = params.source?.hostId ?? "gateway:local";
|
|
1864
|
+
const label = params.source?.label ?? "Local Codex";
|
|
1865
|
+
const sourceHomeId = params.source?.sourceHomeId ?? "gateway:local";
|
|
2034
1866
|
try {
|
|
2035
|
-
const page =
|
|
1867
|
+
const page = await listVisiblePage({
|
|
1868
|
+
control: params.control,
|
|
1869
|
+
cursor: params.query.cursors?.[hostId],
|
|
1870
|
+
excludedThreadIds: params.excludedThreadIds,
|
|
2036
1871
|
limit: params.query.limitPerHost,
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
})
|
|
1872
|
+
onExcludedThread: params.onExcludedThread,
|
|
1873
|
+
searchTerm: params.query.search
|
|
1874
|
+
});
|
|
2040
1875
|
const adoptedSessions = await listAdoptedSessionEntries({
|
|
1876
|
+
agentId: params.agentId,
|
|
2041
1877
|
bindingStore: params.bindingStore,
|
|
2042
1878
|
config: params.config,
|
|
2043
1879
|
runtime: params.runtime,
|
|
2044
1880
|
sessionEntries: params.sessionEntries
|
|
2045
1881
|
});
|
|
2046
1882
|
return {
|
|
2047
|
-
hostId
|
|
2048
|
-
label
|
|
1883
|
+
hostId,
|
|
1884
|
+
label,
|
|
2049
1885
|
kind: "gateway",
|
|
2050
1886
|
connected: true,
|
|
2051
1887
|
...page,
|
|
2052
1888
|
sessions: page.sessions.map((session) => {
|
|
2053
|
-
const adopted = adoptedSessions.get(session.threadId);
|
|
2054
|
-
|
|
1889
|
+
const adopted = adoptedSessions.get(sessionCatalogAdoptedSourceKey(sourceHomeId, session.threadId)) ?? (hostId === "gateway:local" ? adoptedSessions.get(sessionCatalogAdoptedSourceKey("gateway:local", session.threadId)) : void 0);
|
|
1890
|
+
const sourced = params.source ? Object.assign({}, session, { sourceHomeId: params.source.sourceHomeId }) : session;
|
|
1891
|
+
return adopted ? Object.assign({}, sourced, { sessionKey: adopted.key }) : sourced;
|
|
2055
1892
|
})
|
|
2056
1893
|
};
|
|
2057
1894
|
} catch (error) {
|
|
2058
1895
|
return {
|
|
2059
|
-
hostId
|
|
2060
|
-
label
|
|
1896
|
+
hostId,
|
|
1897
|
+
label,
|
|
2061
1898
|
kind: "gateway",
|
|
2062
1899
|
connected: false,
|
|
2063
1900
|
sessions: [],
|
|
@@ -2067,16 +1904,37 @@ async function listGatewayHost(params) {
|
|
|
2067
1904
|
}
|
|
2068
1905
|
/** Lists Gateway-local and paired-node Codex sessions with per-host failures. */
|
|
2069
1906
|
async function listCodexSessionCatalog(params) {
|
|
1907
|
+
const agentId = resolveSessionAgentIds({
|
|
1908
|
+
config: params.config ?? {},
|
|
1909
|
+
agentId: params.agentId
|
|
1910
|
+
}).sessionAgentId;
|
|
2070
1911
|
const query = readGatewayParams(params.query);
|
|
2071
1912
|
const requestedHostIds = query.hostIds ? new Set(query.hostIds) : void 0;
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
1913
|
+
const localSources = params.localHomes?.filter((source) => !requestedHostIds || requestedHostIds.has(source.hostId)) ?? (params.includeLocal !== false && (!requestedHostIds || requestedHostIds.has("gateway:local")) ? [void 0] : []);
|
|
1914
|
+
const managedThreads = await params.bindingStore.managedThreads?.snapshot();
|
|
1915
|
+
const fallbackSource = params.control.homesForAgent(agentId)[0];
|
|
1916
|
+
const localHosts = localSources.map((source) => (() => {
|
|
1917
|
+
const ownershipSource = source ?? fallbackSource;
|
|
1918
|
+
const managedThreadIds = ownershipSource ? managedThreads?.get(ownershipSource.sourceHomeId) : void 0;
|
|
1919
|
+
return listGatewayHost({
|
|
1920
|
+
agentId,
|
|
1921
|
+
bindingStore: params.bindingStore,
|
|
1922
|
+
config: params.config,
|
|
1923
|
+
control: params.control.forRequest(agentId, ownershipSource),
|
|
1924
|
+
query,
|
|
1925
|
+
runtime: params.runtime,
|
|
1926
|
+
sessionEntries: params.sessionEntries,
|
|
1927
|
+
excludedThreadIds: managedThreadIds,
|
|
1928
|
+
...ownershipSource && params.bindingStore.managedThreads ? { onExcludedThread: async ({ threadId, rolloutPath }) => {
|
|
1929
|
+
if (!managedThreadIds?.has(threadId)) await params.bindingStore.managedThreads?.mark({
|
|
1930
|
+
sourceHomeId: ownershipSource.sourceHomeId,
|
|
1931
|
+
threadId,
|
|
1932
|
+
...rolloutPath ? { rolloutPath } : {}
|
|
1933
|
+
});
|
|
1934
|
+
} } : {},
|
|
1935
|
+
...source ? { source } : {}
|
|
1936
|
+
});
|
|
1937
|
+
})());
|
|
2080
1938
|
for (const host of localHosts) if (params.onHost) host.then(params.onHost).catch(() => void 0);
|
|
2081
1939
|
if (!(!requestedHostIds || query.hostIds?.some((hostId) => hostId.startsWith("node:")))) return { hosts: await Promise.all(localHosts) };
|
|
2082
1940
|
let nodes;
|
|
@@ -2095,12 +1953,14 @@ async function listCodexSessionCatalog(params) {
|
|
|
2095
1953
|
return { hosts: [...await Promise.all(localHosts), registryHost] };
|
|
2096
1954
|
}
|
|
2097
1955
|
const adoptedNodeSessions = listNodeAdoptedSessionEntries({
|
|
1956
|
+
agentId,
|
|
2098
1957
|
config: params.config,
|
|
2099
1958
|
runtime: params.runtime,
|
|
2100
1959
|
sessionEntries: params.sessionEntries
|
|
2101
1960
|
});
|
|
2102
1961
|
const nodeHosts = nodes.toSorted(compareNodeLabels).map(async (node) => {
|
|
2103
1962
|
const host = await listPairedNode({
|
|
1963
|
+
agentId,
|
|
2104
1964
|
runtime: params.runtime,
|
|
2105
1965
|
node,
|
|
2106
1966
|
query,
|
|
@@ -2112,16 +1972,55 @@ async function listCodexSessionCatalog(params) {
|
|
|
2112
1972
|
return { hosts: await Promise.all([...localHosts, ...nodeHosts]) };
|
|
2113
1973
|
}
|
|
2114
1974
|
/** Builds the node-local read-only Codex app-server catalog command. */
|
|
2115
|
-
function createCodexSessionCatalogNodeHostCommands(
|
|
1975
|
+
function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources, bindingStore) {
|
|
1976
|
+
const bindRequest = (paramsJSON) => {
|
|
1977
|
+
const parsed = parseJsonParams(paramsJSON);
|
|
1978
|
+
if (!isRecord(parsed)) throw new CatalogParamsError("Codex session catalog parameters must be an object");
|
|
1979
|
+
const requestedAgentId = readBoundedOptionalString(parsed, "agentId", 256);
|
|
1980
|
+
const config = configSources.getRuntimeConfig() ?? {};
|
|
1981
|
+
const agentId = resolveSessionAgentIds({
|
|
1982
|
+
config,
|
|
1983
|
+
agentId: requestedAgentId
|
|
1984
|
+
}).sessionAgentId;
|
|
1985
|
+
if (!listAgentIds(config).includes(agentId)) throw new CatalogParamsError(`unknown Codex session catalog agent: ${agentId}`);
|
|
1986
|
+
const request = { ...parsed };
|
|
1987
|
+
delete request.agentId;
|
|
1988
|
+
const source = controlFactory.homesForAgent(agentId)[0];
|
|
1989
|
+
return {
|
|
1990
|
+
agentId,
|
|
1991
|
+
control: controlFactory.forRequest(agentId, source),
|
|
1992
|
+
sourceHomeId: source?.sourceHomeId,
|
|
1993
|
+
params: request,
|
|
1994
|
+
paramsJSON: JSON.stringify(request)
|
|
1995
|
+
};
|
|
1996
|
+
};
|
|
2116
1997
|
return [
|
|
2117
1998
|
{
|
|
2118
1999
|
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
2119
2000
|
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
2120
2001
|
dangerous: false,
|
|
2121
2002
|
handle: async (paramsJSON) => {
|
|
2122
|
-
const
|
|
2003
|
+
const request = bindRequest(paramsJSON);
|
|
2004
|
+
const pageParams = readPageParams(request.params);
|
|
2123
2005
|
try {
|
|
2124
|
-
const
|
|
2006
|
+
const managedThreads = await bindingStore?.managedThreads?.snapshot();
|
|
2007
|
+
const sourceHomeId = request.sourceHomeId;
|
|
2008
|
+
const managedThreadIds = sourceHomeId ? managedThreads?.get(sourceHomeId) : void 0;
|
|
2009
|
+
const page = await listVisiblePage({
|
|
2010
|
+
control: request.control,
|
|
2011
|
+
cursor: pageParams.cursor,
|
|
2012
|
+
cwd: pageParams.cwd,
|
|
2013
|
+
excludedThreadIds: managedThreadIds,
|
|
2014
|
+
limit: pageParams.limit,
|
|
2015
|
+
...sourceHomeId && bindingStore?.managedThreads ? { onExcludedThread: async ({ threadId, rolloutPath }) => {
|
|
2016
|
+
if (!managedThreadIds?.has(threadId)) await bindingStore.managedThreads?.mark({
|
|
2017
|
+
sourceHomeId,
|
|
2018
|
+
threadId,
|
|
2019
|
+
...rolloutPath ? { rolloutPath } : {}
|
|
2020
|
+
});
|
|
2021
|
+
} } : {},
|
|
2022
|
+
searchTerm: pageParams.searchTerm
|
|
2023
|
+
});
|
|
2125
2024
|
return JSON.stringify(page);
|
|
2126
2025
|
} catch {
|
|
2127
2026
|
throw new Error("Codex app-server catalog is unavailable");
|
|
@@ -2133,10 +2032,11 @@ function createCodexSessionCatalogNodeHostCommands(control, configSources) {
|
|
|
2133
2032
|
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
2134
2033
|
dangerous: false,
|
|
2135
2034
|
handle: async (paramsJSON) => {
|
|
2136
|
-
const
|
|
2035
|
+
const request = bindRequest(paramsJSON);
|
|
2036
|
+
const action = readNodeTranscriptParams(request.params);
|
|
2137
2037
|
try {
|
|
2138
|
-
await requireCatalogEligibleThread(control, action.threadId);
|
|
2139
|
-
const page = parseTranscriptPage(await control.listTurnPage({
|
|
2038
|
+
await requireCatalogEligibleThread(request.control, action.threadId);
|
|
2039
|
+
const page = parseTranscriptPage(await request.control.listTurnPage({
|
|
2140
2040
|
threadId: action.threadId,
|
|
2141
2041
|
limit: action.limit,
|
|
2142
2042
|
sortDirection: "desc",
|
|
@@ -2150,7 +2050,7 @@ function createCodexSessionCatalogNodeHostCommands(control, configSources) {
|
|
|
2150
2050
|
}
|
|
2151
2051
|
}
|
|
2152
2052
|
},
|
|
2153
|
-
createCodexTerminalNodeHostCommand(
|
|
2053
|
+
createCodexTerminalNodeHostCommand(bindRequest, configSources)
|
|
2154
2054
|
];
|
|
2155
2055
|
}
|
|
2156
2056
|
function readNodeTranscriptParams(value) {
|
|
@@ -2179,18 +2079,19 @@ function flattenTranscriptPageDesc(page) {
|
|
|
2179
2079
|
}
|
|
2180
2080
|
/** Reads the persisted transcript for a Gateway-local or paired-node Codex session. */
|
|
2181
2081
|
async function readCodexSessionTranscript(params) {
|
|
2182
|
-
if (params.hostId === "gateway:local") {
|
|
2082
|
+
if (params.source || params.hostId === "gateway:local") {
|
|
2183
2083
|
await requireCatalogEligibleThread(params.control, params.threadId);
|
|
2184
|
-
const
|
|
2084
|
+
const listParams = {
|
|
2185
2085
|
threadId: params.threadId,
|
|
2186
2086
|
limit: params.limit,
|
|
2187
2087
|
sortDirection: "desc",
|
|
2188
2088
|
itemsView: "full",
|
|
2189
2089
|
...params.cursor ? { cursor: params.cursor } : {}
|
|
2190
|
-
}
|
|
2090
|
+
};
|
|
2091
|
+
const page = parseTranscriptPage(await params.control.listTurnPage(listParams));
|
|
2191
2092
|
return {
|
|
2192
2093
|
hostId: params.hostId,
|
|
2193
|
-
label: "Local Codex",
|
|
2094
|
+
label: params.source?.label ?? "Local Codex",
|
|
2194
2095
|
threadId: params.threadId,
|
|
2195
2096
|
items: flattenTranscriptPageDesc(page),
|
|
2196
2097
|
...page.nextCursor ? { nextCursor: page.nextCursor } : {},
|
|
@@ -2204,6 +2105,7 @@ async function readCodexSessionTranscript(params) {
|
|
|
2204
2105
|
nodeId,
|
|
2205
2106
|
command: CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
2206
2107
|
params: {
|
|
2108
|
+
agentId: params.agentId,
|
|
2207
2109
|
threadId: params.threadId,
|
|
2208
2110
|
limit: params.limit,
|
|
2209
2111
|
...params.cursor ? { cursor: params.cursor } : {}
|
|
@@ -2220,332 +2122,629 @@ async function readCodexSessionTranscript(params) {
|
|
|
2220
2122
|
...page.backwardsCursor ? { backwardsCursor: page.backwardsCursor } : {}
|
|
2221
2123
|
};
|
|
2222
2124
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
if (adopted.has(sourceThreadId)) throw new Error(`multiple OpenClaw sessions adopt Codex thread ${sourceThreadId}`);
|
|
2255
|
-
adopted.set(sourceThreadId, {
|
|
2256
|
-
key: sessionKey,
|
|
2257
|
-
sessionId,
|
|
2258
|
-
agentId,
|
|
2259
|
-
boundThreadId
|
|
2260
|
-
});
|
|
2261
|
-
}
|
|
2262
|
-
return adopted;
|
|
2263
|
-
}
|
|
2264
|
-
async function findAdoptedSessionEntry(params) {
|
|
2265
|
-
return (await listAdoptedSessionEntries(params)).get(params.threadId);
|
|
2266
|
-
}
|
|
2267
|
-
async function clearCreatedAdoptionBinding(params) {
|
|
2268
|
-
let cleared = false;
|
|
2269
|
-
let clearError;
|
|
2270
|
-
try {
|
|
2271
|
-
cleared = await params.bindingStore.mutate(params.identity, {
|
|
2272
|
-
kind: "clear",
|
|
2273
|
-
threadId: params.sourceThreadId,
|
|
2274
|
-
expectedPendingSupervisionBranch: params.expectedPending
|
|
2275
|
-
});
|
|
2276
|
-
} catch (error) {
|
|
2277
|
-
clearError = error;
|
|
2278
|
-
}
|
|
2279
|
-
if (cleared) return;
|
|
2280
|
-
let current;
|
|
2281
|
-
try {
|
|
2282
|
-
current = await params.bindingStore.read(params.identity);
|
|
2283
|
-
} catch (readError) {
|
|
2284
|
-
throw new AggregateError([
|
|
2285
|
-
params.cause,
|
|
2286
|
-
...clearError ? [clearError] : [],
|
|
2287
|
-
readError
|
|
2288
|
-
], `OpenClaw session creation failed and the Codex binding could not be verified for ${params.sourceThreadId}`, { cause: readError });
|
|
2289
|
-
}
|
|
2290
|
-
if (!matchesPendingSupervisionOwner(current, params.expectedPending)) return;
|
|
2291
|
-
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 });
|
|
2292
|
-
}
|
|
2293
|
-
function matchesPendingAdoptionBinding(binding, expected) {
|
|
2294
|
-
const historyCoveredThrough = binding?.historyCoveredThrough;
|
|
2295
|
-
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));
|
|
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
|
|
2155
|
+
};
|
|
2296
2156
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2157
|
+
//#endregion
|
|
2158
|
+
//#region extensions/codex/src/session-upstream-activity.ts
|
|
2159
|
+
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);
|
|
2302
2164
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2165
|
+
function readMarker(probe) {
|
|
2166
|
+
if (!isRecord(probe.marker)) return;
|
|
2167
|
+
const turnId = probe.marker.turnId;
|
|
2168
|
+
if (turnId !== null && typeof turnId !== "string") return;
|
|
2169
|
+
const count = probe.marker.userMessageCount;
|
|
2170
|
+
if (count !== void 0 && (!Number.isSafeInteger(count) || count < 0)) return;
|
|
2171
|
+
return {
|
|
2172
|
+
turnId,
|
|
2173
|
+
...count === void 0 ? {} : { userMessageCount: count }
|
|
2308
2174
|
};
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
const
|
|
2315
|
-
if (
|
|
2316
|
-
|
|
2317
|
-
|
|
2175
|
+
}
|
|
2176
|
+
function upstreamConnectionFingerprint(probe) {
|
|
2177
|
+
return isRecord(probe.upstreamRef) && typeof probe.upstreamRef.connectionFingerprint === "string" ? probe.upstreamRef.connectionFingerprint : void 0;
|
|
2178
|
+
}
|
|
2179
|
+
function classifyCodexUpstreamTurns(params) {
|
|
2180
|
+
const marker = readMarker(params.probe);
|
|
2181
|
+
if (!marker) return;
|
|
2182
|
+
const newest = params.turns[0];
|
|
2183
|
+
if (!newest?.id) return;
|
|
2184
|
+
const markerIndex = marker.turnId === null ? -1 : params.turns.findIndex((turn) => turn.id === marker.turnId);
|
|
2185
|
+
const candidateTurns = markerIndex < 0 ? params.turns : params.turns.slice(0, markerIndex + 1);
|
|
2186
|
+
const newestUserMessageCount = countUserMessages(newest);
|
|
2187
|
+
if (!(marker.turnId !== newest.id || marker.userMessageCount === void 0 || newestUserMessageCount > marker.userMessageCount)) return;
|
|
2188
|
+
const ownTexts = new Set(params.probe.ownRecentUserTexts);
|
|
2189
|
+
let humanTurns = 0;
|
|
2190
|
+
let occurredAt;
|
|
2191
|
+
for (const turn of candidateTurns) {
|
|
2192
|
+
const userMessages = turn.items.filter((item) => item.type === "userMessage");
|
|
2193
|
+
const alreadySeen = turn.id === marker.turnId ? marker.userMessageCount ?? userMessages.length : 0;
|
|
2194
|
+
for (const item of userMessages.slice(alreadySeen)) {
|
|
2195
|
+
const texts = normalizeUserMessageTexts(item);
|
|
2196
|
+
if (ownTexts.has(texts.join(" ")) || texts.length > 1 && texts.every((text) => ownTexts.has(text))) continue;
|
|
2197
|
+
humanTurns += 1;
|
|
2198
|
+
if (occurredAt === void 0) {
|
|
2199
|
+
const timestampSeconds = turn.completedAt ?? turn.startedAt;
|
|
2200
|
+
occurredAt = typeof timestampSeconds === "number" && Number.isFinite(timestampSeconds) ? timestampSeconds * 1e3 : params.now ?? Date.now();
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2318
2203
|
}
|
|
2319
|
-
const
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2204
|
+
const activityId = `${newest.id}:${newestUserMessageCount}`;
|
|
2205
|
+
return {
|
|
2206
|
+
kind: "activity",
|
|
2207
|
+
sessionKey: params.probe.sessionKey,
|
|
2208
|
+
humanTurns,
|
|
2209
|
+
nextMarker: {
|
|
2210
|
+
turnId: newest.id,
|
|
2211
|
+
userMessageCount: newestUserMessageCount
|
|
2212
|
+
},
|
|
2213
|
+
...humanTurns > 0 ? {
|
|
2214
|
+
occurredAt: occurredAt ?? params.now ?? Date.now(),
|
|
2215
|
+
dedupeId: activityId
|
|
2216
|
+
} : {}
|
|
2328
2217
|
};
|
|
2329
|
-
let stored;
|
|
2330
|
-
try {
|
|
2331
|
-
stored = await params.bindingStore.mutate(params.identity, {
|
|
2332
|
-
kind: "set",
|
|
2333
|
-
if: { kind: "absent" },
|
|
2334
|
-
binding
|
|
2335
|
-
});
|
|
2336
|
-
} catch (error) {
|
|
2337
|
-
if (matchesPendingAdoptionBinding(await params.bindingStore.read(params.identity), params)) return;
|
|
2338
|
-
throw error;
|
|
2339
|
-
}
|
|
2340
|
-
if (stored) return;
|
|
2341
|
-
if (!matchesPendingAdoptionBinding(await params.bindingStore.read(params.identity), params)) throw new Error(`failed to bind OpenClaw session to Codex thread ${params.sourceThreadId}`);
|
|
2342
2218
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
const
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
recoverMatchingInitialEntry: true,
|
|
2365
|
-
initialEntry: {
|
|
2366
|
-
agentHarnessId: "codex",
|
|
2367
|
-
modelSelectionLocked: true,
|
|
2368
|
-
pluginExtensions: { codex: { supervision: {
|
|
2369
|
-
...marker,
|
|
2370
|
-
initializing: true,
|
|
2371
|
-
modelLocked: true
|
|
2372
|
-
} } }
|
|
2373
|
-
},
|
|
2374
|
-
afterImport: async (entry) => {
|
|
2375
|
-
createdBindingIdentity = sessionBindingIdentity({
|
|
2376
|
-
sessionId: entry.sessionId,
|
|
2377
|
-
sessionKey: entry.key,
|
|
2378
|
-
config: params.config
|
|
2219
|
+
function countUserMessages(turn) {
|
|
2220
|
+
return turn.items.filter((item) => item.type === "userMessage").length;
|
|
2221
|
+
}
|
|
2222
|
+
function normalizeUserMessageTexts(item) {
|
|
2223
|
+
const typed = item;
|
|
2224
|
+
const contentTexts = typed.content?.filter((input) => input.type === "text").map((input) => input.text.trim().replace(/\s+/g, " ")).filter(Boolean);
|
|
2225
|
+
return contentTexts?.length ? contentTexts : [(typed.text ?? "").trim().replace(/\s+/g, " ")];
|
|
2226
|
+
}
|
|
2227
|
+
async function checkCodexUpstreamActivity(probes, control, resolveThreadId = async (probe) => probe.threadId) {
|
|
2228
|
+
return await control.withPinnedConnection(async (pinned) => {
|
|
2229
|
+
const activities = [];
|
|
2230
|
+
for (const probe of probes) {
|
|
2231
|
+
const fingerprint = upstreamConnectionFingerprint(probe);
|
|
2232
|
+
if (probe.upstreamKind !== "codex-app-server" || !fingerprint || fingerprint !== pinned.connectionFingerprint) continue;
|
|
2233
|
+
try {
|
|
2234
|
+
const threadId = await resolveThreadId(probe);
|
|
2235
|
+
const page = await pinned.listTurnPage({
|
|
2236
|
+
threadId,
|
|
2237
|
+
limit: CODEX_UPSTREAM_TURN_LIMIT,
|
|
2238
|
+
sortDirection: "desc",
|
|
2239
|
+
itemsView: "full"
|
|
2379
2240
|
});
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2241
|
+
const marker = readMarker(probe);
|
|
2242
|
+
if (page.data.length === 0 && marker) {
|
|
2243
|
+
try {
|
|
2244
|
+
await pinned.readThread(threadId, false);
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
if (isCodexThreadGoneError(error)) activities.push({
|
|
2247
|
+
kind: "missing",
|
|
2248
|
+
sessionKey: probe.sessionKey
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
2253
|
+
const activity = classifyCodexUpstreamTurns({
|
|
2254
|
+
probe,
|
|
2255
|
+
turns: page.data
|
|
2393
2256
|
});
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
modelLocked: true
|
|
2397
|
-
} } } };
|
|
2398
|
-
}
|
|
2399
|
-
});
|
|
2400
|
-
return {
|
|
2401
|
-
key: created.key,
|
|
2402
|
-
sessionId: created.sessionId,
|
|
2403
|
-
agentId: created.agentId,
|
|
2404
|
-
boundThreadId: params.sourceThread.id
|
|
2405
|
-
};
|
|
2406
|
-
} catch (error) {
|
|
2407
|
-
let raced = await findAdoptedSessionEntry({
|
|
2408
|
-
bindingStore: params.bindingStore,
|
|
2409
|
-
config: params.config,
|
|
2410
|
-
runtime: params.api.runtime,
|
|
2411
|
-
threadId: params.sourceThread.id
|
|
2412
|
-
});
|
|
2413
|
-
if (raced) return raced;
|
|
2414
|
-
if (createdBindingIdentity && createdPendingBinding) {
|
|
2415
|
-
await clearCreatedAdoptionBinding({
|
|
2416
|
-
bindingStore: params.bindingStore,
|
|
2417
|
-
identity: createdBindingIdentity,
|
|
2418
|
-
sourceThreadId: params.sourceThread.id,
|
|
2419
|
-
expectedPending: createdPendingBinding,
|
|
2420
|
-
cause: error
|
|
2421
|
-
});
|
|
2422
|
-
raced = await findAdoptedSessionEntry({
|
|
2423
|
-
bindingStore: params.bindingStore,
|
|
2424
|
-
config: params.config,
|
|
2425
|
-
runtime: params.api.runtime,
|
|
2426
|
-
threadId: params.sourceThread.id
|
|
2427
|
-
});
|
|
2428
|
-
if (raced) return raced;
|
|
2257
|
+
if (activity) activities.push(activity);
|
|
2258
|
+
} catch {}
|
|
2429
2259
|
}
|
|
2430
|
-
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
async function continueLocalCodexSessionInner(params) {
|
|
2434
|
-
await requireCatalogEligibleThread(params.control, params.threadId);
|
|
2435
|
-
const existing = await findAdoptedSessionEntry({
|
|
2436
|
-
bindingStore: params.bindingStore,
|
|
2437
|
-
config: params.config,
|
|
2438
|
-
runtime: params.api.runtime,
|
|
2439
|
-
threadId: params.threadId
|
|
2440
|
-
});
|
|
2441
|
-
if (existing) {
|
|
2442
|
-
const boundThreadId = requireBoundThread(existing);
|
|
2443
|
-
const boundThread = await params.control.readThread(boundThreadId, true);
|
|
2444
|
-
if (boundThread.id !== boundThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
2445
|
-
const changedError = () => new CatalogParamsError("Codex OpenClaw session changed before it could be opened. Retry.");
|
|
2446
|
-
if (!await params.api.runtime.agent.session.patchSessionEntry({
|
|
2447
|
-
sessionKey: existing.key,
|
|
2448
|
-
readConsistency: "latest",
|
|
2449
|
-
preserveActivity: true,
|
|
2450
|
-
update: (entry) => {
|
|
2451
|
-
if (entry.sessionId?.trim() !== existing.sessionId || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true) throw changedError();
|
|
2452
|
-
return { archivedAt: void 0 };
|
|
2453
|
-
}
|
|
2454
|
-
})) throw changedError();
|
|
2455
|
-
const connectionFingerprint = params.control.connectionFingerprint;
|
|
2456
|
-
if (connectionFingerprint) params.onContinued?.({
|
|
2457
|
-
connectionFingerprint,
|
|
2458
|
-
...codexUpstreamBaseline(boundThread, boundCatalogSessionId)
|
|
2459
|
-
});
|
|
2460
|
-
return {
|
|
2461
|
-
sessionKey: existing.key,
|
|
2462
|
-
disposition: "existing"
|
|
2463
|
-
};
|
|
2464
|
-
}
|
|
2465
|
-
const sourceThread = await params.control.readThread(params.threadId, true);
|
|
2466
|
-
if (sourceThread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
2467
|
-
if (sourceThread.status?.type !== "notLoaded") requireIdleThread(sourceThread, "continue");
|
|
2468
|
-
const connectionFingerprint = params.control.connectionFingerprint;
|
|
2469
|
-
if (!connectionFingerprint) throw new Error("Codex Continue requires a pinned app-server connection");
|
|
2470
|
-
const adopted = await createOrReuseAdoptedSession({
|
|
2471
|
-
api: params.api,
|
|
2472
|
-
bindingStore: params.bindingStore,
|
|
2473
|
-
config: params.config,
|
|
2474
|
-
sourceThread,
|
|
2475
|
-
connectionFingerprint
|
|
2476
|
-
});
|
|
2477
|
-
const boundThreadId = requireBoundThread(adopted);
|
|
2478
|
-
const baselineThread = boundThreadId === sourceThread.id ? sourceThread : await params.control.readThread(boundThreadId, true);
|
|
2479
|
-
if (baselineThread.id !== boundThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
2480
|
-
params.onContinued?.({
|
|
2481
|
-
connectionFingerprint,
|
|
2482
|
-
...codexUpstreamBaseline(baselineThread, boundCatalogSessionId)
|
|
2260
|
+
return activities;
|
|
2483
2261
|
});
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2262
|
+
}
|
|
2263
|
+
function createChecker(params) {
|
|
2264
|
+
const resolveThreadId = async (probe) => {
|
|
2265
|
+
const config = params.getRuntimeConfig();
|
|
2266
|
+
const sessionId = params.api.runtime.agent.session.getSessionEntry({
|
|
2267
|
+
agentId: probe.agentId,
|
|
2268
|
+
sessionKey: probe.sessionKey,
|
|
2269
|
+
readConsistency: "latest"
|
|
2270
|
+
})?.sessionId?.trim();
|
|
2271
|
+
if (!sessionId) return probe.threadId;
|
|
2272
|
+
const binding = await params.bindingStore.read(sessionBindingIdentity({
|
|
2273
|
+
sessionId,
|
|
2274
|
+
sessionKey: probe.sessionKey,
|
|
2275
|
+
config
|
|
2276
|
+
}));
|
|
2277
|
+
return binding?.connectionScope === "supervision" && binding.supervisionSourceThreadId === probe.threadId ? binding.threadId : probe.threadId;
|
|
2278
|
+
};
|
|
2279
|
+
return async (probes) => {
|
|
2280
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2281
|
+
for (const probe of probes) {
|
|
2282
|
+
const fingerprint = upstreamConnectionFingerprint(probe);
|
|
2283
|
+
if (!fingerprint) continue;
|
|
2284
|
+
const control = params.control.forUpstream(probe.agentId, fingerprint);
|
|
2285
|
+
if (!control) continue;
|
|
2286
|
+
const key = `${probe.agentId}\0${fingerprint}`;
|
|
2287
|
+
const group = groups.get(key) ?? {
|
|
2288
|
+
control,
|
|
2289
|
+
probes: []
|
|
2290
|
+
};
|
|
2291
|
+
group.probes.push(probe);
|
|
2292
|
+
groups.set(key, group);
|
|
2293
|
+
}
|
|
2294
|
+
return (await Promise.all([...groups.values()].map((group) => checkCodexUpstreamActivity(group.probes, group.control, resolveThreadId)))).flat();
|
|
2487
2295
|
};
|
|
2488
2296
|
}
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
const
|
|
2493
|
-
if (current) return await current;
|
|
2494
|
-
const operation = runSessionActionExclusive(sourceKey, async () => params.control.withPinnedConnection(async (control) => continueLocalCodexSessionInner({
|
|
2495
|
-
...params,
|
|
2496
|
-
control
|
|
2497
|
-
})));
|
|
2498
|
-
continueOperations.set(sourceKey, operation);
|
|
2297
|
+
//#endregion
|
|
2298
|
+
//#region extensions/codex/src/session-catalog-homes.ts
|
|
2299
|
+
function existingCatalogHomeCandidates(value, label) {
|
|
2300
|
+
const codexHome = canonicalCodexCatalogHome(value);
|
|
2499
2301
|
try {
|
|
2500
|
-
return
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2302
|
+
if (!fsSync.statSync(codexHome).isDirectory()) return [];
|
|
2303
|
+
} catch {
|
|
2304
|
+
return [];
|
|
2503
2305
|
}
|
|
2306
|
+
return [{
|
|
2307
|
+
codexHome,
|
|
2308
|
+
label: `Local Codex · ${label ?? path.basename(codexHome)}`
|
|
2309
|
+
}];
|
|
2504
2310
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2311
|
+
/** Resolves every local Codex store the operator already owns, without path disclosure. */
|
|
2312
|
+
function resolveCodexCatalogHomes(params) {
|
|
2313
|
+
const { config, env, ownerAgentId, pluginConfig } = params;
|
|
2314
|
+
const ownerAgentDir = resolveAgentDir(config, ownerAgentId, env);
|
|
2315
|
+
const configuredHomes = readCodexPluginConfig(pluginConfig).sessionCatalog?.homes ?? [];
|
|
2316
|
+
const base = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
2317
|
+
pluginConfig,
|
|
2318
|
+
env,
|
|
2319
|
+
agentDir: ownerAgentDir,
|
|
2320
|
+
config
|
|
2321
|
+
});
|
|
2322
|
+
const primaryCodexHome = canonicalCodexCatalogHome(resolveCodexAppServerLocalHomeDir(base.start, ownerAgentDir, env));
|
|
2323
|
+
const processUserHome = canonicalCodexCatalogHome(resolveCodexAppServerUserHomeDir(env));
|
|
2324
|
+
const processHomeConfigured = Boolean(env.CODEX_HOME?.trim());
|
|
2325
|
+
const candidates = [{
|
|
2326
|
+
codexHome: primaryCodexHome,
|
|
2327
|
+
label: "Local Codex",
|
|
2328
|
+
usesProcessHomeFallback: base.start.transport === "stdio" && base.start.homeScope === "user" && !processHomeConfigured
|
|
2329
|
+
}];
|
|
2330
|
+
if (base.start.transport === "stdio") {
|
|
2331
|
+
candidates.push({
|
|
2332
|
+
codexHome: processUserHome,
|
|
2333
|
+
label: "Local Codex · user",
|
|
2334
|
+
usesProcessHomeFallback: !processHomeConfigured
|
|
2335
|
+
});
|
|
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) => {
|
|
2338
|
+
const { path: home, label } = typeof entry === "string" ? { path: entry } : entry;
|
|
2339
|
+
return existingCatalogHomeCandidates(home, label);
|
|
2518
2340
|
}));
|
|
2519
|
-
if (binding?.connectionScope === "supervision" && binding.supervisionSourceThreadId === params.threadId && binding.pendingSupervisionBranch?.sourceThreadId === params.threadId) throw new CatalogParamsError("Codex session cannot be archived until its OpenClaw branch starts");
|
|
2520
2341
|
}
|
|
2342
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2343
|
+
const homes = [];
|
|
2344
|
+
for (const candidate of candidates) {
|
|
2345
|
+
if (seen.has(candidate.codexHome)) continue;
|
|
2346
|
+
seen.add(candidate.codexHome);
|
|
2347
|
+
const sourceHomeId = codexCatalogHomeId(candidate.codexHome);
|
|
2348
|
+
const primary = homes.length === 0;
|
|
2349
|
+
homes.push({
|
|
2350
|
+
sourceHomeId,
|
|
2351
|
+
hostId: primary ? CODEX_LOCAL_SESSION_HOST_ID : `${CODEX_LOCAL_SESSION_HOST_ID}:${sourceHomeId}`,
|
|
2352
|
+
label: candidate.label,
|
|
2353
|
+
agentDir: ownerAgentDir,
|
|
2354
|
+
appServer: primary ? base : {
|
|
2355
|
+
...base,
|
|
2356
|
+
start: {
|
|
2357
|
+
...base.start,
|
|
2358
|
+
homeScope: "user",
|
|
2359
|
+
env: {
|
|
2360
|
+
...base.start.env,
|
|
2361
|
+
CODEX_HOME: candidate.codexHome
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
},
|
|
2365
|
+
...base.connectionClass === "remote" ? {} : { localSessionsRoot: path.join(candidate.codexHome, "sessions") },
|
|
2366
|
+
usesProcessHomeFallback: candidate.usesProcessHomeFallback ?? false
|
|
2367
|
+
});
|
|
2368
|
+
if (homes.length >= 100) break;
|
|
2369
|
+
}
|
|
2370
|
+
return homes;
|
|
2371
|
+
}
|
|
2372
|
+
/** Discovers Codex homes once per immutable Gateway config generation. */
|
|
2373
|
+
function createCodexCatalogHomeResolver(params) {
|
|
2374
|
+
const env = params.env ?? process.env;
|
|
2375
|
+
const homesByConfig = /* @__PURE__ */ new WeakMap();
|
|
2376
|
+
const buildSnapshot = (config) => {
|
|
2377
|
+
const pluginConfig = params.getPluginConfig();
|
|
2378
|
+
const homesByAgent = new Map(listAgentIds(config).map((agentId) => [agentId, resolveCodexCatalogHomes({
|
|
2379
|
+
config,
|
|
2380
|
+
pluginConfig,
|
|
2381
|
+
ownerAgentId: agentId,
|
|
2382
|
+
env
|
|
2383
|
+
})]));
|
|
2384
|
+
replaceCodexCatalogConnectionHomes([...homesByAgent.values()].flatMap((homes) => homes.filter((home) => home.appServer.start.transport === "stdio").map((home) => ({
|
|
2385
|
+
agentDir: home.agentDir,
|
|
2386
|
+
fingerprint: buildCodexAppServerConnectionFingerprint(home.appServer, home.agentDir),
|
|
2387
|
+
codexHome: resolveCodexAppServerLocalHomeDir(home.appServer.start, home.agentDir, env)
|
|
2388
|
+
}))));
|
|
2389
|
+
homesByConfig.set(config, homesByAgent);
|
|
2390
|
+
return homesByAgent;
|
|
2391
|
+
};
|
|
2392
|
+
let lastSnapshot = buildSnapshot(params.config);
|
|
2393
|
+
return { forAgent(agentId) {
|
|
2394
|
+
const config = params.getRuntimeConfig();
|
|
2395
|
+
if (!config) return lastSnapshot.get(agentId) ?? [];
|
|
2396
|
+
const cached = homesByConfig.get(config);
|
|
2397
|
+
if (cached) return cached.get(agentId) ?? [];
|
|
2398
|
+
lastSnapshot = buildSnapshot(config);
|
|
2399
|
+
return lastSnapshot.get(agentId) ?? [];
|
|
2400
|
+
} };
|
|
2521
2401
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2402
|
+
//#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
|
+
//#region extensions/codex/src/session-catalog-control.ts
|
|
2494
|
+
const CODEX_SESSION_CATALOG_LIST_TTL_MS = 32e3;
|
|
2495
|
+
const CODEX_SESSION_CATALOG_LIST_CACHE_MAX_ENTRIES = 32;
|
|
2496
|
+
function codexCatalogPageCacheKey(params, agentId, source) {
|
|
2497
|
+
return JSON.stringify([
|
|
2498
|
+
agentId,
|
|
2499
|
+
source?.sourceHomeId ?? null,
|
|
2500
|
+
params.cursor ?? null,
|
|
2501
|
+
params.limit ?? null,
|
|
2502
|
+
params.searchTerm?.trim().toLocaleLowerCase() || null,
|
|
2503
|
+
params.cwd?.trim() || null
|
|
2504
|
+
]);
|
|
2505
|
+
}
|
|
2506
|
+
function createCodexCatalogRequestSnapshot(requestTimeoutMs, request) {
|
|
2507
|
+
return {
|
|
2508
|
+
requestTimeoutMs,
|
|
2509
|
+
listThreads: (params, timeoutMs) => request(CODEX_CONTROL_METHODS.listThreads, params, timeoutMs),
|
|
2510
|
+
listThreadTurns: (params) => request(CODEX_CONTROL_METHODS.listThreadTurns, params),
|
|
2511
|
+
forkThread: (params) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params)),
|
|
2512
|
+
readThread: async (threadId, includeTurns) => (await request(CODEX_CONTROL_METHODS.readThread, {
|
|
2513
|
+
threadId,
|
|
2514
|
+
includeTurns
|
|
2515
|
+
})).thread,
|
|
2516
|
+
archiveThread: async (threadId) => {
|
|
2517
|
+
await request(CODEX_CONTROL_METHODS.archiveThread, { threadId });
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
function createCodexSessionCatalogControlFromRequests(params) {
|
|
2522
|
+
return {
|
|
2523
|
+
...params.clientId ? { clientId: params.clientId } : {},
|
|
2524
|
+
...params.connectionFingerprint ? { connectionFingerprint: params.connectionFingerprint } : {},
|
|
2525
|
+
withPinnedConnection: params.withPinnedConnection,
|
|
2526
|
+
async listPage(pageParams) {
|
|
2527
|
+
const limit = normalizeLimit(pageParams.limit, "limit");
|
|
2528
|
+
const search = pageParams.searchTerm?.trim().toLocaleLowerCase() || void 0;
|
|
2529
|
+
const cwd = pageParams.cwd?.trim() || void 0;
|
|
2530
|
+
const maxPages = search ? 20 : 1;
|
|
2531
|
+
const sessions = [];
|
|
2532
|
+
const managedThreads = [];
|
|
2533
|
+
let cursor = readControlCursor(pageParams.cursor, "request");
|
|
2534
|
+
let nextCursor;
|
|
2535
|
+
let backwardsCursor;
|
|
2536
|
+
const seenCursors = new Set(cursor ? [cursor] : []);
|
|
2537
|
+
const requests = params.createRequestSnapshot();
|
|
2538
|
+
const deadline = params.now() + requests.requestTimeoutMs;
|
|
2539
|
+
for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {
|
|
2540
|
+
const remainingTimeoutMs = Math.ceil(deadline - params.now());
|
|
2541
|
+
if (remainingTimeoutMs <= 0) throw new Error("Codex session catalog listing timed out");
|
|
2542
|
+
const response = await requests.listThreads({
|
|
2543
|
+
archived: false,
|
|
2544
|
+
limit: limit - sessions.length,
|
|
2545
|
+
modelProviders: [],
|
|
2546
|
+
sortKey: "updated_at",
|
|
2547
|
+
sortDirection: "desc",
|
|
2548
|
+
...cwd ? { cwd } : {},
|
|
2549
|
+
...cursor ? { cursor } : {}
|
|
2550
|
+
}, remainingTimeoutMs);
|
|
2551
|
+
if (pageIndex === 0) backwardsCursor = readControlCursor(response.backwardsCursor, "backwards response");
|
|
2552
|
+
for (const thread of response.data) {
|
|
2553
|
+
if (await isOpenClawManagedCodexThread(thread, params.localSessionsRoot)) {
|
|
2554
|
+
const rolloutPath = typeof thread.path === "string" ? thread.path.trim() : "";
|
|
2555
|
+
managedThreads.push({
|
|
2556
|
+
threadId: thread.id,
|
|
2557
|
+
...rolloutPath ? { rolloutPath } : {}
|
|
2558
|
+
});
|
|
2559
|
+
continue;
|
|
2541
2560
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2561
|
+
const session = toCatalogSession(thread, false);
|
|
2562
|
+
if (session && (!search || (session.name ?? session.fallbackName)?.toLocaleLowerCase().includes(search))) sessions.push(session);
|
|
2563
|
+
}
|
|
2564
|
+
nextCursor = readControlCursor(response.nextCursor, "next response");
|
|
2565
|
+
if (!nextCursor || sessions.length >= limit) break;
|
|
2566
|
+
if (seenCursors.has(nextCursor)) throw new Error("Codex session catalog returned a repeated search cursor");
|
|
2567
|
+
seenCursors.add(nextCursor);
|
|
2568
|
+
cursor = nextCursor;
|
|
2569
|
+
}
|
|
2570
|
+
return {
|
|
2571
|
+
sessions,
|
|
2572
|
+
...managedThreads.length > 0 ? { managedThreads } : {},
|
|
2573
|
+
...nextCursor ? { nextCursor } : {},
|
|
2574
|
+
...backwardsCursor ? { backwardsCursor } : {}
|
|
2575
|
+
};
|
|
2576
|
+
},
|
|
2577
|
+
async listDescendantPage(listParams) {
|
|
2578
|
+
const requests = params.createRequestSnapshot();
|
|
2579
|
+
return await requests.listThreads(listParams, requests.requestTimeoutMs);
|
|
2580
|
+
},
|
|
2581
|
+
async readThread(threadId, includeTurns = false) {
|
|
2582
|
+
return await params.createRequestSnapshot().readThread(threadId, includeTurns);
|
|
2583
|
+
},
|
|
2584
|
+
async listTurnPage(listParams) {
|
|
2585
|
+
return await params.createRequestSnapshot().listThreadTurns(listParams);
|
|
2586
|
+
},
|
|
2587
|
+
async forkThread(forkParams) {
|
|
2588
|
+
return await params.createRequestSnapshot().forkThread(forkParams);
|
|
2589
|
+
},
|
|
2590
|
+
async archiveThread(threadId) {
|
|
2591
|
+
await params.createRequestSnapshot().archiveThread(threadId);
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
/** Builds the passive catalog over the Codex plugin's canonical shared client. */
|
|
2596
|
+
function createCodexSessionCatalogControl(params) {
|
|
2597
|
+
const now = params.now ?? Date.now;
|
|
2598
|
+
const getPluginConfig = () => params.getPluginConfig();
|
|
2599
|
+
const homeResolver = createCodexCatalogHomeResolver({
|
|
2600
|
+
config: params.getRuntimeConfig() ?? params.config ?? {},
|
|
2601
|
+
getRuntimeConfig: params.getRuntimeConfig,
|
|
2602
|
+
getPluginConfig: params.getPluginConfig,
|
|
2603
|
+
...params.env ? { env: params.env } : {}
|
|
2604
|
+
});
|
|
2605
|
+
const requestOptionsByConfig = /* @__PURE__ */ new WeakMap();
|
|
2606
|
+
const catalogPagesByConfig = /* @__PURE__ */ new WeakMap();
|
|
2607
|
+
const resolveRequestOptions = (startOptions, agentId, source) => {
|
|
2608
|
+
const runtimeConfig = params.getRuntimeConfig();
|
|
2609
|
+
const agentDir = source?.agentDir ?? resolveAgentDir(runtimeConfig ?? {}, agentId);
|
|
2610
|
+
const resolvedStartOptions = source?.appServer.start ?? startOptions;
|
|
2611
|
+
if (!runtimeConfig) return {
|
|
2612
|
+
agentDir,
|
|
2613
|
+
config: void 0,
|
|
2614
|
+
startOptions: structuredClone(resolvedStartOptions)
|
|
2615
|
+
};
|
|
2616
|
+
let byAgent = requestOptionsByConfig.get(runtimeConfig);
|
|
2617
|
+
const cacheKey = `${agentId ?? ""}\0${source?.sourceHomeId ?? ""}`;
|
|
2618
|
+
const cached = byAgent?.get(cacheKey);
|
|
2619
|
+
if (cached) return cached;
|
|
2620
|
+
const resolved = {
|
|
2621
|
+
agentDir,
|
|
2622
|
+
config: structuredClone(runtimeConfig),
|
|
2623
|
+
startOptions: structuredClone(resolvedStartOptions)
|
|
2624
|
+
};
|
|
2625
|
+
if (!byAgent) {
|
|
2626
|
+
byAgent = /* @__PURE__ */ new Map();
|
|
2627
|
+
requestOptionsByConfig.set(runtimeConfig, byAgent);
|
|
2628
|
+
}
|
|
2629
|
+
byAgent.set(cacheKey, resolved);
|
|
2630
|
+
return resolved;
|
|
2631
|
+
};
|
|
2632
|
+
const createRequestSnapshot = (agentId, source) => {
|
|
2633
|
+
const pluginConfig = getPluginConfig();
|
|
2634
|
+
const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
|
|
2635
|
+
const requestOptions = resolveRequestOptions(runtime.start, agentId, source);
|
|
2636
|
+
return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await codexControlRequest(pluginConfig, method, requestParams, {
|
|
2637
|
+
...requestOptions,
|
|
2638
|
+
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
2639
|
+
}));
|
|
2640
|
+
};
|
|
2641
|
+
const forRequest = (agentId, source) => {
|
|
2642
|
+
const withPinnedConnection = async (run) => {
|
|
2643
|
+
const pluginConfig = getPluginConfig();
|
|
2644
|
+
const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
|
|
2645
|
+
const { agentDir, config: runtimeConfig, startOptions } = resolveRequestOptions(runtime.start, agentId, source);
|
|
2646
|
+
const client = await getLeasedSharedCodexAppServerClient({
|
|
2647
|
+
agentDir,
|
|
2648
|
+
config: runtimeConfig,
|
|
2649
|
+
startOptions,
|
|
2650
|
+
timeoutMs: runtime.requestTimeoutMs
|
|
2545
2651
|
});
|
|
2652
|
+
try {
|
|
2653
|
+
const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await requestCodexAppServerClientJson({
|
|
2654
|
+
client,
|
|
2655
|
+
method,
|
|
2656
|
+
requestParams,
|
|
2657
|
+
config: runtimeConfig,
|
|
2658
|
+
timeoutMs: timeoutMs ?? runtime.requestTimeoutMs
|
|
2659
|
+
}));
|
|
2660
|
+
const pinnedControl = createCodexSessionCatalogControlFromRequests({
|
|
2661
|
+
clientId: resolveCodexAppServerClientInstanceId(client),
|
|
2662
|
+
connectionFingerprint: buildCodexAppServerConnectionFingerprint(runtime, agentDir),
|
|
2663
|
+
createRequestSnapshot: () => requests,
|
|
2664
|
+
...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
|
|
2665
|
+
now,
|
|
2666
|
+
withPinnedConnection: async (nestedRun) => await nestedRun(pinnedControl)
|
|
2667
|
+
});
|
|
2668
|
+
return await run(pinnedControl);
|
|
2669
|
+
} finally {
|
|
2670
|
+
releaseLeasedSharedCodexAppServerClient(client);
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
const control = createCodexSessionCatalogControlFromRequests({
|
|
2674
|
+
createRequestSnapshot: () => createRequestSnapshot(agentId, source),
|
|
2675
|
+
...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
|
|
2676
|
+
now,
|
|
2677
|
+
withPinnedConnection
|
|
2546
2678
|
});
|
|
2547
|
-
|
|
2679
|
+
return {
|
|
2680
|
+
...control,
|
|
2681
|
+
async listPage(pageParams) {
|
|
2682
|
+
const runtimeConfig = params.getRuntimeConfig();
|
|
2683
|
+
if (!runtimeConfig) return await control.listPage(pageParams);
|
|
2684
|
+
let cache = catalogPagesByConfig.get(runtimeConfig);
|
|
2685
|
+
if (!cache) {
|
|
2686
|
+
cache = /* @__PURE__ */ new Map();
|
|
2687
|
+
catalogPagesByConfig.set(runtimeConfig, cache);
|
|
2688
|
+
}
|
|
2689
|
+
const key = codexCatalogPageCacheKey(pageParams, agentId, source);
|
|
2690
|
+
const cached = cache.get(key);
|
|
2691
|
+
if (pageParams.forceRefresh !== true && cached) {
|
|
2692
|
+
cache.delete(key);
|
|
2693
|
+
cache.set(key, cached);
|
|
2694
|
+
if (cached.expiresAt > now()) return cached.value ?? await cached.page;
|
|
2695
|
+
}
|
|
2696
|
+
if (cached) cache.delete(key);
|
|
2697
|
+
const page = control.listPage(pageParams);
|
|
2698
|
+
const staleValue = cached?.value;
|
|
2699
|
+
const entry = {
|
|
2700
|
+
expiresAt: Number.POSITIVE_INFINITY,
|
|
2701
|
+
page,
|
|
2702
|
+
...staleValue ? { value: staleValue } : {}
|
|
2703
|
+
};
|
|
2704
|
+
cache.set(key, entry);
|
|
2705
|
+
pruneMapToMaxSize(cache, CODEX_SESSION_CATALOG_LIST_CACHE_MAX_ENTRIES);
|
|
2706
|
+
const settle = (value) => {
|
|
2707
|
+
if (cache.get(key) === entry) {
|
|
2708
|
+
entry.value = value;
|
|
2709
|
+
entry.expiresAt = now() + CODEX_SESSION_CATALOG_LIST_TTL_MS;
|
|
2710
|
+
}
|
|
2711
|
+
return value;
|
|
2712
|
+
};
|
|
2713
|
+
const restore = () => {
|
|
2714
|
+
if (cache.get(key) !== entry) return;
|
|
2715
|
+
if (staleValue) cache.set(key, {
|
|
2716
|
+
expiresAt: now(),
|
|
2717
|
+
page: Promise.resolve(staleValue),
|
|
2718
|
+
value: staleValue
|
|
2719
|
+
});
|
|
2720
|
+
else cache.delete(key);
|
|
2721
|
+
};
|
|
2722
|
+
if (pageParams.forceRefresh !== true && staleValue) {
|
|
2723
|
+
page.then(settle, restore);
|
|
2724
|
+
return staleValue;
|
|
2725
|
+
}
|
|
2726
|
+
try {
|
|
2727
|
+
return settle(await page);
|
|
2728
|
+
} catch (error) {
|
|
2729
|
+
restore();
|
|
2730
|
+
throw error;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
};
|
|
2735
|
+
const homesForAgent = (agentId) => homeResolver.forAgent(agentId);
|
|
2736
|
+
const forUpstream = (agentId, connectionFingerprint) => {
|
|
2737
|
+
const source = homesForAgent(agentId).find((home) => buildCodexAppServerConnectionFingerprint(home.appServer, home.agentDir) === connectionFingerprint);
|
|
2738
|
+
return source ? forRequest(agentId, source) : void 0;
|
|
2739
|
+
};
|
|
2740
|
+
return {
|
|
2741
|
+
forRequest,
|
|
2742
|
+
forUpstream,
|
|
2743
|
+
homesForAgent
|
|
2744
|
+
};
|
|
2548
2745
|
}
|
|
2746
|
+
//#endregion
|
|
2747
|
+
//#region extensions/codex/src/session-catalog.ts
|
|
2549
2748
|
/** Allows read-only catalog and transcript commands on supported paired-node platforms. */
|
|
2550
2749
|
function createCodexSessionCatalogNodeInvokePolicies() {
|
|
2551
2750
|
return [{
|
|
@@ -2563,7 +2762,7 @@ function createCodexSessionCatalogNodeInvokePolicies() {
|
|
|
2563
2762
|
}];
|
|
2564
2763
|
}
|
|
2565
2764
|
function toGenericCatalogHost(host, localTerminalAvailable) {
|
|
2566
|
-
const local = host.hostId
|
|
2765
|
+
const local = isLocalCodexCatalogHost(host.hostId);
|
|
2567
2766
|
return {
|
|
2568
2767
|
hostId: host.hostId,
|
|
2569
2768
|
label: host.label,
|
|
@@ -2578,6 +2777,7 @@ function toGenericCatalogHost(host, localTerminalAvailable) {
|
|
|
2578
2777
|
const name = session.name ?? session.fallbackName;
|
|
2579
2778
|
return {
|
|
2580
2779
|
threadId: session.threadId,
|
|
2780
|
+
...session.sourceHomeId ? { sourceHomeId: session.sourceHomeId } : {},
|
|
2581
2781
|
...name ? { name } : {},
|
|
2582
2782
|
...session.cwd ? { cwd: session.cwd } : {},
|
|
2583
2783
|
status: session.status,
|
|
@@ -2599,13 +2799,44 @@ function toGenericCatalogHost(host, localTerminalAvailable) {
|
|
|
2599
2799
|
...host.error ? { error: host.error } : {}
|
|
2600
2800
|
};
|
|
2601
2801
|
}
|
|
2802
|
+
function isLocalCodexCatalogHost(hostId) {
|
|
2803
|
+
return hostId === "gateway:local" || hostId.startsWith(`gateway:local:`);
|
|
2804
|
+
}
|
|
2805
|
+
function resolveLocalCatalogHomeForThread(params) {
|
|
2806
|
+
if (params.homes.length === 0) throw new CatalogParamsError("local Codex sessions are unavailable in isolated state");
|
|
2807
|
+
const exact = params.sourceHomeId ? params.homes.filter((home) => home.sourceHomeId === params.sourceHomeId) : params.homes.filter((home) => home.hostId === params.hostId);
|
|
2808
|
+
if (exact.length === 0 || params.sourceHomeId && exact[0]?.hostId !== params.hostId) throw new CatalogParamsError("Codex session source home is unavailable");
|
|
2809
|
+
return exact[0];
|
|
2810
|
+
}
|
|
2602
2811
|
function registerCodexSessionCatalog(params) {
|
|
2603
|
-
const
|
|
2604
|
-
const
|
|
2605
|
-
return
|
|
2812
|
+
const catalogHomes = (agentId, allowProcessHomeFallback) => {
|
|
2813
|
+
const homes = params.control.homesForAgent(agentId);
|
|
2814
|
+
return allowProcessHomeFallback === false ? homes.filter((home) => !home.usesProcessHomeFallback) : homes;
|
|
2815
|
+
};
|
|
2816
|
+
const resolveRequestAgentId = (agentId) => resolveSessionAgentIds({
|
|
2817
|
+
config: params.getRuntimeConfig() ?? params.api.config,
|
|
2818
|
+
agentId
|
|
2819
|
+
}).sessionAgentId;
|
|
2820
|
+
const bindRequest = (request) => {
|
|
2821
|
+
const agentId = resolveRequestAgentId(request.agentId);
|
|
2822
|
+
const source = isLocalCodexCatalogHost(request.hostId) ? resolveLocalCatalogHomeForThread({
|
|
2823
|
+
homes: [...catalogHomes(agentId, request.allowProcessHomeFallback)],
|
|
2824
|
+
hostId: request.hostId,
|
|
2825
|
+
...request.sourceHomeId ? { sourceHomeId: request.sourceHomeId } : {}
|
|
2826
|
+
}) : void 0;
|
|
2827
|
+
return {
|
|
2828
|
+
agentId,
|
|
2829
|
+
source,
|
|
2830
|
+
control: params.control.forRequest(agentId, source)
|
|
2831
|
+
};
|
|
2606
2832
|
};
|
|
2607
|
-
const
|
|
2608
|
-
|
|
2833
|
+
const bindLocalRequest = (request) => {
|
|
2834
|
+
const bound = bindRequest(request);
|
|
2835
|
+
if (!bound.source) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
2836
|
+
return {
|
|
2837
|
+
...bound,
|
|
2838
|
+
source: bound.source
|
|
2839
|
+
};
|
|
2609
2840
|
};
|
|
2610
2841
|
const checkUpstreamActivity = createChecker(params);
|
|
2611
2842
|
params.api.registerSessionCatalog({
|
|
@@ -2615,9 +2846,12 @@ function registerCodexSessionCatalog(params) {
|
|
|
2615
2846
|
resolveCreateSession: ({ agentId }) => resolveCodexCatalogCreateSession(params.getRuntimeConfig() ?? params.api.config, agentId),
|
|
2616
2847
|
list: async (query) => {
|
|
2617
2848
|
const localTerminalAvailable = resolveLocalCodexTerminalExecutable() !== void 0;
|
|
2618
|
-
const { allowProcessHomeFallback, listNodes, onHost, sessionEntries, ...gatewayQuery } = query;
|
|
2849
|
+
const { agentId: requestedAgentId, allowProcessHomeFallback, listNodes, onHost, sessionEntries, ...gatewayQuery } = query;
|
|
2850
|
+
const agentId = resolveRequestAgentId(requestedAgentId);
|
|
2619
2851
|
const mapHost = (host) => toGenericCatalogHost(host, localTerminalAvailable);
|
|
2852
|
+
const localHomes = [...catalogHomes(agentId, allowProcessHomeFallback)];
|
|
2620
2853
|
return (await listCodexSessionCatalog({
|
|
2854
|
+
agentId,
|
|
2621
2855
|
bindingStore: params.bindingStore,
|
|
2622
2856
|
config: params.getRuntimeConfig(),
|
|
2623
2857
|
runtime: params.api.runtime,
|
|
@@ -2625,20 +2859,21 @@ function registerCodexSessionCatalog(params) {
|
|
|
2625
2859
|
query: gatewayQuery,
|
|
2626
2860
|
listNodes,
|
|
2627
2861
|
sessionEntries,
|
|
2628
|
-
|
|
2862
|
+
localHomes,
|
|
2629
2863
|
...onHost ? { onHost: (host) => onHost(mapHost(host)) } : {}
|
|
2630
2864
|
})).hosts.map(mapHost);
|
|
2631
2865
|
},
|
|
2632
2866
|
read: async (request) => {
|
|
2633
|
-
const {
|
|
2634
|
-
assertLocalAccess(catalogRequest.hostId, allowProcessHomeFallback);
|
|
2867
|
+
const { agentId, source, control } = bindRequest(request);
|
|
2635
2868
|
const page = await readCodexSessionTranscript({
|
|
2869
|
+
agentId,
|
|
2636
2870
|
runtime: params.api.runtime,
|
|
2637
|
-
control
|
|
2638
|
-
hostId:
|
|
2639
|
-
threadId:
|
|
2640
|
-
cursor:
|
|
2641
|
-
limit:
|
|
2871
|
+
control,
|
|
2872
|
+
hostId: request.hostId,
|
|
2873
|
+
threadId: request.threadId,
|
|
2874
|
+
cursor: request.cursor,
|
|
2875
|
+
limit: request.limit ?? 20,
|
|
2876
|
+
...source ? { source } : {}
|
|
2642
2877
|
});
|
|
2643
2878
|
return {
|
|
2644
2879
|
...page,
|
|
@@ -2646,58 +2881,69 @@ function registerCodexSessionCatalog(params) {
|
|
|
2646
2881
|
};
|
|
2647
2882
|
},
|
|
2648
2883
|
continueSession: async (request) => {
|
|
2649
|
-
assertLocalAccess(request.hostId, request.allowProcessHomeFallback);
|
|
2650
2884
|
const config = params.getRuntimeConfig();
|
|
2651
2885
|
if (!config) throw new Error("OpenClaw runtime config is unavailable");
|
|
2652
2886
|
if (request.hostId.startsWith("node:")) return await continueNodeCodexSession({
|
|
2887
|
+
agentId: resolveRequestAgentId(request.agentId),
|
|
2653
2888
|
api: params.api,
|
|
2654
2889
|
config,
|
|
2655
2890
|
hostId: request.hostId,
|
|
2656
2891
|
threadId: request.threadId,
|
|
2657
2892
|
clientScopes: request.clientScopes
|
|
2658
2893
|
});
|
|
2659
|
-
if (request.hostId
|
|
2894
|
+
if (!isLocalCodexCatalogHost(request.hostId)) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
2895
|
+
const { agentId, source, control } = bindLocalRequest(request);
|
|
2660
2896
|
let upstreamBaseline;
|
|
2661
2897
|
return codexUpstreamContinueResult((await continueLocalCodexSession({
|
|
2898
|
+
agentId,
|
|
2662
2899
|
api: params.api,
|
|
2663
2900
|
bindingStore: params.bindingStore,
|
|
2664
2901
|
config,
|
|
2665
|
-
control
|
|
2902
|
+
control,
|
|
2666
2903
|
threadId: request.threadId,
|
|
2904
|
+
hostId: source.hostId,
|
|
2905
|
+
sourceHomeId: source.sourceHomeId,
|
|
2906
|
+
...source.hostId === "gateway:local" ? { allowLegacy: true } : {},
|
|
2667
2907
|
onContinued: (baseline) => {
|
|
2668
2908
|
upstreamBaseline = baseline;
|
|
2669
2909
|
}
|
|
2670
2910
|
})).sessionKey, request.threadId, upstreamBaseline);
|
|
2671
2911
|
},
|
|
2672
|
-
checkUpstreamActivity: (probes, policy) => checkUpstreamActivity(probes.filter((probe) => probe.hostId
|
|
2912
|
+
checkUpstreamActivity: (probes, policy) => checkUpstreamActivity(probes.filter((probe) => !isLocalCodexCatalogHost(probe.hostId) || policy?.allowProcessHomeFallback !== false || catalogHomes(probe.agentId, false).some((home) => home.hostId === probe.hostId))),
|
|
2673
2913
|
archive: async (request) => {
|
|
2674
|
-
assertLocalAccess(request.hostId, request.allowProcessHomeFallback);
|
|
2675
2914
|
if (request.confirmNoOtherRunner !== true) throw new CatalogParamsError("archive requires confirmation that no other runner is active");
|
|
2676
|
-
if (request.hostId
|
|
2915
|
+
if (!isLocalCodexCatalogHost(request.hostId)) throw new CatalogParamsError("paired-node Codex sessions are view-only");
|
|
2677
2916
|
const config = params.getRuntimeConfig();
|
|
2678
2917
|
if (!config) throw new Error("OpenClaw runtime config is unavailable");
|
|
2918
|
+
const { agentId, source, control } = bindLocalRequest(request);
|
|
2679
2919
|
await archiveLocalCodexSession({
|
|
2920
|
+
agentId,
|
|
2680
2921
|
bindingStore: params.bindingStore,
|
|
2681
2922
|
config,
|
|
2682
|
-
control
|
|
2923
|
+
control,
|
|
2683
2924
|
runtime: params.api.runtime,
|
|
2684
|
-
threadId: request.threadId
|
|
2925
|
+
threadId: request.threadId,
|
|
2926
|
+
hostId: source.hostId,
|
|
2927
|
+
sourceHomeId: source.sourceHomeId,
|
|
2928
|
+
...source.hostId === "gateway:local" ? { allowLegacy: true } : {}
|
|
2685
2929
|
});
|
|
2686
2930
|
return { ok: true };
|
|
2687
2931
|
},
|
|
2688
2932
|
openTerminal: async (request) => {
|
|
2689
|
-
|
|
2933
|
+
const { agentId, source, control } = bindRequest(request);
|
|
2690
2934
|
return await openCodexCatalogTerminal({
|
|
2691
2935
|
api: params.api,
|
|
2692
|
-
control
|
|
2936
|
+
control,
|
|
2693
2937
|
getPluginConfig: params.getPluginConfig,
|
|
2694
2938
|
getRuntimeConfig: params.getRuntimeConfig,
|
|
2695
2939
|
parseCatalogPage,
|
|
2696
|
-
...
|
|
2940
|
+
...source ? { source } : {},
|
|
2941
|
+
...request,
|
|
2942
|
+
agentId
|
|
2697
2943
|
});
|
|
2698
2944
|
},
|
|
2699
2945
|
startTerminalSession: async (request) => {
|
|
2700
|
-
|
|
2946
|
+
if (!request.nodeId && catalogHomes(request.agentId, request.allowProcessHomeFallback).length === 0) throw new CatalogParamsError("local Codex sessions are unavailable in isolated state");
|
|
2701
2947
|
return await startCodexCatalogTerminal({
|
|
2702
2948
|
getPluginConfig: params.getPluginConfig,
|
|
2703
2949
|
getRuntimeConfig: params.getRuntimeConfig,
|
|
@@ -2715,4 +2961,4 @@ const codexSessionCatalogRuntime = {
|
|
|
2715
2961
|
archiveLocal: archiveLocalCodexSession
|
|
2716
2962
|
};
|
|
2717
2963
|
//#endregion
|
|
2718
|
-
export { CODEX_LOCAL_SESSION_HOST_ID as a,
|
|
2964
|
+
export { CODEX_LOCAL_SESSION_HOST_ID as a, createCodexCliSessionNodeInvokePolicies as c, resolveCodexCliSessionForBindingOnNode as d, resumeCodexCliSessionOnNode as f, createCodexSessionCatalogNodeHostCommands as i, formatCodexCliSessions as l, createCodexSessionCatalogNodeInvokePolicies as n, assertCodexArchiveDescendantsUnowned as o, createCodexSessionCatalogControl as r, createCodexCliSessionNodeHostCommands as s, codexSessionCatalogRuntime as t, listCodexCliSessionsOnNode as u };
|