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