@openclaw/codex 2026.9.2 → 2026.9.4
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 +29 -26
- package/dist/apply-CvT8tW5i.js +356 -0
- package/dist/{attempt-client-cleanup-Bp1ZOnk2.js → attempt-client-cleanup-DKHzbwt5.js} +38 -214
- package/dist/{attempt-context-CSazkWns.js → attempt-context-D44yeDII.js} +9 -7
- package/dist/{event-projector-usage-ZqEVbAEY.js → attempt-notifications-C9XufG6l.js} +3 -106
- package/dist/{auth-binding-Ds_QVm-X.js → auth-binding-CWlKVccJ.js} +2 -2
- package/dist/auth-cache-key-7orQae_j.js +498 -0
- package/dist/auth-profile-CwzO4_RG.js +43 -0
- package/dist/auth-profile-selection-DFu9e167.js +45 -0
- package/dist/binding-connection-D3udKbIE.js +60 -0
- package/dist/{bounded-turn-DxGJRYbY.js → bounded-turn-C_Hneydj.js} +28 -125
- package/dist/build-state-BCmg9-5p.js +19 -0
- package/dist/{capabilities-B1mgvpP8.js → capabilities-8vx68WLH.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/client-B57KWPQx.js +4005 -0
- package/dist/command-dispatch-BpduNGVf.js +21 -0
- package/dist/{command-formatters-DSbGMXA0.js → command-formatters-lxlkNZgU.js} +4 -3
- package/dist/{command-handlers-DOJvGv6-.js → command-handlers-tiL9iWj7.js} +788 -135
- package/dist/command-rpc-CR5EOEZX.js +233 -0
- package/dist/{compact-Y_xBqrHN.js → compact-B_H3AOd7.js} +19 -14
- package/dist/config-oIORaQ5T.js +106 -0
- package/dist/{config-CyapYSji.js → config-options-CXMq1T-C.js} +205 -593
- package/dist/config-runtime-D0zGNIHu.js +11 -0
- package/dist/{config-utils-C57JiJrU.js → config-utils-DujwEnhg.js} +2 -4
- package/dist/conversation-binding-DsB4-O7U.js +900 -0
- package/dist/{plugin-marketplace-discovery-B7gHXJ3d.js → conversation-control-CtUu1b3m.js} +11 -143
- package/dist/doctor-contract-api.js +5 -5
- package/dist/{dynamic-tools-BwpvTxaX.js → dynamic-tools-CmN5RAip.js} +133 -76
- package/dist/{effective-mcp-catalog-CY9Jn4bA.js → effective-mcp-catalog-Bd73bqA8.js} +3 -3
- package/dist/{elicitation-input-DMkeki8d.js → elicitation-input-DPoK8F7w.js} +8 -22
- package/dist/ephemeral-turn-D78_VmzO.js +101 -0
- package/dist/{event-projector-assistant-message-B9kp6iGC.js → event-projector-assistant-message-BmBM4qBV.js} +12 -25
- package/dist/event-projector-items-5VhsECCO.js +200 -0
- package/dist/event-projector-usage-BfXmpOec.js +102 -0
- package/dist/{harness-Czh2Sdoc.js → harness-C7rRa7bm.js} +48 -24
- package/dist/harness.js +1 -1
- package/dist/history-rejection-B4nOXNXF.js +13 -0
- package/dist/incognito-session-uhrBF6wJ.js +15 -0
- package/dist/index.js +406 -2915
- package/dist/inference-context-Dr_mzvk4.js +94 -0
- package/dist/inference-proxy-HGXKUIre.js +311 -0
- package/dist/{isolated-completion-CRh-51AK.js → isolated-completion-DWVb5U2J.js} +7 -4
- package/dist/launch-args-RXQwn8zV.js +90 -0
- package/dist/{managed-binary-DcpnJF9x.js → managed-binary-D22rNEcR.js} +13 -103
- package/dist/managed-thread-store-D1t5jh3G.js +66 -0
- package/dist/media-understanding-provider.js +4 -1
- package/dist/migration-provider-api.js +2 -0
- package/dist/{model-catalog-DT5vo6qo.js → model-catalog-Dq2n8f_p.js} +6 -5
- package/dist/{models-CcrgSMQR.js → models-PCKOim-h.js} +3 -3
- package/dist/native-execution-policy-umd0tgh4.js +128 -0
- package/dist/native-subagent-history-DjtPloaU.js +140 -0
- package/dist/native-subagent-history-owner-Pcp7JuVF.js +34 -0
- package/dist/{native-subagent-monitor-B3bvQGcg.js → native-subagent-monitor-fQ7eIXEf.js} +115 -460
- package/dist/native-subagent-task-ids-BzTdT0Uw.js +387 -0
- package/dist/node-cli-sessions-DKaOmVUh.js +527 -0
- package/dist/{node-exec-server.runtime-nfbUOtUB.js → node-exec-server.runtime-Cl2vapEi.js} +55 -24
- package/dist/plan-7uG-Fp36.js +947 -0
- package/dist/plugin-activation-BYQculil.js +138 -0
- package/dist/{thread-resume-BLgqkUc1.js → plugin-app-cache-key-d0eKxy7b.js} +4 -52
- package/dist/{plugin-approval-roundtrip-aQVdyL5g.js → plugin-approval-roundtrip-Bz3FT8W5.js} +1 -1
- package/dist/plugin-inventory-oL4Na1ZG.js +328 -0
- package/dist/plugin-marketplace-discovery-P-Vndfqq.js +137 -0
- package/dist/{plugin-metadata-cache-BwCLTY_U.js → plugin-metadata-cache-ClPJpRio.js} +17 -9
- package/dist/protocol-5bh1G-H7.js +275 -0
- package/dist/provider-MQ8PVkuE.js +50 -0
- package/dist/{provider-capabilities-BpG9Ltc5.js → provider-capabilities-BvCUL69A.js} +2 -2
- package/dist/{rate-limits-Fx_l4eNa.js → rate-limits-CBMmWBr9.js} +1 -1
- package/dist/request-B7hEkBGq.js +212 -0
- package/dist/rpc-error-ttN_QjEm.js +30 -0
- package/dist/{run-attempt-BzthFQSM.js → run-attempt-CvsAAGa0.js} +664 -377
- package/dist/{runtime-artifact-Bh_gf6-t.js → runtime-artifact-BJ0rXBZ_.js} +27 -27
- package/dist/sandbox-guard-BxgyoM8M.js +166 -0
- package/dist/{session-binding-4Ki04sW-.js → session-binding-CI8UuilT.js} +30 -48
- package/dist/{session-binding-record-CyaXZvTe.js → session-binding-record-Bcvslnhf.js} +12 -26
- package/dist/session-catalog-adoption-tcZi1IfX.js +251 -0
- package/dist/session-catalog-archive-CwKg8Xz2.js +56 -0
- package/dist/session-catalog-ct-AH01I.js +1477 -0
- package/dist/session-catalog-home-id-CwhLdC03.js +18 -0
- package/dist/session-catalog-node-adoption-B1tgDW-m.js +182 -0
- package/dist/session-catalog-node-continue-Ci-DXyJP.js +238 -0
- package/dist/session-catalog-provenance-D-dIfRAH.js +96 -0
- package/dist/{session-cli-BjLhpmS1.js → session-cli-DNN_jhGc.js} +3 -2
- package/dist/{session-history-DWmMqiqW.js → session-history-Cz-tOOYj.js} +6 -3
- package/dist/{session-upstream-marker-DReTpGz6.js → session-history-import-DwyijFI6.js} +5 -50
- package/dist/{session-history-read-LHldwvyh.js → session-history-read-cxPUxyVV.js} +21 -7
- package/dist/session-history-worker-runtime.js +18 -6
- package/dist/session-history.worker-BeK1YOyT.js +378 -0
- package/dist/session-history.worker.js +1 -1
- package/dist/{session-initialization-BCFVnLL4.js → session-initialization-Ct0_Hzmg.js} +2 -2
- package/dist/{session-permission-policy-BaueHmSj.js → session-permission-policy-BBmXA8Eg.js} +4 -7
- package/dist/{session-retirement-DNdmYdFc.js → session-retirement-B2YfbAAT.js} +6 -5
- package/dist/session-upstream-marker-D15C9NHp.js +46 -0
- package/dist/{settled-turn-context-BOM3Y1pl.js → settled-turn-context-Du6gdUDd.js} +9 -9
- package/dist/{settled-turn-finalizer-BI-b1sry.js → settled-turn-finalizer-Dn91stmc.js} +8 -7
- package/dist/shared-client-CscigXXL.js +3109 -0
- package/dist/{side-question-B6DWAw1A.js → side-question-BGMJQf4U.js} +65 -170
- package/dist/source-CElXU0Oy.js +518 -0
- package/dist/thread-archive-guard-DfpBr-Sz.js +53 -0
- package/dist/thread-history-page-D0JIp48V.js +361 -0
- package/dist/{thread-lifecycle-DPK30jad.js → thread-lifecycle-DXl1-I6b.js} +187 -639
- package/dist/{thread-ownership-CbucAmmp.js → thread-ownership-BsDRTsyp.js} +13 -15
- package/dist/thread-policy-BuEQfE0g.js +54 -0
- package/dist/thread-resume-DsDOZpFg.js +52 -0
- package/dist/timeout-BVw8aaM1.js +33 -0
- package/dist/transcript-history-projection-v17wXgdK.js +238 -0
- package/dist/{transcript-mirror-_njEB84z.js → transcript-mirror-C5CKgNy6.js} +50 -260
- package/dist/{transcript-mirror-attestation-Byl34PC-.js → transcript-mirror-attestation-DibE0krH.js} +17 -4
- package/dist/{transport-stdio-CzTngV0j.js → transport-stdio-BGjHoYLr.js} +73 -43
- package/dist/{upstream-session-fork-1fwbNKzQ.js → upstream-session-fork-BZerMte_.js} +27 -19
- package/dist/{usage-i9fv5-4m.js → usage-tCk-mg_x.js} +4 -3
- package/dist/version-omm6IPI3.js +12 -0
- package/dist/{web-search-provider.runtime-DKQn67A7.js → web-search-provider.runtime-B7bGbGWq.js} +3 -3
- package/openclaw.plugin.json +23 -1
- package/package.json +4 -4
- package/dist/request-C8H22rPi.js +0 -475
- package/dist/session-catalog-B47uwekb.js +0 -3272
- package/dist/session-history.worker-zZVcx0ME.js +0 -267
- package/dist/shared-client-CKhX2kFt.js +0 -7505
- package/dist/thread-policy-CTalhWFy.js +0 -105
|
@@ -1,3272 +0,0 @@
|
|
|
1
|
-
import { l as sessionBindingIdentity } from "./session-binding-record-CyaXZvTe.js";
|
|
2
|
-
import { B as isJsonObject, I as CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES, L as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, M as resolveCodexAppServerHomeDir, N as resolveCodexAppServerLocalHomeDir, O as readCodexPluginConfig, P as resolveCodexAppServerUserHomeDir, i as resolveCodexAppServerRuntimeOptions, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-CyapYSji.js";
|
|
3
|
-
import { $ as assertCodexThreadForkParams, Ft as withTimeout, J as isCodexThreadReadMissingError, K as resolveCodexAppServerClientInstanceId, Kt as resolveCodexAppServerPreparedAuthHandoff, Ut as resolveCodexAppServerAuthProfileStore, Vt as resolveCodexAppServerAuthProfileId, Xt as readCodexSessionMeta, Yt as isOpenClawManagedCodexThread, c as getLeasedSharedCodexAppServerClient, h as releaseLeasedSharedCodexAppServerClient, y as retireSharedCodexAppServerClientIfCurrent } from "./shared-client-CKhX2kFt.js";
|
|
4
|
-
import { r as createCodexSessionGenerationSupersededError, s as reclaimCurrentCodexSessionGeneration } from "./session-binding-4Ki04sW-.js";
|
|
5
|
-
import { a as closeCodexStartupClientBestEffort } from "./attempt-client-cleanup-Bp1ZOnk2.js";
|
|
6
|
-
import { n as listCodexAppServerModels } from "./models-CcrgSMQR.js";
|
|
7
|
-
import { a as replaceCodexCatalogConnectionHomes, n as buildCodexAppServerConnectionFingerprint, t as resumeCodexAppServerThread } from "./thread-resume-BLgqkUc1.js";
|
|
8
|
-
import { d as codexCatalogHomeId, l as projectCodexUserItemText, r as importCodexThreadHistoryToTranscript, u as canonicalCodexCatalogHome } from "./transcript-mirror-_njEB84z.js";
|
|
9
|
-
import { i as requestCodexAppServerJson, o as withCodexAppServerJsonClient, r as requestCodexAppServerClientJson } from "./request-C8H22rPi.js";
|
|
10
|
-
import { n as describeControlFailure, t as CODEX_CONTROL_METHODS } from "./capabilities-B1mgvpP8.js";
|
|
11
|
-
import { a as formatCodexDisplayText } from "./command-formatters-DSbGMXA0.js";
|
|
12
|
-
import { n as createCodexCliNodeConversationBindingData } from "./conversation-binding-data-BiodpIzq.js";
|
|
13
|
-
import { n as prepareCodexAppServerAuthBinding } from "./auth-binding-Ds_QVm-X.js";
|
|
14
|
-
import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-DReTpGz6.js";
|
|
15
|
-
import { asFiniteNumber, isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
16
|
-
import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
17
|
-
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
18
|
-
import { z } from "zod";
|
|
19
|
-
import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
20
|
-
import { runCommandBuffered } from "openclaw/plugin-sdk/process-runtime";
|
|
21
|
-
import fs from "node:fs/promises";
|
|
22
|
-
import path from "node:path";
|
|
23
|
-
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
24
|
-
import { prepareAgentRuntimeAuth } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
25
|
-
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
26
|
-
import fsSync from "node:fs";
|
|
27
|
-
import os from "node:os";
|
|
28
|
-
import { listAgentIds as listAgentIds$1, resolveAgentDir as resolveAgentDir$2, resolveAgentWorkspaceDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId as resolveDefaultAgentId$1, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
|
|
29
|
-
import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
|
|
30
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
31
|
-
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
32
|
-
import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
|
|
33
|
-
import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
34
|
-
import process$1 from "node:process";
|
|
35
|
-
import { resolveSessionModelRef } from "openclaw/plugin-sdk/model-session-runtime";
|
|
36
|
-
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
|
|
37
|
-
import { createSessionCatalogAdoptionCoordinator, listSessionCatalogEntries, publishSessionCatalogHost, sessionCatalogAdoptedSessionKey, sessionCatalogAdoptedSourceKey, sessionCatalogPaging } from "openclaw/plugin-sdk/session-catalog";
|
|
38
|
-
import { decodeNodePtyResumeParams, decodeNodePtyStartParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
|
|
39
|
-
//#region extensions/codex/src/jsonl-lines.ts
|
|
40
|
-
const JSONL_STREAM_THRESHOLD_BYTES = 4194304;
|
|
41
|
-
const JSONL_READ_CHUNK_BYTES = 1048576;
|
|
42
|
-
async function visitJsonlLines(file, visitor, chunkBytes = JSONL_READ_CHUNK_BYTES) {
|
|
43
|
-
let size;
|
|
44
|
-
try {
|
|
45
|
-
size = (await fs.stat(file)).size;
|
|
46
|
-
} catch {
|
|
47
|
-
return {
|
|
48
|
-
ok: false,
|
|
49
|
-
lineCount: 0
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
if (size <= JSONL_STREAM_THRESHOLD_BYTES) {
|
|
53
|
-
let content;
|
|
54
|
-
try {
|
|
55
|
-
content = await fs.readFile(file, "utf8");
|
|
56
|
-
} catch {
|
|
57
|
-
return {
|
|
58
|
-
ok: false,
|
|
59
|
-
lineCount: 0
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
if (content.length === 0) return {
|
|
63
|
-
ok: true,
|
|
64
|
-
lineCount: 0
|
|
65
|
-
};
|
|
66
|
-
let lineCount = 0;
|
|
67
|
-
for (const line of content.split(/\r?\n/u)) {
|
|
68
|
-
lineCount += 1;
|
|
69
|
-
if (visitor(line) === false) break;
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
ok: true,
|
|
73
|
-
lineCount
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
let handle;
|
|
77
|
-
try {
|
|
78
|
-
handle = await fs.open(file, "r");
|
|
79
|
-
} catch {
|
|
80
|
-
return {
|
|
81
|
-
ok: false,
|
|
82
|
-
lineCount: 0
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
const buffer = Buffer.allocUnsafe(chunkBytes);
|
|
86
|
-
const decoder = new TextDecoder();
|
|
87
|
-
let pendingFragments = [];
|
|
88
|
-
let lineCount = 0;
|
|
89
|
-
try {
|
|
90
|
-
while (true) {
|
|
91
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
92
|
-
if (bytesRead === 0) break;
|
|
93
|
-
const content = decoder.decode(buffer.subarray(0, bytesRead), { stream: true });
|
|
94
|
-
let lineStart = 0;
|
|
95
|
-
while (true) {
|
|
96
|
-
const newline = content.indexOf("\n", lineStart);
|
|
97
|
-
if (newline === -1) break;
|
|
98
|
-
let rawLine = content.slice(lineStart, newline);
|
|
99
|
-
if (pendingFragments.length > 0) {
|
|
100
|
-
pendingFragments.push(rawLine);
|
|
101
|
-
rawLine = pendingFragments.join("");
|
|
102
|
-
pendingFragments = [];
|
|
103
|
-
}
|
|
104
|
-
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
105
|
-
lineCount += 1;
|
|
106
|
-
if (visitor(line) === false) return {
|
|
107
|
-
ok: true,
|
|
108
|
-
lineCount
|
|
109
|
-
};
|
|
110
|
-
lineStart = newline + 1;
|
|
111
|
-
}
|
|
112
|
-
if (lineStart < content.length) pendingFragments.push(content.slice(lineStart));
|
|
113
|
-
}
|
|
114
|
-
const decoderTail = decoder.decode();
|
|
115
|
-
if (decoderTail.length > 0) pendingFragments.push(decoderTail);
|
|
116
|
-
if (pendingFragments.length > 0) {
|
|
117
|
-
const rawLine = pendingFragments.join("");
|
|
118
|
-
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
119
|
-
lineCount += 1;
|
|
120
|
-
visitor(line);
|
|
121
|
-
}
|
|
122
|
-
return {
|
|
123
|
-
ok: true,
|
|
124
|
-
lineCount
|
|
125
|
-
};
|
|
126
|
-
} catch {
|
|
127
|
-
return {
|
|
128
|
-
ok: false,
|
|
129
|
-
lineCount: 0
|
|
130
|
-
};
|
|
131
|
-
} finally {
|
|
132
|
-
await handle.close().catch(() => void 0);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
//#endregion
|
|
136
|
-
//#region extensions/codex/src/node-cli-sessions.ts
|
|
137
|
-
const CODEX_CLI_SESSIONS_LIST_COMMAND = "codex.cli.sessions.list";
|
|
138
|
-
const CODEX_CLI_SESSION_RESUME_COMMAND = "codex.cli.session.resume";
|
|
139
|
-
const DEFAULT_SESSION_LIMIT = 10;
|
|
140
|
-
const MAX_SESSION_LIMIT = 50;
|
|
141
|
-
const DEFAULT_RESUME_TIMEOUT_MS = 12e5;
|
|
142
|
-
const SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
143
|
-
const activeResumeSessions = /* @__PURE__ */ new Set();
|
|
144
|
-
function createCodexCliSessionNodeHostCommands() {
|
|
145
|
-
return [{
|
|
146
|
-
command: CODEX_CLI_SESSIONS_LIST_COMMAND,
|
|
147
|
-
cap: "codex-cli-sessions",
|
|
148
|
-
handle: listLocalCodexCliSessions
|
|
149
|
-
}, {
|
|
150
|
-
command: CODEX_CLI_SESSION_RESUME_COMMAND,
|
|
151
|
-
cap: "codex-cli-sessions",
|
|
152
|
-
dangerous: true,
|
|
153
|
-
handle: resumeLocalCodexCliSession
|
|
154
|
-
}];
|
|
155
|
-
}
|
|
156
|
-
function createCodexCliSessionNodeInvokePolicies() {
|
|
157
|
-
return [{
|
|
158
|
-
commands: [CODEX_CLI_SESSIONS_LIST_COMMAND],
|
|
159
|
-
defaultPlatforms: [
|
|
160
|
-
"macos",
|
|
161
|
-
"linux",
|
|
162
|
-
"windows"
|
|
163
|
-
],
|
|
164
|
-
handle: (ctx) => ctx.invokeNode()
|
|
165
|
-
}, {
|
|
166
|
-
commands: [CODEX_CLI_SESSION_RESUME_COMMAND],
|
|
167
|
-
dangerous: true,
|
|
168
|
-
handle: (ctx) => ctx.invokeNode()
|
|
169
|
-
}];
|
|
170
|
-
}
|
|
171
|
-
async function listCodexCliSessionsOnNode(params) {
|
|
172
|
-
const node = await resolveCodexCliNode({
|
|
173
|
-
runtime: params.runtime,
|
|
174
|
-
requestedNode: params.requestedNode,
|
|
175
|
-
command: CODEX_CLI_SESSIONS_LIST_COMMAND
|
|
176
|
-
});
|
|
177
|
-
return {
|
|
178
|
-
node,
|
|
179
|
-
result: parseCodexCliSessionsListResult(await params.runtime.nodes.invoke({
|
|
180
|
-
nodeId: readNodeId(node),
|
|
181
|
-
command: CODEX_CLI_SESSIONS_LIST_COMMAND,
|
|
182
|
-
params: {
|
|
183
|
-
limit: params.limit,
|
|
184
|
-
filter: params.filter
|
|
185
|
-
},
|
|
186
|
-
timeoutMs: 15e3,
|
|
187
|
-
scopes: ["operator.write"]
|
|
188
|
-
}))
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
async function resolveCodexCliSessionForBindingOnNode(params) {
|
|
192
|
-
const listing = await listCodexCliSessionsOnNode({
|
|
193
|
-
runtime: params.runtime,
|
|
194
|
-
requestedNode: params.requestedNode,
|
|
195
|
-
filter: params.sessionId,
|
|
196
|
-
limit: MAX_SESSION_LIMIT
|
|
197
|
-
});
|
|
198
|
-
if (!listing.node.commands?.includes("codex.cli.session.resume")) throw new Error(`Node ${formatNodeLabel(listing.node)} does not expose ${CODEX_CLI_SESSION_RESUME_COMMAND}.`);
|
|
199
|
-
return {
|
|
200
|
-
node: listing.node,
|
|
201
|
-
session: listing.result.sessions.find((session) => session.sessionId === params.sessionId)
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
async function resumeCodexCliSessionOnNode(params) {
|
|
205
|
-
const payload = unwrapNodeInvokePayload$1(await params.runtime.nodes.invoke({
|
|
206
|
-
nodeId: params.nodeId,
|
|
207
|
-
command: CODEX_CLI_SESSION_RESUME_COMMAND,
|
|
208
|
-
params: {
|
|
209
|
-
sessionId: params.sessionId,
|
|
210
|
-
prompt: params.prompt,
|
|
211
|
-
cwd: params.cwd,
|
|
212
|
-
timeoutMs: params.timeoutMs
|
|
213
|
-
},
|
|
214
|
-
timeoutMs: (params.timeoutMs ?? DEFAULT_RESUME_TIMEOUT_MS) + 5e3,
|
|
215
|
-
scopes: ["operator.write"]
|
|
216
|
-
}));
|
|
217
|
-
if (!isRecord(payload) || payload.ok !== true || typeof payload.text !== "string") throw new Error("Codex CLI resume returned an invalid payload.");
|
|
218
|
-
return {
|
|
219
|
-
ok: true,
|
|
220
|
-
sessionId: typeof payload.sessionId === "string" ? payload.sessionId : params.sessionId,
|
|
221
|
-
text: payload.text
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
function formatCodexCliSessions(params) {
|
|
225
|
-
if (params.result.sessions.length === 0) return `No Codex CLI sessions returned from ${formatCodexDisplayText(formatNodeLabel(params.node))}.`;
|
|
226
|
-
return [`Codex CLI sessions on ${formatCodexDisplayText(formatNodeLabel(params.node))}:`, ...params.result.sessions.map((session) => {
|
|
227
|
-
const details = [session.cwd, session.updatedAt].filter((value) => Boolean(value));
|
|
228
|
-
return `- ${formatCodexDisplayText(session.sessionId)}${session.lastMessage ? ` - ${formatCodexDisplayText(session.lastMessage)}` : ""}${details.length > 0 ? ` (${details.map(formatCodexDisplayText).join(", ")})` : ""}\n Bind: /codex resume ${formatCodexDisplayText(session.sessionId)} --host ${formatCodexDisplayText(readNodeId(params.node))} --bind here`;
|
|
229
|
-
})].join("\n");
|
|
230
|
-
}
|
|
231
|
-
async function listLocalCodexCliSessions(paramsJSON) {
|
|
232
|
-
const params = readRecordParam(paramsJSON);
|
|
233
|
-
const limit = normalizeLimit$1(params.limit);
|
|
234
|
-
const filter = typeof params.filter === "string" ? params.filter.trim().toLowerCase() : "";
|
|
235
|
-
const codexHome = resolveCodexHome();
|
|
236
|
-
const summaries = await readHistorySessions(codexHome);
|
|
237
|
-
await hydrateSessionFiles(codexHome, summaries);
|
|
238
|
-
await hydrateSessionsFromSessionFiles(codexHome, summaries);
|
|
239
|
-
const sessions = [...summaries.values()].filter((session) => {
|
|
240
|
-
if (!filter) return true;
|
|
241
|
-
return [
|
|
242
|
-
session.sessionId,
|
|
243
|
-
session.cwd,
|
|
244
|
-
session.lastMessage
|
|
245
|
-
].some((value) => value?.toLowerCase().includes(filter));
|
|
246
|
-
}).toSorted((a, b) => compareOptionalStringsDesc(a.updatedAt, b.updatedAt)).slice(0, limit);
|
|
247
|
-
return JSON.stringify({
|
|
248
|
-
sessions,
|
|
249
|
-
codexHome
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
async function resumeLocalCodexCliSession(paramsJSON) {
|
|
253
|
-
const params = readRecordParam(paramsJSON);
|
|
254
|
-
const sessionId = typeof params.sessionId === "string" ? params.sessionId.trim() : "";
|
|
255
|
-
const prompt = typeof params.prompt === "string" ? params.prompt.trim() : "";
|
|
256
|
-
if (!sessionId || !SESSION_ID_PATTERN.test(sessionId)) throw new Error("Missing or invalid Codex CLI session id.");
|
|
257
|
-
if (!prompt) throw new Error("Missing Codex CLI prompt.");
|
|
258
|
-
if (activeResumeSessions.has(sessionId)) throw new Error(`Codex CLI session ${sessionId} already has an active resume turn.`);
|
|
259
|
-
activeResumeSessions.add(sessionId);
|
|
260
|
-
try {
|
|
261
|
-
const text = await runCodexExecResume({
|
|
262
|
-
sessionId,
|
|
263
|
-
prompt,
|
|
264
|
-
cwd: typeof params.cwd === "string" && params.cwd.trim() ? params.cwd.trim() : void 0,
|
|
265
|
-
timeoutMs: normalizeTimeoutMs(params.timeoutMs)
|
|
266
|
-
});
|
|
267
|
-
return JSON.stringify({
|
|
268
|
-
ok: true,
|
|
269
|
-
sessionId,
|
|
270
|
-
text: text.trim() || "Codex completed without a text reply."
|
|
271
|
-
});
|
|
272
|
-
} finally {
|
|
273
|
-
activeResumeSessions.delete(sessionId);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
async function runCodexExecResume(params) {
|
|
277
|
-
const outputPath = path.join(await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-codex-cli-")), "last-message.txt");
|
|
278
|
-
try {
|
|
279
|
-
const args = [
|
|
280
|
-
"exec",
|
|
281
|
-
"resume",
|
|
282
|
-
"--skip-git-repo-check",
|
|
283
|
-
"--output-last-message",
|
|
284
|
-
outputPath,
|
|
285
|
-
params.sessionId,
|
|
286
|
-
"-"
|
|
287
|
-
];
|
|
288
|
-
const invocation = materializeWindowsSpawnProgram(resolveWindowsSpawnProgram({
|
|
289
|
-
command: "codex",
|
|
290
|
-
platform: process$1.platform,
|
|
291
|
-
env: process$1.env,
|
|
292
|
-
execPath: process$1.execPath,
|
|
293
|
-
packageName: "@openai/codex"
|
|
294
|
-
}), args);
|
|
295
|
-
const result = await runCommandBuffered([invocation.command, ...invocation.argv], {
|
|
296
|
-
cwd: params.cwd || process$1.cwd(),
|
|
297
|
-
input: params.prompt,
|
|
298
|
-
env: process$1.env,
|
|
299
|
-
killGraceMs: 2e3,
|
|
300
|
-
killProcessTree: false,
|
|
301
|
-
terminateOnOutputError: true,
|
|
302
|
-
timeoutMs: params.timeoutMs
|
|
303
|
-
});
|
|
304
|
-
if (result.termination === "timeout") throw new Error(`codex exec resume timed out after ${String(params.timeoutMs)}ms`);
|
|
305
|
-
if (result.termination === "error" && result.error) throw result.error;
|
|
306
|
-
if (result.code !== 0) {
|
|
307
|
-
const message = result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim() || `codex exec resume exited with code ${String(result.code)}`;
|
|
308
|
-
throw new Error(message);
|
|
309
|
-
}
|
|
310
|
-
return await fs.readFile(outputPath, "utf8");
|
|
311
|
-
} finally {
|
|
312
|
-
await fs.rm(path.dirname(outputPath), {
|
|
313
|
-
recursive: true,
|
|
314
|
-
force: true
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
async function readHistorySessions(codexHome) {
|
|
319
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
320
|
-
if (!(await visitJsonlLines(path.join(codexHome, "history.jsonl"), (line) => {
|
|
321
|
-
const trimmed = line.trim();
|
|
322
|
-
if (!trimmed) return;
|
|
323
|
-
let parsed;
|
|
324
|
-
try {
|
|
325
|
-
parsed = JSON.parse(trimmed);
|
|
326
|
-
} catch {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
if (!isRecord(parsed) || typeof parsed.session_id !== "string") return;
|
|
330
|
-
const sessionId = parsed.session_id.trim();
|
|
331
|
-
if (!sessionId) return;
|
|
332
|
-
const entry = summaries.get(sessionId) ?? {
|
|
333
|
-
sessionId,
|
|
334
|
-
messageCount: 0
|
|
335
|
-
};
|
|
336
|
-
entry.messageCount += 1;
|
|
337
|
-
if (typeof parsed.text === "string" && parsed.text.trim()) entry.lastMessage = truncateText(parsed.text.trim(), 140);
|
|
338
|
-
if (typeof parsed.ts === "number") entry.updatedAt = timestampMsToIsoString(parsed.ts * 1e3) ?? entry.updatedAt;
|
|
339
|
-
summaries.set(sessionId, entry);
|
|
340
|
-
})).ok) return /* @__PURE__ */ new Map();
|
|
341
|
-
return summaries;
|
|
342
|
-
}
|
|
343
|
-
async function hydrateSessionFiles(codexHome, summaries) {
|
|
344
|
-
if (summaries.size === 0) return;
|
|
345
|
-
const files = await findSessionFiles(path.join(codexHome, "sessions"), 4);
|
|
346
|
-
const pending = new Set(summaries.keys());
|
|
347
|
-
for (const file of files) {
|
|
348
|
-
const basename = path.basename(file);
|
|
349
|
-
const sessionId = [...pending].find((id) => basename.includes(id));
|
|
350
|
-
if (!sessionId) continue;
|
|
351
|
-
const entry = summaries.get(sessionId);
|
|
352
|
-
if (!entry) continue;
|
|
353
|
-
entry.sessionFile = file;
|
|
354
|
-
const cwd = readSessionMetaCwd(await readFirstLine(file) ?? "");
|
|
355
|
-
if (cwd) entry.cwd = cwd;
|
|
356
|
-
pending.delete(sessionId);
|
|
357
|
-
if (pending.size === 0) return;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
async function hydrateSessionsFromSessionFiles(codexHome, summaries) {
|
|
361
|
-
const files = await findSessionFiles(path.join(codexHome, "sessions"), 4);
|
|
362
|
-
for (const file of files) {
|
|
363
|
-
const summary = await readSessionFileSummary(file);
|
|
364
|
-
if (!summary) continue;
|
|
365
|
-
const existing = summaries.get(summary.sessionId);
|
|
366
|
-
summaries.set(summary.sessionId, {
|
|
367
|
-
...summary,
|
|
368
|
-
...existing,
|
|
369
|
-
cwd: existing?.cwd ?? summary.cwd,
|
|
370
|
-
sessionFile: existing?.sessionFile ?? summary.sessionFile,
|
|
371
|
-
updatedAt: existing?.updatedAt ?? summary.updatedAt,
|
|
372
|
-
lastMessage: existing?.lastMessage ?? summary.lastMessage,
|
|
373
|
-
messageCount: existing?.messageCount ?? summary.messageCount
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
async function readSessionFileSummary(file) {
|
|
378
|
-
let sessionId = "";
|
|
379
|
-
let cwd;
|
|
380
|
-
let updatedAt;
|
|
381
|
-
let lastMessage;
|
|
382
|
-
let messageCount = 0;
|
|
383
|
-
const result = await visitJsonlLines(file, (line) => {
|
|
384
|
-
const trimmed = line.trim();
|
|
385
|
-
if (!trimmed) return;
|
|
386
|
-
let parsed;
|
|
387
|
-
try {
|
|
388
|
-
parsed = JSON.parse(trimmed);
|
|
389
|
-
} catch {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
if (!isRecord(parsed)) return;
|
|
393
|
-
if (typeof parsed.timestamp === "string" && parsed.timestamp.trim()) updatedAt = parsed.timestamp.trim();
|
|
394
|
-
if (parsed.type === "session_meta" && isRecord(parsed.payload)) {
|
|
395
|
-
if (typeof parsed.payload.id === "string" && parsed.payload.id.trim()) sessionId = parsed.payload.id.trim();
|
|
396
|
-
if (typeof parsed.payload.cwd === "string" && parsed.payload.cwd.trim()) cwd = parsed.payload.cwd.trim();
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
const messageText = readResponseItemMessageText(parsed);
|
|
400
|
-
if (messageText) {
|
|
401
|
-
messageCount += 1;
|
|
402
|
-
lastMessage = truncateText(messageText, 140);
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
if (!result.ok) return null;
|
|
406
|
-
if (result.lineCount === 0) return null;
|
|
407
|
-
if (!sessionId) sessionId = readSessionIdFromFilename(file) ?? "";
|
|
408
|
-
if (!sessionId) return null;
|
|
409
|
-
return {
|
|
410
|
-
sessionId,
|
|
411
|
-
updatedAt: updatedAt ?? await readFileMtimeIso(file),
|
|
412
|
-
lastMessage,
|
|
413
|
-
cwd,
|
|
414
|
-
sessionFile: file,
|
|
415
|
-
messageCount
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
async function findSessionFiles(dir, maxDepth) {
|
|
419
|
-
if (maxDepth < 0) return [];
|
|
420
|
-
let entries;
|
|
421
|
-
try {
|
|
422
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
423
|
-
} catch {
|
|
424
|
-
return [];
|
|
425
|
-
}
|
|
426
|
-
const files = [];
|
|
427
|
-
for (const entry of entries) {
|
|
428
|
-
const entryPath = path.join(dir, entry.name);
|
|
429
|
-
if (entry.isDirectory()) files.push(...await findSessionFiles(entryPath, maxDepth - 1));
|
|
430
|
-
else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(entryPath);
|
|
431
|
-
}
|
|
432
|
-
return files;
|
|
433
|
-
}
|
|
434
|
-
function readSessionMetaCwd(line) {
|
|
435
|
-
try {
|
|
436
|
-
const parsed = JSON.parse(line);
|
|
437
|
-
if (!isRecord(parsed) || parsed.type !== "session_meta" || !isRecord(parsed.payload)) return;
|
|
438
|
-
return typeof parsed.payload.cwd === "string" && parsed.payload.cwd.trim() ? parsed.payload.cwd.trim() : void 0;
|
|
439
|
-
} catch {
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function readResponseItemMessageText(parsed) {
|
|
444
|
-
if (parsed.type !== "response_item" || !isRecord(parsed.payload)) return;
|
|
445
|
-
if (parsed.payload.type !== "message") return;
|
|
446
|
-
if ((typeof parsed.payload.role === "string" ? parsed.payload.role : "") !== "user") return;
|
|
447
|
-
const parts = (Array.isArray(parsed.payload.content) ? parsed.payload.content : []).flatMap((entry) => {
|
|
448
|
-
if (!isRecord(entry)) return [];
|
|
449
|
-
const text = typeof entry.text === "string" ? entry.text : typeof entry.input_text === "string" ? entry.input_text : void 0;
|
|
450
|
-
return text?.trim() ? [text.trim()] : [];
|
|
451
|
-
});
|
|
452
|
-
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
453
|
-
}
|
|
454
|
-
function readSessionIdFromFilename(file) {
|
|
455
|
-
return path.basename(file).match(/[0-9a-f]{8}-[0-9a-f-]{27,}/iu)?.[0];
|
|
456
|
-
}
|
|
457
|
-
async function resolveCodexCliNode(params) {
|
|
458
|
-
const list = await params.runtime.nodes.list(params.requestedNode ? void 0 : { connected: true });
|
|
459
|
-
const requested = params.requestedNode?.trim();
|
|
460
|
-
const candidates = list.nodes.filter((node) => {
|
|
461
|
-
if (requested) return [
|
|
462
|
-
node.nodeId,
|
|
463
|
-
node.displayName,
|
|
464
|
-
node.remoteIp
|
|
465
|
-
].some((value) => value === requested);
|
|
466
|
-
return node.connected === true && node.commands?.includes(params.command);
|
|
467
|
-
});
|
|
468
|
-
if (candidates.length === 0) throw new Error(requested ? `Codex CLI node ${requested} was not found.` : "No connected node exposes Codex CLI session commands.");
|
|
469
|
-
const usable = candidates.filter((node) => node.commands?.includes(params.command));
|
|
470
|
-
if (usable.length === 0) throw new Error(`Node ${requested ?? "candidate"} does not expose ${params.command}.`);
|
|
471
|
-
if (usable.length > 1) throw new Error("Multiple Codex CLI-capable nodes connected. Pass --host <node-id>.");
|
|
472
|
-
return expectDefined(usable[0], "single usable Codex CLI node");
|
|
473
|
-
}
|
|
474
|
-
function parseCodexCliSessionsListResult(raw) {
|
|
475
|
-
const payload = unwrapNodeInvokePayload$1(raw);
|
|
476
|
-
if (!isRecord(payload) || !Array.isArray(payload.sessions)) throw new Error("Codex CLI session list returned an invalid payload.");
|
|
477
|
-
return {
|
|
478
|
-
codexHome: typeof payload.codexHome === "string" ? payload.codexHome : "",
|
|
479
|
-
sessions: payload.sessions.flatMap((entry) => {
|
|
480
|
-
if (!isRecord(entry) || typeof entry.sessionId !== "string") return [];
|
|
481
|
-
return [{
|
|
482
|
-
sessionId: entry.sessionId,
|
|
483
|
-
updatedAt: typeof entry.updatedAt === "string" ? entry.updatedAt : void 0,
|
|
484
|
-
lastMessage: typeof entry.lastMessage === "string" ? entry.lastMessage : void 0,
|
|
485
|
-
cwd: typeof entry.cwd === "string" ? entry.cwd : void 0,
|
|
486
|
-
sessionFile: typeof entry.sessionFile === "string" ? entry.sessionFile : void 0,
|
|
487
|
-
messageCount: typeof entry.messageCount === "number" && Number.isFinite(entry.messageCount) ? entry.messageCount : 0
|
|
488
|
-
}];
|
|
489
|
-
})
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
function unwrapNodeInvokePayload$1(raw) {
|
|
493
|
-
const record = isRecord(raw) ? raw : {};
|
|
494
|
-
if (typeof record.payloadJSON === "string" && record.payloadJSON.trim()) try {
|
|
495
|
-
return JSON.parse(record.payloadJSON);
|
|
496
|
-
} catch (error) {
|
|
497
|
-
throw new Error("Codex CLI node command returned malformed payloadJSON.", { cause: error });
|
|
498
|
-
}
|
|
499
|
-
if ("payload" in record) return record.payload;
|
|
500
|
-
return raw;
|
|
501
|
-
}
|
|
502
|
-
function readRecordParam(paramsJSON) {
|
|
503
|
-
if (!paramsJSON?.trim()) return {};
|
|
504
|
-
try {
|
|
505
|
-
const parsed = JSON.parse(paramsJSON);
|
|
506
|
-
return isRecord(parsed) ? parsed : {};
|
|
507
|
-
} catch {
|
|
508
|
-
return {};
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
function resolveCodexHome() {
|
|
512
|
-
return process$1.env.CODEX_HOME?.trim() || path.join(os.homedir(), ".codex");
|
|
513
|
-
}
|
|
514
|
-
async function readFirstLine(file) {
|
|
515
|
-
let firstLine;
|
|
516
|
-
return (await visitJsonlLines(file, (line) => {
|
|
517
|
-
firstLine = line;
|
|
518
|
-
return false;
|
|
519
|
-
}, 65536)).ok ? firstLine : void 0;
|
|
520
|
-
}
|
|
521
|
-
async function readFileMtimeIso(file) {
|
|
522
|
-
try {
|
|
523
|
-
return (await fs.stat(file)).mtime.toISOString();
|
|
524
|
-
} catch {
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
function normalizeLimit$1(value) {
|
|
529
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.min(MAX_SESSION_LIMIT, Math.max(1, Math.floor(value))) : DEFAULT_SESSION_LIMIT;
|
|
530
|
-
}
|
|
531
|
-
function normalizeTimeoutMs(value) {
|
|
532
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(36e5, Math.floor(value)) : DEFAULT_RESUME_TIMEOUT_MS;
|
|
533
|
-
}
|
|
534
|
-
function truncateText(value, max) {
|
|
535
|
-
if (value.length <= max) return value;
|
|
536
|
-
return `${truncateUtf16Safe(value, Math.max(0, max - 3))}...`;
|
|
537
|
-
}
|
|
538
|
-
function compareOptionalStringsDesc(a, b) {
|
|
539
|
-
return (b ?? "").localeCompare(a ?? "");
|
|
540
|
-
}
|
|
541
|
-
function readNodeId(node) {
|
|
542
|
-
if (!node.nodeId) throw new Error("Codex CLI node did not include a node id.");
|
|
543
|
-
return node.nodeId;
|
|
544
|
-
}
|
|
545
|
-
function formatNodeLabel(node) {
|
|
546
|
-
return [
|
|
547
|
-
node.displayName,
|
|
548
|
-
node.nodeId,
|
|
549
|
-
node.remoteIp
|
|
550
|
-
].filter(Boolean).join(" / ") || "node";
|
|
551
|
-
}
|
|
552
|
-
//#endregion
|
|
553
|
-
//#region extensions/codex/src/command-rpc.ts
|
|
554
|
-
async function prepareControlAuth(options, startOptions) {
|
|
555
|
-
if (!options.onResponse) return {
|
|
556
|
-
authProfileId: options.authProfileId ?? void 0,
|
|
557
|
-
clientOptions: { authProfileId: options.authProfileId }
|
|
558
|
-
};
|
|
559
|
-
if (!options.config || !options.sessionKey || !options.sessionId) throw new Error("Codex control subscription requires admitted session authority.");
|
|
560
|
-
const config = options.config;
|
|
561
|
-
const { sessionAgentId } = resolveSessionAgentIdsStrict({
|
|
562
|
-
config,
|
|
563
|
-
sessionKey: options.sessionKey,
|
|
564
|
-
agentId: options.agentId
|
|
565
|
-
});
|
|
566
|
-
const agentDir = options.agentDir ?? resolveAgentDir$2(config, sessionAgentId);
|
|
567
|
-
const workspaceDir = resolveAgentWorkspaceDir(config, sessionAgentId);
|
|
568
|
-
const entry = getSessionEntry({
|
|
569
|
-
agentId: sessionAgentId,
|
|
570
|
-
storePath: options.storePath?.trim() || resolveStorePath(config.session?.store, { agentId: sessionAgentId }),
|
|
571
|
-
sessionKey: options.sessionKey,
|
|
572
|
-
hydrateSkillPromptRefs: false,
|
|
573
|
-
readConsistency: "latest"
|
|
574
|
-
});
|
|
575
|
-
if (entry?.sessionId !== options.sessionId) throw createCodexSessionGenerationSupersededError(options.sessionId);
|
|
576
|
-
if (options.authProfileId === null || startOptions.homeScope === "user") return {
|
|
577
|
-
authProfileId: options.authProfileId ?? void 0,
|
|
578
|
-
clientOptions: { authProfileId: options.authProfileId }
|
|
579
|
-
};
|
|
580
|
-
const model = resolveSessionModelRef(config, entry, sessionAgentId);
|
|
581
|
-
const authProfileId = entry?.authProfileOverride ?? options.authProfileId;
|
|
582
|
-
const store = resolveCodexAppServerAuthProfileStore({
|
|
583
|
-
agentDir,
|
|
584
|
-
config,
|
|
585
|
-
authProfileId
|
|
586
|
-
});
|
|
587
|
-
const { plan, attempts } = prepareAgentRuntimeAuth({
|
|
588
|
-
provider: model.provider,
|
|
589
|
-
modelId: model.model,
|
|
590
|
-
config,
|
|
591
|
-
agentDir,
|
|
592
|
-
workspaceDir,
|
|
593
|
-
authProfileStore: store,
|
|
594
|
-
sessionAuthProfileId: authProfileId,
|
|
595
|
-
sessionAuthProfileSource: entry?.authProfileOverrideSource,
|
|
596
|
-
harnessId: "codex",
|
|
597
|
-
harnessAuthBootstrap: "harness"
|
|
598
|
-
});
|
|
599
|
-
const route = plan.modelRoute;
|
|
600
|
-
const resolvedAuth = route ? await resolveApiKeyForProvider({
|
|
601
|
-
provider: route.provider,
|
|
602
|
-
modelId: route.modelId,
|
|
603
|
-
modelApi: route.api,
|
|
604
|
-
cfg: config,
|
|
605
|
-
agentDir,
|
|
606
|
-
workspaceDir,
|
|
607
|
-
store,
|
|
608
|
-
profileId: attempts[0]?.profileId,
|
|
609
|
-
lockedProfile: plan.forwardedAuthProfileSource === "user",
|
|
610
|
-
allowAuthProfileFallback: attempts[0]?.allowAuthProfileFallback,
|
|
611
|
-
skipSetupProviderFallback: true
|
|
612
|
-
}) : void 0;
|
|
613
|
-
const handoff = await resolveCodexAppServerPreparedAuthHandoff({
|
|
614
|
-
authRequirement: route?.authRequirement,
|
|
615
|
-
resolvedApiKey: resolvedAuth?.apiKey,
|
|
616
|
-
authProfileId: route ? plan.forwardedAuthProfileId : resolveCodexAppServerAuthProfileId({
|
|
617
|
-
authProfileId: plan.forwardedAuthProfileId,
|
|
618
|
-
store,
|
|
619
|
-
config
|
|
620
|
-
}),
|
|
621
|
-
authProfileStore: store,
|
|
622
|
-
agentDir,
|
|
623
|
-
homeScope: startOptions.homeScope ?? "agent",
|
|
624
|
-
config,
|
|
625
|
-
subscriptionProfileRequiredError: "Prepared Codex subscription route requires a forwarded OpenAI OAuth or token profile.",
|
|
626
|
-
subscriptionProfileUnusableError: "Prepared Codex subscription auth profile is unusable."
|
|
627
|
-
});
|
|
628
|
-
const binding = handoff.authProfileId ? await prepareCodexAppServerAuthBinding({
|
|
629
|
-
authProfileId: handoff.authProfileId,
|
|
630
|
-
authProfileStore: store,
|
|
631
|
-
agentDir,
|
|
632
|
-
config
|
|
633
|
-
}) : void 0;
|
|
634
|
-
return {
|
|
635
|
-
authProfileId: handoff.authProfileId,
|
|
636
|
-
clientOptions: {
|
|
637
|
-
...handoff.preparedAuth ? { preparedAuth: handoff.preparedAuth } : { authProfileId: handoff.authProfileId },
|
|
638
|
-
authRequirement: route?.authRequirement,
|
|
639
|
-
authProfileStore: binding?.authProfileStore ?? store,
|
|
640
|
-
authBindingFingerprint: binding?.fingerprint,
|
|
641
|
-
agentDir
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
function requestOptions(pluginConfig, limit, config, agentDir) {
|
|
646
|
-
const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
647
|
-
return {
|
|
648
|
-
limit,
|
|
649
|
-
timeoutMs: runtime.requestTimeoutMs,
|
|
650
|
-
startOptions: runtime.start,
|
|
651
|
-
config,
|
|
652
|
-
agentDir
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
async function codexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
656
|
-
const runtime = options.startOptions ? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig }) : resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
|
657
|
-
const startOptions = options.startOptions ?? runtime.start;
|
|
658
|
-
const auth = await prepareControlAuth(options, startOptions);
|
|
659
|
-
const controlRequestOptions = {
|
|
660
|
-
timeoutMs: options.timeoutMs ?? runtime.requestTimeoutMs,
|
|
661
|
-
assertCurrent: options.assertCurrent,
|
|
662
|
-
startOptions,
|
|
663
|
-
config: options.config,
|
|
664
|
-
sessionKey: options.sessionKey,
|
|
665
|
-
sessionId: options.sessionId,
|
|
666
|
-
agentDir: options.agentDir,
|
|
667
|
-
isolated: options.isolated,
|
|
668
|
-
...auth.clientOptions
|
|
669
|
-
};
|
|
670
|
-
if (options.onResponse || options.beforeRequest) return await withCodexAppServerJsonClient(controlRequestOptions, async (request, client, scope) => {
|
|
671
|
-
await options.beforeRequest?.(request, client, scope);
|
|
672
|
-
scope.assertCurrent();
|
|
673
|
-
let response;
|
|
674
|
-
if (method === "thread/resume") {
|
|
675
|
-
if (!isJsonObject(requestParams) || typeof requestParams.threadId !== "string") throw new Error("Codex thread/resume requires a thread id.");
|
|
676
|
-
response = await resumeCodexAppServerThread({
|
|
677
|
-
client,
|
|
678
|
-
request: {
|
|
679
|
-
...requestParams,
|
|
680
|
-
threadId: requestParams.threadId
|
|
681
|
-
},
|
|
682
|
-
requestResume: () => request({
|
|
683
|
-
method,
|
|
684
|
-
requestParams
|
|
685
|
-
}),
|
|
686
|
-
abandonClient: () => closeCodexStartupClientBestEffort(client)
|
|
687
|
-
});
|
|
688
|
-
} else response = await request({
|
|
689
|
-
method,
|
|
690
|
-
requestParams
|
|
691
|
-
});
|
|
692
|
-
await options.onResponse?.(response, client, {
|
|
693
|
-
authProfileId: auth.authProfileId,
|
|
694
|
-
assertCurrent: scope.assertCurrent
|
|
695
|
-
});
|
|
696
|
-
scope.assertCurrent();
|
|
697
|
-
return response;
|
|
698
|
-
});
|
|
699
|
-
return await requestCodexAppServerJson({
|
|
700
|
-
method,
|
|
701
|
-
requestParams,
|
|
702
|
-
...controlRequestOptions
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
async function safeCodexControlRequest(pluginConfig, method, requestParams, options = {}) {
|
|
706
|
-
return await safeValue(async () => await codexControlRequest(pluginConfig, method, requestParams, options));
|
|
707
|
-
}
|
|
708
|
-
async function safeCodexModelList(pluginConfig, limit, config, agentDir) {
|
|
709
|
-
return await safeValue(async () => await listCodexAppServerModels(requestOptions(pluginConfig, limit, config, agentDir)));
|
|
710
|
-
}
|
|
711
|
-
async function readCodexStatusProbes(pluginConfig, config, agentDir) {
|
|
712
|
-
const [models, account, limits, mcps, skills] = await Promise.all([
|
|
713
|
-
safeCodexModelList(pluginConfig, 20, config, agentDir),
|
|
714
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, {
|
|
715
|
-
config,
|
|
716
|
-
agentDir
|
|
717
|
-
}),
|
|
718
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, {
|
|
719
|
-
config,
|
|
720
|
-
agentDir
|
|
721
|
-
}),
|
|
722
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, {
|
|
723
|
-
config,
|
|
724
|
-
agentDir
|
|
725
|
-
}),
|
|
726
|
-
safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, {
|
|
727
|
-
config,
|
|
728
|
-
agentDir
|
|
729
|
-
})
|
|
730
|
-
]);
|
|
731
|
-
return {
|
|
732
|
-
models,
|
|
733
|
-
account,
|
|
734
|
-
limits,
|
|
735
|
-
mcps,
|
|
736
|
-
skills
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
async function safeValue(read) {
|
|
740
|
-
try {
|
|
741
|
-
return {
|
|
742
|
-
ok: true,
|
|
743
|
-
value: await read()
|
|
744
|
-
};
|
|
745
|
-
} catch (error) {
|
|
746
|
-
return {
|
|
747
|
-
ok: false,
|
|
748
|
-
error: describeControlFailure(error)
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
//#endregion
|
|
753
|
-
//#region extensions/codex/src/app-server/thread-archive-guard.ts
|
|
754
|
-
const DESCENDANT_PAGE_LIMIT = 100;
|
|
755
|
-
const MAX_DESCENDANT_PAGES = 100;
|
|
756
|
-
const MAX_THREAD_ID_LENGTH = 256;
|
|
757
|
-
const MAX_CURSOR_LENGTH$1 = 4096;
|
|
758
|
-
function readNextCursor(value) {
|
|
759
|
-
if (value === void 0 || value === null) return;
|
|
760
|
-
if (typeof value !== "string" || !value.trim() || value.length > MAX_CURSOR_LENGTH$1) throw new Error("Codex app-server returned an invalid descendant-list cursor");
|
|
761
|
-
return value;
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Native archive includes the spawned subtree. Enumerate that same subtree first so an
|
|
765
|
-
* OpenClaw-owned descendant cannot be stopped as an undocumented side effect.
|
|
766
|
-
*/
|
|
767
|
-
async function assertCodexArchiveDescendantsUnowned(params) {
|
|
768
|
-
const ancestorThreadId = normalizeBoundedOptionalString(params.threadId, MAX_THREAD_ID_LENGTH);
|
|
769
|
-
if (!ancestorThreadId) throw new Error("cannot verify Codex archive descendants for an invalid thread id");
|
|
770
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
771
|
-
const seenThreadIds = /* @__PURE__ */ new Set([ancestorThreadId]);
|
|
772
|
-
let cursor;
|
|
773
|
-
for (let pageIndex = 0; pageIndex < MAX_DESCENDANT_PAGES; pageIndex += 1) {
|
|
774
|
-
const response = await params.listPage({
|
|
775
|
-
ancestorThreadId,
|
|
776
|
-
archived: false,
|
|
777
|
-
limit: DESCENDANT_PAGE_LIMIT,
|
|
778
|
-
sortKey: "created_at",
|
|
779
|
-
sortDirection: "desc",
|
|
780
|
-
useStateDbOnly: true,
|
|
781
|
-
...cursor ? { cursor } : {}
|
|
782
|
-
});
|
|
783
|
-
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex app-server returned an invalid descendant-list response");
|
|
784
|
-
if (response.data.length > DESCENDANT_PAGE_LIMIT) throw new Error("Codex app-server exceeded the descendant-list page limit");
|
|
785
|
-
for (const value of response.data) {
|
|
786
|
-
if (!isJsonObject(value)) throw new Error("Codex app-server returned an invalid descendant thread");
|
|
787
|
-
const descendantThreadId = normalizeBoundedOptionalString(value.id, MAX_THREAD_ID_LENGTH);
|
|
788
|
-
if (!descendantThreadId) throw new Error("Codex app-server returned a descendant without a valid thread id");
|
|
789
|
-
if (seenThreadIds.has(descendantThreadId)) throw new Error("Codex app-server returned a cyclic descendant thread list");
|
|
790
|
-
seenThreadIds.add(descendantThreadId);
|
|
791
|
-
await params.assertDescendantIdle(descendantThreadId);
|
|
792
|
-
if (await params.bindingStore.hasOtherThreadOwner(descendantThreadId)) throw new Error("cannot archive a Codex thread while a spawned descendant is owned by an OpenClaw session");
|
|
793
|
-
}
|
|
794
|
-
const nextCursor = readNextCursor(response.nextCursor);
|
|
795
|
-
if (!nextCursor) return;
|
|
796
|
-
if (seenCursors.has(nextCursor)) throw new Error("Codex app-server returned a repeated descendant-list cursor");
|
|
797
|
-
seenCursors.add(nextCursor);
|
|
798
|
-
cursor = nextCursor;
|
|
799
|
-
}
|
|
800
|
-
throw new Error("Codex descendant enumeration exceeded its safety limit");
|
|
801
|
-
}
|
|
802
|
-
//#endregion
|
|
803
|
-
//#region extensions/codex/src/session-catalog-parsing.ts
|
|
804
|
-
const DEFAULT_PAGE_LIMIT = 50;
|
|
805
|
-
const CODEX_APP_SERVER_THREADS_CAPABILITY = "codex-app-server-threads";
|
|
806
|
-
const CODEX_APP_SERVER_THREADS_LIST_COMMAND = "codex.appServer.threads.list.v1";
|
|
807
|
-
const CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND = "codex.appServer.thread.turns.list.v1";
|
|
808
|
-
const CODEX_CATALOG_TRANSCRIPT_READ_COMMAND = "codex.sessionCatalog.transcript.read.v1";
|
|
809
|
-
const CODEX_LOCAL_SESSION_HOST_ID = "gateway:local";
|
|
810
|
-
const NODE_INVOKE_TIMEOUT_MS = 65e3;
|
|
811
|
-
const MAX_SEARCH_LENGTH = 500;
|
|
812
|
-
const MAX_CURSOR_LENGTH = 4096;
|
|
813
|
-
const MAX_CURSOR_COUNT = 100;
|
|
814
|
-
const MAX_HOST_ID_LENGTH = 256;
|
|
815
|
-
const MAX_CWD_LENGTH = 4096;
|
|
816
|
-
const MAX_SESSION_NAME_LENGTH = 500;
|
|
817
|
-
const MAX_SESSION_PREVIEW_LENGTH = 500;
|
|
818
|
-
const MAX_SESSION_KEY_LENGTH = 1024;
|
|
819
|
-
const MAX_METADATA_LENGTH = 500;
|
|
820
|
-
const MAX_ACTIVE_FLAGS = 16;
|
|
821
|
-
const MAX_TRANSCRIPT_PAGE_BYTES = 20971520;
|
|
822
|
-
var CatalogParamsError = class extends Error {};
|
|
823
|
-
function readControlCursor(value, label) {
|
|
824
|
-
if (value === void 0 || value === null) return;
|
|
825
|
-
if (typeof value !== "string" || !value.trim() || value.length > 4096) throw new CatalogParamsError(`invalid Codex session catalog ${label} cursor`);
|
|
826
|
-
return value;
|
|
827
|
-
}
|
|
828
|
-
function boundedCatalogString(value, maxLength, overflow = "omit") {
|
|
829
|
-
if (typeof value !== "string") return;
|
|
830
|
-
const normalized = value.trim();
|
|
831
|
-
if (!normalized) return;
|
|
832
|
-
if (normalized.length <= maxLength) return normalized;
|
|
833
|
-
return overflow === "truncate" ? truncateUtf16Safe(normalized, maxLength) : void 0;
|
|
834
|
-
}
|
|
835
|
-
function catalogPreview(value) {
|
|
836
|
-
if (typeof value !== "string") return;
|
|
837
|
-
return boundedCatalogString(sanitizeTerminalText(value.replace(/\s+/g, " ")), MAX_SESSION_PREVIEW_LENGTH, "truncate");
|
|
838
|
-
}
|
|
839
|
-
function normalizeInteractiveThreadSource(source) {
|
|
840
|
-
if (CODEX_INTERACTIVE_THREAD_SOURCE_KINDS.some((kind) => kind === source) || CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES.some((kind) => kind === source)) return source;
|
|
841
|
-
if (isRecord(source) && CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES.some((kind) => kind === source.custom)) return source.custom;
|
|
842
|
-
}
|
|
843
|
-
function isInteractiveThreadSource(source) {
|
|
844
|
-
return normalizeInteractiveThreadSource(source) !== void 0;
|
|
845
|
-
}
|
|
846
|
-
function toCatalogSession(thread, archived) {
|
|
847
|
-
const source = normalizeInteractiveThreadSource(thread.source);
|
|
848
|
-
if (!source) return;
|
|
849
|
-
const record = thread;
|
|
850
|
-
const threadId = boundedCatalogString(thread.id, 256);
|
|
851
|
-
if (!threadId) return;
|
|
852
|
-
const activeFlags = thread.status?.type === "active" ? thread.status.activeFlags?.flatMap((flag) => {
|
|
853
|
-
const normalized = boundedCatalogString(flag, 128);
|
|
854
|
-
return normalized ? [normalized] : [];
|
|
855
|
-
}).slice(0, MAX_ACTIVE_FLAGS) : void 0;
|
|
856
|
-
const gitInfo = isRecord(record.gitInfo) ? record.gitInfo : void 0;
|
|
857
|
-
const sessionId = boundedCatalogString(thread.sessionId, 256);
|
|
858
|
-
const name = boundedCatalogString(thread.name, MAX_SESSION_NAME_LENGTH, "truncate");
|
|
859
|
-
const fallbackName = name ? void 0 : catalogPreview(thread.preview);
|
|
860
|
-
const cwd = boundedCatalogString(thread.cwd, MAX_CWD_LENGTH);
|
|
861
|
-
const modelProvider = boundedCatalogString(record.modelProvider, MAX_METADATA_LENGTH, "truncate");
|
|
862
|
-
const cliVersion = boundedCatalogString(record.cliVersion, MAX_METADATA_LENGTH, "truncate");
|
|
863
|
-
const gitBranch = boundedCatalogString(gitInfo?.branch, MAX_METADATA_LENGTH, "truncate");
|
|
864
|
-
return {
|
|
865
|
-
threadId,
|
|
866
|
-
status: thread.status?.type ?? "notLoaded",
|
|
867
|
-
archived,
|
|
868
|
-
...sessionId ? { sessionId } : {},
|
|
869
|
-
...thread.name === null ? { name: null } : name ? { name } : {},
|
|
870
|
-
...fallbackName ? { fallbackName } : {},
|
|
871
|
-
...cwd ? { cwd } : {},
|
|
872
|
-
...activeFlags?.length ? { activeFlags } : {},
|
|
873
|
-
...typeof thread.createdAt === "number" && Number.isFinite(thread.createdAt) ? { createdAt: thread.createdAt } : {},
|
|
874
|
-
...typeof thread.updatedAt === "number" && Number.isFinite(thread.updatedAt) ? { updatedAt: thread.updatedAt } : {},
|
|
875
|
-
...typeof record.recencyAt === "number" && Number.isFinite(record.recencyAt) ? { recencyAt: record.recencyAt } : record.recencyAt === null ? { recencyAt: null } : {},
|
|
876
|
-
source,
|
|
877
|
-
...modelProvider ? { modelProvider } : {},
|
|
878
|
-
...cliVersion ? { cliVersion } : {},
|
|
879
|
-
...gitBranch ? { gitBranch } : {}
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
function normalizeLimit(value, key) {
|
|
883
|
-
if (value === void 0) return DEFAULT_PAGE_LIMIT;
|
|
884
|
-
if (!Number.isInteger(value) || value < 1 || value > 100) throw new CatalogParamsError(`${key} must be an integer from 1 to 100`);
|
|
885
|
-
return value;
|
|
886
|
-
}
|
|
887
|
-
function readBoundedOptionalString(params, key, maxLength) {
|
|
888
|
-
const value = params[key];
|
|
889
|
-
if (value === void 0) return;
|
|
890
|
-
if (typeof value !== "string") throw new CatalogParamsError(`${key} must be a string`);
|
|
891
|
-
const trimmed = value.trim();
|
|
892
|
-
if (!trimmed) return;
|
|
893
|
-
if (trimmed.length > maxLength) throw new CatalogParamsError(`${key} must be at most ${maxLength} characters`);
|
|
894
|
-
return trimmed;
|
|
895
|
-
}
|
|
896
|
-
function requireOnlyKeys(params, allowed) {
|
|
897
|
-
const unknown = Object.keys(params).find((key) => !allowed.has(key));
|
|
898
|
-
if (unknown) throw new CatalogParamsError(`unknown Codex session catalog parameter: ${unknown}`);
|
|
899
|
-
}
|
|
900
|
-
function readPageParams(value) {
|
|
901
|
-
if (!isRecord(value)) throw new CatalogParamsError("Codex session catalog parameters must be an object");
|
|
902
|
-
const params = value;
|
|
903
|
-
requireOnlyKeys(params, /* @__PURE__ */ new Set([
|
|
904
|
-
"cursor",
|
|
905
|
-
"limit",
|
|
906
|
-
"searchTerm",
|
|
907
|
-
"cwd"
|
|
908
|
-
]));
|
|
909
|
-
const cursor = readBoundedOptionalString(params, "cursor", MAX_CURSOR_LENGTH);
|
|
910
|
-
const searchTerm = readBoundedOptionalString(params, "searchTerm", MAX_SEARCH_LENGTH);
|
|
911
|
-
const cwd = readBoundedOptionalString(params, "cwd", MAX_CWD_LENGTH);
|
|
912
|
-
return {
|
|
913
|
-
limit: normalizeLimit(params.limit, "limit"),
|
|
914
|
-
...cursor ? { cursor } : {},
|
|
915
|
-
...searchTerm ? { searchTerm } : {},
|
|
916
|
-
...cwd ? { cwd } : {}
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
function readGatewayParams(value) {
|
|
920
|
-
if (value !== void 0 && !isRecord(value)) throw new CatalogParamsError("Codex session catalog parameters must be an object");
|
|
921
|
-
const params = isRecord(value) ? value : {};
|
|
922
|
-
requireOnlyKeys(params, /* @__PURE__ */ new Set([
|
|
923
|
-
"search",
|
|
924
|
-
"limitPerHost",
|
|
925
|
-
"hostIds",
|
|
926
|
-
"cursors"
|
|
927
|
-
]));
|
|
928
|
-
const search = readBoundedOptionalString(params, "search", MAX_SEARCH_LENGTH);
|
|
929
|
-
let hostIds;
|
|
930
|
-
if (params.hostIds !== void 0) {
|
|
931
|
-
if (!Array.isArray(params.hostIds) || params.hostIds.length > 100) throw new CatalogParamsError(`hostIds must contain at most 100 host ids`);
|
|
932
|
-
hostIds = [...new Set(params.hostIds.map((hostId) => readHostId(hostId)))];
|
|
933
|
-
}
|
|
934
|
-
let cursors;
|
|
935
|
-
if (params.cursors !== void 0) {
|
|
936
|
-
if (!isRecord(params.cursors)) throw new CatalogParamsError("cursors must be an object");
|
|
937
|
-
const entries = Object.entries(params.cursors);
|
|
938
|
-
if (entries.length > MAX_CURSOR_COUNT) throw new CatalogParamsError(`cursors may contain at most ${MAX_CURSOR_COUNT} hosts`);
|
|
939
|
-
cursors = {};
|
|
940
|
-
for (const [hostId, cursor] of entries) {
|
|
941
|
-
const normalizedHostId = hostId.trim();
|
|
942
|
-
if (normalizedHostId.length === 0 || normalizedHostId.length > MAX_HOST_ID_LENGTH || !normalizedHostId.startsWith("gateway:") && !normalizedHostId.startsWith("node:")) throw new CatalogParamsError(`invalid Codex session catalog host id: ${hostId}`);
|
|
943
|
-
if (typeof cursor !== "string" || !cursor.trim() || cursor.trim().length > 4096) throw new CatalogParamsError(`invalid cursor for Codex session catalog host: ${hostId}`);
|
|
944
|
-
cursors[normalizedHostId] = cursor.trim();
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
return {
|
|
948
|
-
limitPerHost: normalizeLimit(params.limitPerHost, "limitPerHost"),
|
|
949
|
-
...search ? { search } : {},
|
|
950
|
-
...hostIds && hostIds.length > 0 ? { hostIds } : {},
|
|
951
|
-
...cursors && Object.keys(cursors).length > 0 ? { cursors } : {}
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
function readHostId(value) {
|
|
955
|
-
if (typeof value !== "string") throw new CatalogParamsError("Codex session catalog host ids must be strings");
|
|
956
|
-
const hostId = value.trim();
|
|
957
|
-
if (hostId.length === 0 || hostId.length > MAX_HOST_ID_LENGTH || !hostId.startsWith("gateway:") && !hostId.startsWith("node:")) throw new CatalogParamsError(`invalid Codex session catalog host id: ${value}`);
|
|
958
|
-
return hostId;
|
|
959
|
-
}
|
|
960
|
-
function parseJsonParams(paramsJSON) {
|
|
961
|
-
if (!paramsJSON?.trim()) return {};
|
|
962
|
-
try {
|
|
963
|
-
return JSON.parse(paramsJSON);
|
|
964
|
-
} catch (error) {
|
|
965
|
-
throw new Error("Codex session catalog parameters must be valid JSON", { cause: error });
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
function parseOptionalCatalogString(value, field, maxLength) {
|
|
969
|
-
if (value === void 0) return;
|
|
970
|
-
if (typeof value !== "string" || value.length > maxLength) throw new Error(`Codex session catalog returned an invalid ${field}`);
|
|
971
|
-
return value;
|
|
972
|
-
}
|
|
973
|
-
function parseCatalogSession(value, options = {}) {
|
|
974
|
-
if (!isRecord(value) || typeof value.threadId !== "string" || !value.threadId.trim() || value.threadId.length > 256 || value.archived !== false) throw new Error("Codex session catalog returned an invalid session");
|
|
975
|
-
const status = parseOptionalCatalogString(value.status, "status", 64);
|
|
976
|
-
if (!status?.trim()) throw new Error("Codex session catalog returned an invalid status");
|
|
977
|
-
if (value.activeFlags !== void 0 && !Array.isArray(value.activeFlags)) throw new Error("Codex session catalog returned invalid active flags");
|
|
978
|
-
if (Array.isArray(value.activeFlags) && value.activeFlags.length > MAX_ACTIVE_FLAGS) throw new Error("Codex session catalog returned too many active flags");
|
|
979
|
-
const activeFlags = Array.isArray(value.activeFlags) ? value.activeFlags.map((entry) => {
|
|
980
|
-
const flag = parseOptionalCatalogString(entry, "active flag", 128);
|
|
981
|
-
if (flag === void 0) throw new Error("Codex session catalog returned an invalid active flag");
|
|
982
|
-
return flag;
|
|
983
|
-
}) : void 0;
|
|
984
|
-
const sessionId = parseOptionalCatalogString(value.sessionId, "session id", 256);
|
|
985
|
-
const name = value.name === null ? null : parseOptionalCatalogString(value.name, "session name", MAX_SESSION_NAME_LENGTH);
|
|
986
|
-
const fallbackName = parseOptionalCatalogString(value.fallbackName, "session fallback name", MAX_SESSION_PREVIEW_LENGTH);
|
|
987
|
-
const cwd = parseOptionalCatalogString(value.cwd, "cwd", MAX_CWD_LENGTH);
|
|
988
|
-
const source = parseOptionalCatalogString(value.source, "source", MAX_METADATA_LENGTH);
|
|
989
|
-
const modelProvider = parseOptionalCatalogString(value.modelProvider, "model provider", MAX_METADATA_LENGTH);
|
|
990
|
-
const cliVersion = parseOptionalCatalogString(value.cliVersion, "CLI version", MAX_METADATA_LENGTH);
|
|
991
|
-
const gitBranch = parseOptionalCatalogString(value.gitBranch, "Git branch", MAX_METADATA_LENGTH);
|
|
992
|
-
const sessionKey = options.allowSessionKey ? parseOptionalCatalogString(value.sessionKey, "OpenClaw session key", MAX_SESSION_KEY_LENGTH) : void 0;
|
|
993
|
-
const createdAt = asFiniteNumber(value.createdAt);
|
|
994
|
-
const updatedAt = asFiniteNumber(value.updatedAt);
|
|
995
|
-
const recencyAt = value.recencyAt === null ? null : asFiniteNumber(value.recencyAt);
|
|
996
|
-
return {
|
|
997
|
-
threadId: value.threadId,
|
|
998
|
-
status,
|
|
999
|
-
archived: value.archived,
|
|
1000
|
-
...sessionId !== void 0 ? { sessionId } : {},
|
|
1001
|
-
...name !== void 0 ? { name } : {},
|
|
1002
|
-
...fallbackName !== void 0 ? { fallbackName } : {},
|
|
1003
|
-
...cwd !== void 0 ? { cwd } : {},
|
|
1004
|
-
...activeFlags && activeFlags.length > 0 ? { activeFlags } : {},
|
|
1005
|
-
...createdAt !== void 0 ? { createdAt } : {},
|
|
1006
|
-
...updatedAt !== void 0 ? { updatedAt } : {},
|
|
1007
|
-
...recencyAt !== void 0 ? { recencyAt } : {},
|
|
1008
|
-
...source !== void 0 ? { source } : {},
|
|
1009
|
-
...modelProvider !== void 0 ? { modelProvider } : {},
|
|
1010
|
-
...cliVersion !== void 0 ? { cliVersion } : {},
|
|
1011
|
-
...gitBranch !== void 0 ? { gitBranch } : {},
|
|
1012
|
-
...sessionKey !== void 0 ? { sessionKey } : {}
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
function parseCatalogPage(value, options = {}) {
|
|
1016
|
-
if (!isRecord(value) || !Array.isArray(value.sessions) || value.sessions.length > 100) throw new Error("Codex session catalog returned an invalid page");
|
|
1017
|
-
const nextCursor = parseOptionalCatalogString(value.nextCursor, "next cursor", MAX_CURSOR_LENGTH);
|
|
1018
|
-
const backwardsCursor = parseOptionalCatalogString(value.backwardsCursor, "backwards cursor", MAX_CURSOR_LENGTH);
|
|
1019
|
-
return {
|
|
1020
|
-
sessions: value.sessions.map((session) => parseCatalogSession(session, options)),
|
|
1021
|
-
...nextCursor ? { nextCursor } : {},
|
|
1022
|
-
...backwardsCursor ? { backwardsCursor } : {}
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
function filterCatalogPageByTitle(page, searchTerm) {
|
|
1026
|
-
if (!searchTerm) return page;
|
|
1027
|
-
return {
|
|
1028
|
-
...page,
|
|
1029
|
-
sessions: page.sessions.filter((session) => (session.name ?? session.fallbackName)?.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()))
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
function unwrapNodeInvokePayload(value) {
|
|
1033
|
-
if (!isRecord(value)) return value;
|
|
1034
|
-
if (typeof value.payloadJSON === "string" && value.payloadJSON.trim()) try {
|
|
1035
|
-
return JSON.parse(value.payloadJSON);
|
|
1036
|
-
} catch (error) {
|
|
1037
|
-
throw new Error("Codex node returned malformed session catalog JSON", { cause: error });
|
|
1038
|
-
}
|
|
1039
|
-
return "payload" in value ? value.payload : value;
|
|
1040
|
-
}
|
|
1041
|
-
function catalogErrorDetail(error) {
|
|
1042
|
-
if (error instanceof Error) return error.message.trim();
|
|
1043
|
-
if (typeof error === "string") return error.trim();
|
|
1044
|
-
if (error && typeof error === "object" && "message" in error) {
|
|
1045
|
-
const message = error.message;
|
|
1046
|
-
return typeof message === "string" ? message.trim() : "";
|
|
1047
|
-
}
|
|
1048
|
-
return "";
|
|
1049
|
-
}
|
|
1050
|
-
function catalogError(code, error) {
|
|
1051
|
-
const summary = {
|
|
1052
|
-
APP_SERVER_UNAVAILABLE: "Codex app-server is unavailable on this host",
|
|
1053
|
-
NODE_INVOKE_FAILED: "The paired node could not return its Codex session catalog",
|
|
1054
|
-
NODE_LIST_FAILED: "Paired nodes could not be listed"
|
|
1055
|
-
}[code] ?? "Codex session catalog request failed";
|
|
1056
|
-
const detail = code === "NODE_LIST_FAILED" ? catalogErrorDetail(error) : "";
|
|
1057
|
-
return {
|
|
1058
|
-
code,
|
|
1059
|
-
message: detail && detail !== summary ? `${summary}: ${detail}` : summary
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
function parseTranscriptPage(value) {
|
|
1063
|
-
if (!isRecord(value) || !Array.isArray(value.data) || value.data.length > 50 || value.data.some((turn) => !isRecord(turn) || !Array.isArray(turn.items) || turn.items.some((item) => !isRecord(item)))) throw new Error("Codex app-server returned an invalid transcript page");
|
|
1064
|
-
const nextCursor = readControlCursor(value.nextCursor, "transcript next response");
|
|
1065
|
-
const backwardsCursor = readControlCursor(value.backwardsCursor, "transcript backwards response");
|
|
1066
|
-
const page = {
|
|
1067
|
-
data: value.data,
|
|
1068
|
-
...nextCursor ? { nextCursor } : {},
|
|
1069
|
-
...backwardsCursor ? { backwardsCursor } : {}
|
|
1070
|
-
};
|
|
1071
|
-
if (Buffer.byteLength(JSON.stringify(page), "utf8") > 20971520) throw new Error("Codex app-server transcript page exceeds the safe response size");
|
|
1072
|
-
return page;
|
|
1073
|
-
}
|
|
1074
|
-
function requireBoundThread(entry) {
|
|
1075
|
-
if (!entry.boundThreadId) throw new CatalogParamsError("Codex adoption is missing its bound thread. Retry.");
|
|
1076
|
-
return entry.boundThreadId;
|
|
1077
|
-
}
|
|
1078
|
-
//#endregion
|
|
1079
|
-
//#region extensions/codex/src/session-catalog-node-adoption.ts
|
|
1080
|
-
const CODEX_NODE_SESSION_KEY_PREFIX = "harness:codex:node-session:";
|
|
1081
|
-
const continueOperations = /* @__PURE__ */ new Map();
|
|
1082
|
-
const sessionActionTails = /* @__PURE__ */ new Map();
|
|
1083
|
-
async function runSessionActionExclusive(threadId, run) {
|
|
1084
|
-
const operation = (sessionActionTails.get(threadId) ?? Promise.resolve()).then(run);
|
|
1085
|
-
const tail = operation.then(() => void 0, () => void 0);
|
|
1086
|
-
sessionActionTails.set(threadId, tail);
|
|
1087
|
-
try {
|
|
1088
|
-
return await operation;
|
|
1089
|
-
} finally {
|
|
1090
|
-
if (sessionActionTails.get(threadId) === tail) sessionActionTails.delete(threadId);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
function adoptionSessionKeyRest(sessionKey) {
|
|
1094
|
-
const trimmed = sessionKey.trim();
|
|
1095
|
-
return parseAgentSessionKey(trimmed)?.rest ?? trimmed;
|
|
1096
|
-
}
|
|
1097
|
-
function nodeAdoptionSessionKey(hostId, threadId) {
|
|
1098
|
-
const source = JSON.stringify([hostId, threadId]);
|
|
1099
|
-
return sessionCatalogAdoptedSessionKey(CODEX_NODE_SESSION_KEY_PREFIX, source);
|
|
1100
|
-
}
|
|
1101
|
-
function readNodeSessionMarker(entry) {
|
|
1102
|
-
const codex = isRecord(entry.pluginExtensions?.codex) ? entry.pluginExtensions.codex : void 0;
|
|
1103
|
-
const marker = codex && isRecord(codex.sessionCatalog) ? codex.sessionCatalog : void 0;
|
|
1104
|
-
if (!marker || typeof marker.sourceHostId !== "string" || !marker.sourceHostId.startsWith("node:") || typeof marker.sourceThreadId !== "string" || !marker.sourceThreadId.trim() || typeof marker.nodeId !== "string" || !marker.nodeId.trim()) return;
|
|
1105
|
-
return {
|
|
1106
|
-
sourceHostId: marker.sourceHostId,
|
|
1107
|
-
sourceThreadId: marker.sourceThreadId,
|
|
1108
|
-
nodeId: marker.nodeId,
|
|
1109
|
-
...marker.initializing === true ? { initializing: true } : {}
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
function listNodeAdoptedSessionEntries(params) {
|
|
1113
|
-
const adopted = /* @__PURE__ */ new Map();
|
|
1114
|
-
for (const { agentId, entry, sessionKey } of listSessionCatalogEntries({
|
|
1115
|
-
...params.agentId ? { agentId: params.agentId } : {},
|
|
1116
|
-
config: params.config ?? {},
|
|
1117
|
-
runtime: params.runtime,
|
|
1118
|
-
sessionEntries: params.sessionEntries
|
|
1119
|
-
})) {
|
|
1120
|
-
const marker = readNodeSessionMarker(entry);
|
|
1121
|
-
const sessionId = entry.sessionId?.trim();
|
|
1122
|
-
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;
|
|
1123
|
-
const sourceKey = sessionCatalogAdoptedSourceKey(marker.sourceHostId, marker.sourceThreadId);
|
|
1124
|
-
if (adopted.has(sourceKey)) throw new Error(`multiple OpenClaw sessions adopt Codex thread ${marker.sourceThreadId} on ${marker.sourceHostId}`);
|
|
1125
|
-
adopted.set(sourceKey, {
|
|
1126
|
-
key: sessionKey,
|
|
1127
|
-
sessionId,
|
|
1128
|
-
agentId,
|
|
1129
|
-
...marker.initializing === true ? { initializing: true } : {}
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
return adopted;
|
|
1133
|
-
}
|
|
1134
|
-
function findNodeAdoptedSessionEntry(params) {
|
|
1135
|
-
return listNodeAdoptedSessionEntries(params).get(sessionCatalogAdoptedSourceKey(params.hostId, params.threadId));
|
|
1136
|
-
}
|
|
1137
|
-
function nodeSessionMarker(params) {
|
|
1138
|
-
return {
|
|
1139
|
-
sourceHostId: params.hostId,
|
|
1140
|
-
sourceThreadId: params.threadId,
|
|
1141
|
-
nodeId: params.nodeId,
|
|
1142
|
-
...params.initializing === true ? { initializing: true } : {}
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
async function finalizeNodeAdoptedSession(params) {
|
|
1146
|
-
const changedError = () => new CatalogParamsError("Codex OpenClaw session changed before it could be bound. Retry.");
|
|
1147
|
-
let finalized;
|
|
1148
|
-
try {
|
|
1149
|
-
finalized = await params.api.runtime.agent.session.patchSessionEntry({
|
|
1150
|
-
sessionKey: params.adopted.key,
|
|
1151
|
-
readConsistency: "latest",
|
|
1152
|
-
preserveActivity: true,
|
|
1153
|
-
update: (entry) => {
|
|
1154
|
-
const current = readNodeSessionMarker(entry);
|
|
1155
|
-
if (entry.sessionId?.trim() !== params.adopted.sessionId || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true || !current || current.sourceHostId !== params.marker.sourceHostId || current.sourceThreadId !== params.marker.sourceThreadId || current.nodeId !== params.marker.nodeId) throw changedError();
|
|
1156
|
-
if (current.initializing !== true) return {
|
|
1157
|
-
archivedAt: void 0,
|
|
1158
|
-
archivedBy: void 0,
|
|
1159
|
-
archiveReason: void 0
|
|
1160
|
-
};
|
|
1161
|
-
const codex = isRecord(entry.pluginExtensions?.codex) ? entry.pluginExtensions.codex : {};
|
|
1162
|
-
return {
|
|
1163
|
-
archivedAt: void 0,
|
|
1164
|
-
archivedBy: void 0,
|
|
1165
|
-
archiveReason: void 0,
|
|
1166
|
-
pluginExtensions: {
|
|
1167
|
-
...entry.pluginExtensions,
|
|
1168
|
-
codex: {
|
|
1169
|
-
...codex,
|
|
1170
|
-
sessionCatalog: params.marker
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
|
-
});
|
|
1176
|
-
} catch (error) {
|
|
1177
|
-
const currentEntry = params.api.runtime.agent.session.getSessionEntry({
|
|
1178
|
-
sessionKey: params.adopted.key,
|
|
1179
|
-
readConsistency: "latest"
|
|
1180
|
-
});
|
|
1181
|
-
const current = currentEntry ? readNodeSessionMarker(currentEntry) : void 0;
|
|
1182
|
-
if (currentEntry?.sessionId?.trim() === params.adopted.sessionId && current?.initializing !== true && current?.sourceHostId === params.marker.sourceHostId && current.sourceThreadId === params.marker.sourceThreadId && current.nodeId === params.marker.nodeId) return;
|
|
1183
|
-
throw error;
|
|
1184
|
-
}
|
|
1185
|
-
if (!finalized) throw changedError();
|
|
1186
|
-
}
|
|
1187
|
-
async function createOrReuseNodeAdoptedSession(params) {
|
|
1188
|
-
const existing = findNodeAdoptedSessionEntry({
|
|
1189
|
-
agentId: params.agentId,
|
|
1190
|
-
config: params.config,
|
|
1191
|
-
runtime: params.api.runtime,
|
|
1192
|
-
hostId: params.hostId,
|
|
1193
|
-
threadId: params.record.threadId,
|
|
1194
|
-
includeInitializing: true
|
|
1195
|
-
});
|
|
1196
|
-
if (existing) return existing;
|
|
1197
|
-
const initializingMarker = {
|
|
1198
|
-
...nodeSessionMarker({
|
|
1199
|
-
hostId: params.hostId,
|
|
1200
|
-
threadId: params.record.threadId,
|
|
1201
|
-
nodeId: params.nodeId
|
|
1202
|
-
}),
|
|
1203
|
-
initializing: true
|
|
1204
|
-
};
|
|
1205
|
-
try {
|
|
1206
|
-
const created = await params.api.runtime.agent.session.createSessionEntry({
|
|
1207
|
-
cfg: params.config,
|
|
1208
|
-
key: nodeAdoptionSessionKey(params.hostId, params.record.threadId),
|
|
1209
|
-
agentId: params.agentId,
|
|
1210
|
-
recoverMatchingInitialEntry: true,
|
|
1211
|
-
displayName: params.record.name ?? void 0,
|
|
1212
|
-
...params.record.cwd?.trim() ? { spawnedCwd: params.record.cwd.trim() } : {},
|
|
1213
|
-
initialEntry: {
|
|
1214
|
-
agentHarnessId: "codex",
|
|
1215
|
-
modelSelectionLocked: true,
|
|
1216
|
-
pluginExtensions: { codex: { sessionCatalog: initializingMarker } }
|
|
1217
|
-
},
|
|
1218
|
-
afterCreate: async (entry) => {
|
|
1219
|
-
const storePath = resolveStorePath(params.config.session?.store, { agentId: entry.agentId });
|
|
1220
|
-
await importCodexThreadHistoryToTranscript({
|
|
1221
|
-
thread: params.history.thread,
|
|
1222
|
-
throughTurnId: params.history.throughTurnId,
|
|
1223
|
-
storePath,
|
|
1224
|
-
sessionId: entry.sessionId,
|
|
1225
|
-
sessionKey: entry.key,
|
|
1226
|
-
agentId: entry.agentId,
|
|
1227
|
-
...params.record.cwd?.trim() ? { cwd: params.record.cwd.trim() } : {},
|
|
1228
|
-
modelProvider: params.record.modelProvider,
|
|
1229
|
-
config: params.config
|
|
1230
|
-
});
|
|
1231
|
-
return { pluginExtensions: { codex: { sessionCatalog: initializingMarker } } };
|
|
1232
|
-
}
|
|
1233
|
-
});
|
|
1234
|
-
return {
|
|
1235
|
-
key: created.key,
|
|
1236
|
-
sessionId: created.sessionId,
|
|
1237
|
-
agentId: created.agentId,
|
|
1238
|
-
initializing: true
|
|
1239
|
-
};
|
|
1240
|
-
} catch (error) {
|
|
1241
|
-
const raced = findNodeAdoptedSessionEntry({
|
|
1242
|
-
agentId: params.agentId,
|
|
1243
|
-
config: params.config,
|
|
1244
|
-
runtime: params.api.runtime,
|
|
1245
|
-
hostId: params.hostId,
|
|
1246
|
-
threadId: params.record.threadId,
|
|
1247
|
-
includeInitializing: true
|
|
1248
|
-
});
|
|
1249
|
-
if (raced) return raced;
|
|
1250
|
-
throw error;
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
//#endregion
|
|
1254
|
-
//#region extensions/codex/src/session-catalog-adoption.ts
|
|
1255
|
-
const CODEX_SUPERVISION_SESSION_KEY_PREFIX = "harness:codex:supervision:";
|
|
1256
|
-
const boundCatalogSessionId = (value) => boundedCatalogString(value, 256);
|
|
1257
|
-
function requireIdleThread(thread, action) {
|
|
1258
|
-
if (thread.status?.type === "idle" || action === "archive" && thread.status?.type === "notLoaded") return;
|
|
1259
|
-
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"}`);
|
|
1260
|
-
throw new CatalogParamsError(action === "archive" ? "Codex session cannot be archived in its current state" : "Codex session cannot start a branch in its current state");
|
|
1261
|
-
}
|
|
1262
|
-
function adoptionSessionKey(threadId, sourceHomeId) {
|
|
1263
|
-
const source = sourceHomeId ? JSON.stringify([sourceHomeId, threadId]) : threadId;
|
|
1264
|
-
return sessionCatalogAdoptedSessionKey(CODEX_SUPERVISION_SESSION_KEY_PREFIX, source);
|
|
1265
|
-
}
|
|
1266
|
-
function isAdoptionSessionKeyForThread(sessionKey, threadId, sourceHomeId) {
|
|
1267
|
-
return adoptionSessionKeyRest(sessionKey) === adoptionSessionKey(threadId, sourceHomeId);
|
|
1268
|
-
}
|
|
1269
|
-
function readCodexSupervisionMarker(entry) {
|
|
1270
|
-
const codex = isRecord(entry.pluginExtensions?.codex) ? entry.pluginExtensions.codex : void 0;
|
|
1271
|
-
const marker = codex && isRecord(codex.supervision) ? codex.supervision : void 0;
|
|
1272
|
-
const sourceThreadId = marker?.sourceThreadId;
|
|
1273
|
-
const sourceHomeId = marker?.sourceHomeId;
|
|
1274
|
-
if (typeof sourceThreadId !== "string" || !sourceThreadId.trim() || sourceHomeId !== void 0 && (typeof sourceHomeId !== "string" || !sourceHomeId.trim())) return;
|
|
1275
|
-
return {
|
|
1276
|
-
sourceThreadId: sourceThreadId.trim(),
|
|
1277
|
-
...typeof sourceHomeId === "string" ? { sourceHomeId: sourceHomeId.trim() } : {}
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
async function listAdoptedSessionEntries(params) {
|
|
1281
|
-
const adopted = /* @__PURE__ */ new Map();
|
|
1282
|
-
for (const { agentId, entry, sessionKey } of listSessionCatalogEntries({
|
|
1283
|
-
...params.agentId ? { agentId: params.agentId } : {},
|
|
1284
|
-
config: params.config ?? {},
|
|
1285
|
-
runtime: params.runtime,
|
|
1286
|
-
sessionEntries: params.sessionEntries
|
|
1287
|
-
})) {
|
|
1288
|
-
const sessionKeyRest = adoptionSessionKeyRest(sessionKey);
|
|
1289
|
-
const marker = readCodexSupervisionMarker(entry);
|
|
1290
|
-
if (!sessionKeyRest.startsWith(CODEX_SUPERVISION_SESSION_KEY_PREFIX) || !marker || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true) continue;
|
|
1291
|
-
const sessionId = entry.sessionId?.trim();
|
|
1292
|
-
if (!sessionId) continue;
|
|
1293
|
-
const binding = params.bindingStore.read(sessionBindingIdentity({
|
|
1294
|
-
sessionId,
|
|
1295
|
-
sessionKey,
|
|
1296
|
-
config: params.config
|
|
1297
|
-
}));
|
|
1298
|
-
const sourceThreadId = binding?.supervisionSourceThreadId?.trim();
|
|
1299
|
-
const boundThreadId = binding?.threadId.trim();
|
|
1300
|
-
if (binding?.connectionScope !== "supervision" || !sourceThreadId || !boundThreadId || sessionKeyRest !== adoptionSessionKey(sourceThreadId, marker.sourceHomeId)) continue;
|
|
1301
|
-
const sourceKey = sessionCatalogAdoptedSourceKey(marker.sourceHomeId ?? "gateway:local", sourceThreadId);
|
|
1302
|
-
if (adopted.has(sourceKey)) throw new Error(`multiple OpenClaw sessions adopt Codex thread ${sourceThreadId} from the same home`);
|
|
1303
|
-
adopted.set(sourceKey, {
|
|
1304
|
-
key: sessionKey,
|
|
1305
|
-
sessionId,
|
|
1306
|
-
agentId,
|
|
1307
|
-
boundThreadId
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
|
-
return adopted;
|
|
1311
|
-
}
|
|
1312
|
-
async function findAdoptedSessionEntry(params) {
|
|
1313
|
-
const adopted = await listAdoptedSessionEntries(params);
|
|
1314
|
-
return adopted.get(sessionCatalogAdoptedSourceKey(params.sourceHomeId ?? "gateway:local", params.threadId)) ?? (params.sourceHomeId && params.allowLegacy === true ? adopted.get(sessionCatalogAdoptedSourceKey("gateway:local", params.threadId)) : void 0);
|
|
1315
|
-
}
|
|
1316
|
-
function matchesPendingAdoptionBinding(binding, expected) {
|
|
1317
|
-
const historyCoveredThrough = binding?.historyCoveredThrough;
|
|
1318
|
-
return binding?.threadId === expected.sourceThreadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && binding.cwd === expected.cwd && binding.conversationSourceTransferComplete === true && binding.preserveNativeModel === true && binding.pendingSupervisionBranch?.sourceThreadId === expected.sourceThreadId && binding.pendingSupervisionBranch.connectionFingerprint === expected.connectionFingerprint && binding.pendingSupervisionBranch.lastTurnId === expected.lastTurnId && (binding.pendingSupervisionBranch.cleanupThreadIds?.length ?? 0) === 0 && typeof historyCoveredThrough === "string" && Number.isFinite(Date.parse(historyCoveredThrough));
|
|
1319
|
-
}
|
|
1320
|
-
async function ensurePendingAdoptionBinding(params) {
|
|
1321
|
-
const pending = {
|
|
1322
|
-
sourceThreadId: params.sourceThreadId,
|
|
1323
|
-
connectionFingerprint: params.connectionFingerprint,
|
|
1324
|
-
...params.lastTurnId ? { lastTurnId: params.lastTurnId } : {}
|
|
1325
|
-
};
|
|
1326
|
-
const ownsGeneration = await reclaimCurrentCodexSessionGeneration({
|
|
1327
|
-
assertCurrent: params.initialization.assertCurrent,
|
|
1328
|
-
bindingStore: params.bindingStore,
|
|
1329
|
-
identity: params.identity,
|
|
1330
|
-
config: params.config
|
|
1331
|
-
});
|
|
1332
|
-
params.initialization.assertCurrent();
|
|
1333
|
-
if (!ownsGeneration) throw new Error(`failed to claim the OpenClaw session generation for ${params.sourceThreadId}`);
|
|
1334
|
-
const existing = params.bindingStore.read(params.identity);
|
|
1335
|
-
params.initialization.assertCurrent();
|
|
1336
|
-
if (existing) {
|
|
1337
|
-
if (matchesPendingAdoptionBinding(existing, params)) return;
|
|
1338
|
-
throw new Error(`OpenClaw session is already bound to Codex thread ${existing.threadId}`);
|
|
1339
|
-
}
|
|
1340
|
-
const binding = {
|
|
1341
|
-
threadId: params.sourceThreadId,
|
|
1342
|
-
connectionScope: "supervision",
|
|
1343
|
-
supervisionSourceThreadId: params.sourceThreadId,
|
|
1344
|
-
cwd: params.cwd,
|
|
1345
|
-
historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1346
|
-
conversationSourceTransferComplete: true,
|
|
1347
|
-
preserveNativeModel: true,
|
|
1348
|
-
pendingSupervisionBranch: pending
|
|
1349
|
-
};
|
|
1350
|
-
await params.initialization.bind(binding);
|
|
1351
|
-
}
|
|
1352
|
-
async function createOrReuseAdoptedSession(params) {
|
|
1353
|
-
const runtime = params.api.runtime;
|
|
1354
|
-
const lookup = {
|
|
1355
|
-
...params,
|
|
1356
|
-
runtime,
|
|
1357
|
-
threadId: params.sourceThread.id
|
|
1358
|
-
};
|
|
1359
|
-
const existing = await findAdoptedSessionEntry(lookup);
|
|
1360
|
-
if (existing) return existing;
|
|
1361
|
-
try {
|
|
1362
|
-
const spawnedCwd = params.sourceThread.cwd?.trim() || void 0;
|
|
1363
|
-
const pendingLastTurnId = codexLastTerminalTurnId(params.sourceThread, boundCatalogSessionId);
|
|
1364
|
-
const marker = {
|
|
1365
|
-
sourceThreadId: params.sourceThread.id,
|
|
1366
|
-
...params.sourceHomeId ? { sourceHomeId: params.sourceHomeId } : {}
|
|
1367
|
-
};
|
|
1368
|
-
const created = await createImportedCodexSession({
|
|
1369
|
-
runtime: params.api.runtime,
|
|
1370
|
-
bindingStore: params.bindingStore,
|
|
1371
|
-
config: params.config,
|
|
1372
|
-
key: adoptionSessionKey(params.sourceThread.id, params.sourceHomeId),
|
|
1373
|
-
agentId: params.agentId,
|
|
1374
|
-
displayName: params.sourceThread.name ?? void 0,
|
|
1375
|
-
thread: params.sourceThread,
|
|
1376
|
-
throughTurnId: pendingLastTurnId ?? null,
|
|
1377
|
-
recoverMatchingInitialEntry: true,
|
|
1378
|
-
initialEntry: {
|
|
1379
|
-
agentHarnessId: "codex",
|
|
1380
|
-
modelSelectionLocked: true,
|
|
1381
|
-
pluginExtensions: { codex: { supervision: {
|
|
1382
|
-
...marker,
|
|
1383
|
-
initializing: true,
|
|
1384
|
-
modelLocked: true
|
|
1385
|
-
} } }
|
|
1386
|
-
},
|
|
1387
|
-
afterImport: async (entry, initialization) => {
|
|
1388
|
-
const identity = sessionBindingIdentity({
|
|
1389
|
-
sessionId: entry.sessionId,
|
|
1390
|
-
sessionKey: entry.key,
|
|
1391
|
-
config: params.config
|
|
1392
|
-
});
|
|
1393
|
-
await ensurePendingAdoptionBinding({
|
|
1394
|
-
bindingStore: params.bindingStore,
|
|
1395
|
-
config: params.config,
|
|
1396
|
-
identity,
|
|
1397
|
-
initialization,
|
|
1398
|
-
sourceThreadId: params.sourceThread.id,
|
|
1399
|
-
connectionFingerprint: params.connectionFingerprint,
|
|
1400
|
-
cwd: spawnedCwd ?? "",
|
|
1401
|
-
...pendingLastTurnId ? { lastTurnId: pendingLastTurnId } : {}
|
|
1402
|
-
});
|
|
1403
|
-
return { pluginExtensions: { codex: { supervision: {
|
|
1404
|
-
...marker,
|
|
1405
|
-
modelLocked: true
|
|
1406
|
-
} } } };
|
|
1407
|
-
}
|
|
1408
|
-
});
|
|
1409
|
-
return {
|
|
1410
|
-
key: created.key,
|
|
1411
|
-
sessionId: created.sessionId,
|
|
1412
|
-
agentId: created.agentId,
|
|
1413
|
-
boundThreadId: params.sourceThread.id
|
|
1414
|
-
};
|
|
1415
|
-
} catch (error) {
|
|
1416
|
-
const raced = await findAdoptedSessionEntry(lookup);
|
|
1417
|
-
if (raced) return raced;
|
|
1418
|
-
throw error;
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
async function continueLocalCodexSessionInner(params) {
|
|
1422
|
-
await params.control.requireEligibleThread(params.threadId);
|
|
1423
|
-
const existing = await findAdoptedSessionEntry({
|
|
1424
|
-
...params,
|
|
1425
|
-
runtime: params.api.runtime
|
|
1426
|
-
});
|
|
1427
|
-
if (existing) {
|
|
1428
|
-
const boundThreadId = requireBoundThread(existing);
|
|
1429
|
-
const boundThread = await params.control.readThread(boundThreadId, true);
|
|
1430
|
-
if (boundThread.id !== boundThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1431
|
-
const changedError = () => new CatalogParamsError("Codex OpenClaw session changed before it could be opened. Retry.");
|
|
1432
|
-
if (!await params.api.runtime.agent.session.patchSessionEntry({
|
|
1433
|
-
sessionKey: existing.key,
|
|
1434
|
-
readConsistency: "latest",
|
|
1435
|
-
preserveActivity: true,
|
|
1436
|
-
update: (entry) => {
|
|
1437
|
-
if (entry.sessionId?.trim() !== existing.sessionId || entry.initializationPending === true || entry.agentHarnessId !== "codex" || entry.modelSelectionLocked !== true) throw changedError();
|
|
1438
|
-
return {
|
|
1439
|
-
archivedAt: void 0,
|
|
1440
|
-
archivedBy: void 0,
|
|
1441
|
-
archiveReason: void 0
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
})) throw changedError();
|
|
1445
|
-
const connectionFingerprint = params.control.connectionFingerprint;
|
|
1446
|
-
if (connectionFingerprint) params.onContinued?.({
|
|
1447
|
-
connectionFingerprint,
|
|
1448
|
-
...codexUpstreamBaseline(boundThread, boundCatalogSessionId)
|
|
1449
|
-
});
|
|
1450
|
-
return {
|
|
1451
|
-
sessionKey: existing.key,
|
|
1452
|
-
disposition: "existing"
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
const sourceThread = await params.control.readThread(params.threadId, true);
|
|
1456
|
-
if (sourceThread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1457
|
-
if (sourceThread.status?.type !== "notLoaded") requireIdleThread(sourceThread, "continue");
|
|
1458
|
-
const connectionFingerprint = params.control.connectionFingerprint;
|
|
1459
|
-
if (!connectionFingerprint) throw new Error("Codex Continue requires a pinned app-server connection");
|
|
1460
|
-
const adopted = await createOrReuseAdoptedSession({
|
|
1461
|
-
...params,
|
|
1462
|
-
sourceThread,
|
|
1463
|
-
connectionFingerprint
|
|
1464
|
-
});
|
|
1465
|
-
const boundThreadId = requireBoundThread(adopted);
|
|
1466
|
-
const baselineThread = boundThreadId === sourceThread.id ? sourceThread : await params.control.readThread(boundThreadId, true);
|
|
1467
|
-
if (baselineThread.id !== boundThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1468
|
-
params.onContinued?.({
|
|
1469
|
-
connectionFingerprint,
|
|
1470
|
-
...codexUpstreamBaseline(baselineThread, boundCatalogSessionId)
|
|
1471
|
-
});
|
|
1472
|
-
return {
|
|
1473
|
-
sessionKey: adopted.key,
|
|
1474
|
-
disposition: "forked"
|
|
1475
|
-
};
|
|
1476
|
-
}
|
|
1477
|
-
/** Creates one locked OpenClaw branch whose first harness run forks the Codex source. */
|
|
1478
|
-
async function continueLocalCodexSession(params) {
|
|
1479
|
-
const sourceKey = sessionCatalogAdoptedSourceKey(params.hostId ?? "gateway:local", params.threadId);
|
|
1480
|
-
const operationKey = sessionCatalogAdoptedSourceKey(params.agentId, sourceKey);
|
|
1481
|
-
const current = continueOperations.get(operationKey);
|
|
1482
|
-
if (current) return await current;
|
|
1483
|
-
const run = async (control) => await continueLocalCodexSessionInner({
|
|
1484
|
-
...params,
|
|
1485
|
-
control
|
|
1486
|
-
});
|
|
1487
|
-
const operation = runSessionActionExclusive(sourceKey, async () => params.control.withPinnedConnection(run));
|
|
1488
|
-
continueOperations.set(operationKey, operation);
|
|
1489
|
-
try {
|
|
1490
|
-
return await operation;
|
|
1491
|
-
} finally {
|
|
1492
|
-
if (continueOperations.get(operationKey) === operation) continueOperations.delete(operationKey);
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
//#endregion
|
|
1496
|
-
//#region extensions/codex/src/session-catalog-archive.ts
|
|
1497
|
-
function assertNoPendingSupervisionBranch(params) {
|
|
1498
|
-
const adoptedEntries = [params.agentId, ...listAgentIds$1(params.config).filter((agentId) => agentId !== params.agentId)].flatMap((agentId) => params.runtime.agent.session.listSessionEntries({
|
|
1499
|
-
agentId,
|
|
1500
|
-
readOnly: true
|
|
1501
|
-
})).filter((candidate) => isAdoptionSessionKeyForThread(candidate.sessionKey, params.threadId, params.sourceHomeId) || params.sourceHomeId !== void 0 && params.allowLegacy === true && isAdoptionSessionKeyForThread(candidate.sessionKey, params.threadId));
|
|
1502
|
-
for (const adopted of adoptedEntries) {
|
|
1503
|
-
if (adopted.entry.initializationPending === true) throw new CatalogParamsError("Codex session cannot be archived while its OpenClaw branch is initializing");
|
|
1504
|
-
const sessionId = adopted.entry.sessionId?.trim();
|
|
1505
|
-
if (!sessionId) continue;
|
|
1506
|
-
const binding = params.bindingStore.read(sessionBindingIdentity({
|
|
1507
|
-
sessionId,
|
|
1508
|
-
sessionKey: adopted.sessionKey,
|
|
1509
|
-
config: params.config
|
|
1510
|
-
}));
|
|
1511
|
-
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");
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
/** Archives one inactive Gateway-local Codex thread after a fresh status read. */
|
|
1515
|
-
async function archiveLocalCodexSession(params) {
|
|
1516
|
-
return await runSessionActionExclusive(sessionCatalogAdoptedSourceKey(params.hostId ?? "gateway:local", params.threadId), async () => {
|
|
1517
|
-
return await params.bindingStore.withThreadArchiveFence(async () => {
|
|
1518
|
-
const run = async (control) => {
|
|
1519
|
-
assertNoPendingSupervisionBranch(params);
|
|
1520
|
-
await control.requireEligibleThread(params.threadId);
|
|
1521
|
-
const thread = await control.readThread(params.threadId, false);
|
|
1522
|
-
if (thread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
1523
|
-
requireIdleThread(thread, "archive");
|
|
1524
|
-
if (await params.bindingStore.hasOtherThreadOwner(params.threadId)) throw new CatalogParamsError("Codex session cannot be archived while it is attached to an OpenClaw session");
|
|
1525
|
-
await assertCodexArchiveDescendantsUnowned({
|
|
1526
|
-
bindingStore: params.bindingStore,
|
|
1527
|
-
threadId: params.threadId,
|
|
1528
|
-
listPage: (request) => control.listDescendantPage(request),
|
|
1529
|
-
assertDescendantIdle: async (descendantThreadId) => {
|
|
1530
|
-
const descendant = await control.readThread(descendantThreadId, false);
|
|
1531
|
-
if (descendant.id !== descendantThreadId) throw new Error("Codex app-server returned a different descendant than requested");
|
|
1532
|
-
requireIdleThread(descendant, "archive");
|
|
1533
|
-
}
|
|
1534
|
-
});
|
|
1535
|
-
await control.archiveThread(params.threadId);
|
|
1536
|
-
return { archived: true };
|
|
1537
|
-
};
|
|
1538
|
-
return await params.control.withPinnedConnection(run);
|
|
1539
|
-
});
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
//#endregion
|
|
1543
|
-
//#region extensions/codex/src/session-catalog-create.ts
|
|
1544
|
-
const CODEX_AGENT_RUNTIME_ID = "codex";
|
|
1545
|
-
const CODEX_CATALOG_DEFAULT_MODEL_REF = "openai/gpt-5.6-sol";
|
|
1546
|
-
function resolveCodexCatalogCreateSession(config, requestedAgentId) {
|
|
1547
|
-
if (!config) return;
|
|
1548
|
-
const agentId = requestedAgentId ?? resolveDefaultAgentId$1(config);
|
|
1549
|
-
const defaultModel = resolveDefaultModelForAgent({
|
|
1550
|
-
cfg: config,
|
|
1551
|
-
agentId
|
|
1552
|
-
});
|
|
1553
|
-
return "error" in resolveAllowedModelRef({
|
|
1554
|
-
cfg: config,
|
|
1555
|
-
catalog: [],
|
|
1556
|
-
raw: CODEX_CATALOG_DEFAULT_MODEL_REF,
|
|
1557
|
-
defaultProvider: defaultModel.provider,
|
|
1558
|
-
defaultModel: defaultModel.model,
|
|
1559
|
-
agentId
|
|
1560
|
-
}) ? void 0 : {
|
|
1561
|
-
model: CODEX_CATALOG_DEFAULT_MODEL_REF,
|
|
1562
|
-
agentRuntime: CODEX_AGENT_RUNTIME_ID
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
//#endregion
|
|
1566
|
-
//#region extensions/codex/src/session-catalog-node-continue.ts
|
|
1567
|
-
const CODEX_NODE_CONTINUE_COMMANDS = [
|
|
1568
|
-
CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1569
|
-
CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
1570
|
-
CODEX_CLI_SESSION_RESUME_COMMAND
|
|
1571
|
-
];
|
|
1572
|
-
const NODE_CATALOG_LIST_RESPONSE_TIMEOUT_MS = 8e3;
|
|
1573
|
-
const continueNodeAdoption = createSessionCatalogAdoptionCoordinator();
|
|
1574
|
-
function nodeLabel(node) {
|
|
1575
|
-
return node.displayName?.trim() || node.remoteIp?.trim() || node.nodeId;
|
|
1576
|
-
}
|
|
1577
|
-
function compareNodeLabels(left, right) {
|
|
1578
|
-
const leftLabel = nodeLabel(left);
|
|
1579
|
-
const rightLabel = nodeLabel(right);
|
|
1580
|
-
if (leftLabel < rightLabel) return -1;
|
|
1581
|
-
if (leftLabel > rightLabel) return 1;
|
|
1582
|
-
return 0;
|
|
1583
|
-
}
|
|
1584
|
-
function canContinueCodexOnNode(node) {
|
|
1585
|
-
return node.connected === true && CODEX_NODE_CONTINUE_COMMANDS.every((command) => node.commands?.includes(command) === true && node.invocableCommands?.includes(command) === true);
|
|
1586
|
-
}
|
|
1587
|
-
async function listPairedNode(params) {
|
|
1588
|
-
const hostId = `node:${params.node.nodeId}`;
|
|
1589
|
-
const common = {
|
|
1590
|
-
hostId,
|
|
1591
|
-
label: nodeLabel(params.node),
|
|
1592
|
-
kind: "node",
|
|
1593
|
-
nodeId: params.node.nodeId,
|
|
1594
|
-
canContinueCodex: canContinueCodexOnNode(params.node),
|
|
1595
|
-
...params.terminalCapabilities
|
|
1596
|
-
};
|
|
1597
|
-
if (params.node.connected !== true) {
|
|
1598
|
-
const host = {
|
|
1599
|
-
...common,
|
|
1600
|
-
connected: false,
|
|
1601
|
-
sessions: [],
|
|
1602
|
-
error: {
|
|
1603
|
-
code: "NODE_OFFLINE",
|
|
1604
|
-
message: "Paired node is offline"
|
|
1605
|
-
}
|
|
1606
|
-
};
|
|
1607
|
-
params.onHost?.(host);
|
|
1608
|
-
return host;
|
|
1609
|
-
}
|
|
1610
|
-
if (!params.node.commands?.includes("codex.appServer.threads.list.v1")) {
|
|
1611
|
-
const host = {
|
|
1612
|
-
...common,
|
|
1613
|
-
connected: true,
|
|
1614
|
-
sessions: []
|
|
1615
|
-
};
|
|
1616
|
-
params.onHost?.(host);
|
|
1617
|
-
return host;
|
|
1618
|
-
}
|
|
1619
|
-
const eventualHost = Promise.resolve().then(async () => {
|
|
1620
|
-
const page = filterCatalogPageByTitle(parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1621
|
-
nodeId: params.node.nodeId,
|
|
1622
|
-
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1623
|
-
params: {
|
|
1624
|
-
agentId: params.agentId,
|
|
1625
|
-
cursor: params.query.cursors?.[hostId],
|
|
1626
|
-
limit: params.query.limitPerHost,
|
|
1627
|
-
searchTerm: params.query.search
|
|
1628
|
-
},
|
|
1629
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1630
|
-
scopes: ["operator.write"],
|
|
1631
|
-
signal: params.signal
|
|
1632
|
-
}))), params.query.search);
|
|
1633
|
-
return {
|
|
1634
|
-
...common,
|
|
1635
|
-
connected: true,
|
|
1636
|
-
...page,
|
|
1637
|
-
sessions: page.sessions.map((session) => {
|
|
1638
|
-
const adopted = params.adoptedSessions.get(sessionCatalogAdoptedSourceKey(hostId, session.threadId));
|
|
1639
|
-
return adopted ? Object.assign({}, session, { sessionKey: adopted.key }) : session;
|
|
1640
|
-
})
|
|
1641
|
-
};
|
|
1642
|
-
}).catch((error) => ({
|
|
1643
|
-
...common,
|
|
1644
|
-
connected: true,
|
|
1645
|
-
sessions: [],
|
|
1646
|
-
error: catalogError("NODE_INVOKE_FAILED", error)
|
|
1647
|
-
}));
|
|
1648
|
-
publishSessionCatalogHost(params, eventualHost);
|
|
1649
|
-
try {
|
|
1650
|
-
return await withTimeout(eventualHost, NODE_CATALOG_LIST_RESPONSE_TIMEOUT_MS, "paired node Codex session catalog timed out");
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
return {
|
|
1653
|
-
...common,
|
|
1654
|
-
connected: true,
|
|
1655
|
-
sessions: [],
|
|
1656
|
-
error: catalogError("NODE_INVOKE_FAILED", error)
|
|
1657
|
-
};
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
async function requireNodeForCodexContinue(params) {
|
|
1661
|
-
const nodeId = params.hostId.slice(5).trim();
|
|
1662
|
-
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1663
|
-
const node = (await params.runtime.nodes.list()).nodes.find((candidate) => candidate.nodeId === nodeId);
|
|
1664
|
-
if (!node || !canContinueCodexOnNode(node)) throw new CatalogParamsError("paired node does not permit Codex session continuation");
|
|
1665
|
-
return {
|
|
1666
|
-
node,
|
|
1667
|
-
nodeId
|
|
1668
|
-
};
|
|
1669
|
-
}
|
|
1670
|
-
async function resolveNodeCodexRecord(params) {
|
|
1671
|
-
let cursor;
|
|
1672
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
1673
|
-
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1674
|
-
const page = parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1675
|
-
nodeId: params.nodeId,
|
|
1676
|
-
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1677
|
-
params: {
|
|
1678
|
-
agentId: params.agentId,
|
|
1679
|
-
limit: 100,
|
|
1680
|
-
...cursor ? { cursor } : {}
|
|
1681
|
-
},
|
|
1682
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1683
|
-
scopes: ["operator.write"]
|
|
1684
|
-
})));
|
|
1685
|
-
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1686
|
-
if (record) return record;
|
|
1687
|
-
const nextCursor = page.nextCursor?.trim();
|
|
1688
|
-
if (!nextCursor) break;
|
|
1689
|
-
if (seenCursors.has(nextCursor)) throw new CatalogParamsError("Codex session eligibility could not be verified");
|
|
1690
|
-
seenCursors.add(nextCursor);
|
|
1691
|
-
cursor = nextCursor;
|
|
1692
|
-
}
|
|
1693
|
-
throw new CatalogParamsError("Codex session is unavailable on the paired node");
|
|
1694
|
-
}
|
|
1695
|
-
function requireContinuableNodeRecord(record) {
|
|
1696
|
-
if (record.archived) throw new CatalogParamsError("Codex session is archived on the paired node");
|
|
1697
|
-
if (!isInteractiveThreadSource(record.source)) throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1698
|
-
if (record.status === "idle" || record.status === "notLoaded") return;
|
|
1699
|
-
if (record.status === "active") throw new CatalogParamsError("Codex session is active on the paired node; wait for it to finish before continuing");
|
|
1700
|
-
throw new CatalogParamsError("Codex session cannot be continued in its current state");
|
|
1701
|
-
}
|
|
1702
|
-
async function readNodeCodexHistory(params) {
|
|
1703
|
-
const page = parseTranscriptPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
1704
|
-
nodeId: params.nodeId,
|
|
1705
|
-
command: CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
1706
|
-
params: {
|
|
1707
|
-
agentId: params.agentId,
|
|
1708
|
-
threadId: params.record.threadId,
|
|
1709
|
-
limit: 50
|
|
1710
|
-
},
|
|
1711
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1712
|
-
scopes: ["operator.write"]
|
|
1713
|
-
})));
|
|
1714
|
-
const thread = {
|
|
1715
|
-
id: params.record.threadId,
|
|
1716
|
-
createdAt: params.record.createdAt ?? 0,
|
|
1717
|
-
modelProvider: params.record.modelProvider ?? "openai",
|
|
1718
|
-
projectId: null,
|
|
1719
|
-
turns: page.data.toReversed()
|
|
1720
|
-
};
|
|
1721
|
-
return {
|
|
1722
|
-
thread,
|
|
1723
|
-
throughTurnId: codexLastTerminalTurnId(thread, (value) => boundedCatalogString(value, 256)) ?? null
|
|
1724
|
-
};
|
|
1725
|
-
}
|
|
1726
|
-
async function continueNodeCodexSessionInner(params) {
|
|
1727
|
-
const { nodeId } = await requireNodeForCodexContinue({
|
|
1728
|
-
runtime: params.api.runtime,
|
|
1729
|
-
hostId: params.hostId
|
|
1730
|
-
});
|
|
1731
|
-
const record = await resolveNodeCodexRecord({
|
|
1732
|
-
agentId: params.agentId,
|
|
1733
|
-
runtime: params.api.runtime,
|
|
1734
|
-
nodeId,
|
|
1735
|
-
threadId: params.threadId
|
|
1736
|
-
});
|
|
1737
|
-
requireContinuableNodeRecord(record);
|
|
1738
|
-
const existing = findNodeAdoptedSessionEntry({
|
|
1739
|
-
agentId: params.agentId,
|
|
1740
|
-
config: params.config,
|
|
1741
|
-
runtime: params.api.runtime,
|
|
1742
|
-
hostId: params.hostId,
|
|
1743
|
-
threadId: params.threadId,
|
|
1744
|
-
includeInitializing: true
|
|
1745
|
-
});
|
|
1746
|
-
let adopted;
|
|
1747
|
-
let disposition;
|
|
1748
|
-
if (existing) {
|
|
1749
|
-
adopted = existing;
|
|
1750
|
-
disposition = "existing";
|
|
1751
|
-
} else {
|
|
1752
|
-
const history = await readNodeCodexHistory({
|
|
1753
|
-
agentId: params.agentId,
|
|
1754
|
-
runtime: params.api.runtime,
|
|
1755
|
-
nodeId,
|
|
1756
|
-
record
|
|
1757
|
-
});
|
|
1758
|
-
adopted = await createOrReuseNodeAdoptedSession({
|
|
1759
|
-
agentId: params.agentId,
|
|
1760
|
-
api: params.api,
|
|
1761
|
-
config: params.config,
|
|
1762
|
-
hostId: params.hostId,
|
|
1763
|
-
nodeId,
|
|
1764
|
-
record,
|
|
1765
|
-
history
|
|
1766
|
-
});
|
|
1767
|
-
disposition = "forked";
|
|
1768
|
-
}
|
|
1769
|
-
const marker = nodeSessionMarker({
|
|
1770
|
-
hostId: params.hostId,
|
|
1771
|
-
threadId: params.threadId,
|
|
1772
|
-
nodeId
|
|
1773
|
-
});
|
|
1774
|
-
return {
|
|
1775
|
-
sessionKey: adopted.key,
|
|
1776
|
-
disposition,
|
|
1777
|
-
conversationBinding: {
|
|
1778
|
-
summary: "Continue this Codex session on its paired node.",
|
|
1779
|
-
detachHint: "Start a new chat to leave the paired-node Codex session.",
|
|
1780
|
-
data: createCodexCliNodeConversationBindingData({
|
|
1781
|
-
nodeId,
|
|
1782
|
-
sessionId: params.threadId,
|
|
1783
|
-
agentId: adopted.agentId,
|
|
1784
|
-
cwd: record.cwd
|
|
1785
|
-
})
|
|
1786
|
-
},
|
|
1787
|
-
afterConversationBound: async () => await finalizeNodeAdoptedSession({
|
|
1788
|
-
api: params.api,
|
|
1789
|
-
adopted,
|
|
1790
|
-
marker
|
|
1791
|
-
})
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
async function continueNodeCodexSession(params) {
|
|
1795
|
-
if (params.clientScopes?.includes("operator.admin") !== true) throw new CatalogParamsError("continuing a paired-node Codex session requires operator.admin");
|
|
1796
|
-
const nodeId = params.hostId.slice(5).trim();
|
|
1797
|
-
if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
1798
|
-
const agentId = resolveSessionAgentIdsStrict({
|
|
1799
|
-
config: params.config,
|
|
1800
|
-
agentId: params.agentId
|
|
1801
|
-
}).sessionAgentId;
|
|
1802
|
-
const sourceKey = sessionCatalogAdoptedSourceKey(`node:${nodeId}`, params.threadId);
|
|
1803
|
-
const operationKey = sessionCatalogAdoptedSourceKey(agentId, sourceKey);
|
|
1804
|
-
return await continueNodeAdoption({
|
|
1805
|
-
sourceKey: operationKey,
|
|
1806
|
-
findExisting: () => void 0,
|
|
1807
|
-
create: () => runSessionActionExclusive(sourceKey, async () => continueNodeCodexSessionInner({
|
|
1808
|
-
...params,
|
|
1809
|
-
agentId
|
|
1810
|
-
})),
|
|
1811
|
-
complete: async (continued) => continued
|
|
1812
|
-
});
|
|
1813
|
-
}
|
|
1814
|
-
//#endregion
|
|
1815
|
-
//#region extensions/codex/src/session-catalog-terminal.ts
|
|
1816
|
-
const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1";
|
|
1817
|
-
const CODEX_TERMINAL_START_COMMAND = "codex.terminal.start.v1";
|
|
1818
|
-
function createCodexTerminalStartNodeHostCommand() {
|
|
1819
|
-
return {
|
|
1820
|
-
command: CODEX_TERMINAL_START_COMMAND,
|
|
1821
|
-
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1822
|
-
dangerous: false,
|
|
1823
|
-
duplex: true,
|
|
1824
|
-
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1825
|
-
env,
|
|
1826
|
-
strategy: "direct"
|
|
1827
|
-
})),
|
|
1828
|
-
handle: async (paramsJSON, io) => {
|
|
1829
|
-
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1830
|
-
const params = decodeNodePtyStartParams(paramsJSON);
|
|
1831
|
-
const resolution = resolveNodeHostExecutable("codex", { strategy: "direct" });
|
|
1832
|
-
if (!resolution) throw new Error("Codex CLI is unavailable; install codex on this node and reconnect");
|
|
1833
|
-
return JSON.stringify(await runNodePtyCommand({
|
|
1834
|
-
file: resolution.executable,
|
|
1835
|
-
args: params.initialMessage !== void 0 ? ["--", params.initialMessage] : [],
|
|
1836
|
-
cwd: params.cwd,
|
|
1837
|
-
requiredCwd: true,
|
|
1838
|
-
cols: params.cols,
|
|
1839
|
-
rows: params.rows
|
|
1840
|
-
}, io));
|
|
1841
|
-
}
|
|
1842
|
-
};
|
|
1843
|
-
}
|
|
1844
|
-
function resolveCodexCatalogTerminalHome(sources) {
|
|
1845
|
-
const runtimeConfig = sources.getRuntimeConfig();
|
|
1846
|
-
if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
|
|
1847
|
-
const agentDir = sources.source?.agentDir ?? (sources.agentId ? resolveAgentDir$2(runtimeConfig, sources.agentId) : resolveDefaultAgentDir(runtimeConfig));
|
|
1848
|
-
const startOptions = sources.source?.appServer.start ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig: sources.getPluginConfig() }).start;
|
|
1849
|
-
return resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
|
|
1850
|
-
}
|
|
1851
|
-
function resolveLocalCodexTerminalExecutable(env = process.env) {
|
|
1852
|
-
return resolveLocalCodexTerminalResolution(env)?.executable;
|
|
1853
|
-
}
|
|
1854
|
-
function resolveLocalCodexTerminalResolution(env = process.env) {
|
|
1855
|
-
return resolveNodeHostExecutable("codex", {
|
|
1856
|
-
env,
|
|
1857
|
-
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1858
|
-
strategy: "fallback"
|
|
1859
|
-
});
|
|
1860
|
-
}
|
|
1861
|
-
function codexNodeTerminalCapability(node) {
|
|
1862
|
-
const commands = node.invocableCommands ?? node.commands;
|
|
1863
|
-
return {
|
|
1864
|
-
canOpenTerminalCodex: node.connected === true && commands?.includes("codex.terminal.resume.v1") === true,
|
|
1865
|
-
canStartTerminal: node.connected === true && node.invocableCommands?.includes("codex.terminal.start.v1") === true
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
|
|
1869
|
-
return {
|
|
1870
|
-
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1871
|
-
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
1872
|
-
dangerous: false,
|
|
1873
|
-
duplex: true,
|
|
1874
|
-
isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
|
|
1875
|
-
env,
|
|
1876
|
-
pathEnv: env.PATH ?? env.Path ?? "",
|
|
1877
|
-
strategy: "direct"
|
|
1878
|
-
})),
|
|
1879
|
-
handle: async (paramsJSON, io) => {
|
|
1880
|
-
if (!io) throw new Error("Codex terminal command requires duplex transport");
|
|
1881
|
-
const request = bindRequest(paramsJSON);
|
|
1882
|
-
const resume = decodeNodePtyResumeParams(request.paramsJSON, (value) => {
|
|
1883
|
-
if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value)) throw new CatalogParamsError("threadId must be a UUID");
|
|
1884
|
-
return value;
|
|
1885
|
-
});
|
|
1886
|
-
const record = await request.control.requireEligibleThread(resume.threadId);
|
|
1887
|
-
const resolution = resolveNodeHostExecutable("codex", {
|
|
1888
|
-
env: process.env,
|
|
1889
|
-
pathEnv: process.env.PATH ?? process.env.Path ?? "",
|
|
1890
|
-
strategy: "direct"
|
|
1891
|
-
});
|
|
1892
|
-
if (!resolution) throw new Error("Codex CLI is unavailable");
|
|
1893
|
-
return JSON.stringify(await runNodePtyCommand({
|
|
1894
|
-
file: resolution.executable,
|
|
1895
|
-
args: ["resume", resume.threadId],
|
|
1896
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1897
|
-
env: { CODEX_HOME: resolveCodexCatalogTerminalHome({
|
|
1898
|
-
...configSources,
|
|
1899
|
-
agentId: request.agentId
|
|
1900
|
-
}) },
|
|
1901
|
-
cols: resume.cols,
|
|
1902
|
-
rows: resume.rows
|
|
1903
|
-
}, io));
|
|
1904
|
-
}
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
async function resolveNodeCatalogEligibleThread(params) {
|
|
1908
|
-
let cursor;
|
|
1909
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
1910
|
-
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
1911
|
-
const raw = await params.runtime.nodes.invoke({
|
|
1912
|
-
nodeId: params.nodeId,
|
|
1913
|
-
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
1914
|
-
params: {
|
|
1915
|
-
agentId: params.agentId,
|
|
1916
|
-
limit: 100,
|
|
1917
|
-
...cursor ? { cursor } : {}
|
|
1918
|
-
},
|
|
1919
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
1920
|
-
scopes: ["operator.write"]
|
|
1921
|
-
});
|
|
1922
|
-
const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
|
|
1923
|
-
const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
|
|
1924
|
-
if (record) {
|
|
1925
|
-
if (isInteractiveThreadSource(record.source)) return record;
|
|
1926
|
-
break;
|
|
1927
|
-
}
|
|
1928
|
-
const nextCursor = page.nextCursor?.trim();
|
|
1929
|
-
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
1930
|
-
seenCursors.add(nextCursor);
|
|
1931
|
-
cursor = nextCursor;
|
|
1932
|
-
}
|
|
1933
|
-
throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
|
|
1934
|
-
}
|
|
1935
|
-
async function openCodexCatalogTerminal(params) {
|
|
1936
|
-
const title = `codex resume ${params.threadId.slice(0, 8)}…`;
|
|
1937
|
-
if (params.hostId === "gateway:local" || params.hostId.startsWith(`gateway:local:`)) {
|
|
1938
|
-
const record = await params.control.requireEligibleThread(params.threadId);
|
|
1939
|
-
const resolution = resolveLocalCodexTerminalResolution();
|
|
1940
|
-
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable");
|
|
1941
|
-
return {
|
|
1942
|
-
kind: "local",
|
|
1943
|
-
argv: [
|
|
1944
|
-
resolution.executable,
|
|
1945
|
-
"resume",
|
|
1946
|
-
params.threadId
|
|
1947
|
-
],
|
|
1948
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1949
|
-
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1950
|
-
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1951
|
-
title
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
if (!params.hostId.startsWith("node:")) throw new CatalogParamsError("hostId is invalid");
|
|
1955
|
-
const nodeId = params.hostId.slice(5);
|
|
1956
|
-
if (!(await params.api.runtime.nodes.list()).nodes.find((candidate) => {
|
|
1957
|
-
const commands = candidate.invocableCommands ?? candidate.commands;
|
|
1958
|
-
return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("codex.appServer.threads.list.v1") === true && commands.includes("codex.terminal.resume.v1");
|
|
1959
|
-
})) throw new CatalogParamsError("paired-node Codex terminal is unavailable");
|
|
1960
|
-
const record = await resolveNodeCatalogEligibleThread({
|
|
1961
|
-
agentId: params.agentId,
|
|
1962
|
-
runtime: params.api.runtime,
|
|
1963
|
-
nodeId,
|
|
1964
|
-
threadId: params.threadId,
|
|
1965
|
-
parseCatalogPage: params.parseCatalogPage
|
|
1966
|
-
});
|
|
1967
|
-
return {
|
|
1968
|
-
kind: "node",
|
|
1969
|
-
nodeId,
|
|
1970
|
-
command: CODEX_TERMINAL_RESUME_COMMAND,
|
|
1971
|
-
paramsJSON: JSON.stringify({
|
|
1972
|
-
agentId: params.agentId,
|
|
1973
|
-
threadId: params.threadId
|
|
1974
|
-
}),
|
|
1975
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1976
|
-
title
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
1979
|
-
async function startCodexCatalogTerminal(params) {
|
|
1980
|
-
if (params.nodeId) return {
|
|
1981
|
-
kind: "node",
|
|
1982
|
-
nodeId: params.nodeId,
|
|
1983
|
-
command: CODEX_TERMINAL_START_COMMAND,
|
|
1984
|
-
paramsJSON: JSON.stringify({
|
|
1985
|
-
cwd: params.cwd,
|
|
1986
|
-
initialMessage: params.initialMessage
|
|
1987
|
-
}),
|
|
1988
|
-
cwd: params.cwd,
|
|
1989
|
-
title: "codex"
|
|
1990
|
-
};
|
|
1991
|
-
const resolution = resolveLocalCodexTerminalResolution();
|
|
1992
|
-
if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable; install Codex or add codex to PATH, then try again");
|
|
1993
|
-
return {
|
|
1994
|
-
kind: "local",
|
|
1995
|
-
argv: [resolution.executable, ...params.initialMessage !== void 0 ? ["--", params.initialMessage] : []],
|
|
1996
|
-
cwd: params.cwd,
|
|
1997
|
-
env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
|
|
1998
|
-
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1999
|
-
title: "codex"
|
|
2000
|
-
};
|
|
2001
|
-
}
|
|
2002
|
-
//#endregion
|
|
2003
|
-
//#region extensions/codex/src/session-catalog-transcript-item.ts
|
|
2004
|
-
const CODEX_MESSAGE_TYPES = /* @__PURE__ */ new Map([
|
|
2005
|
-
["userMessage", "userMessage"],
|
|
2006
|
-
["agentMessage", "agentMessage"],
|
|
2007
|
-
["reasoning", "reasoning"]
|
|
2008
|
-
]);
|
|
2009
|
-
const CODEX_TOOL_TYPES = /* @__PURE__ */ new Set([
|
|
2010
|
-
"commandExecution",
|
|
2011
|
-
"fileChange",
|
|
2012
|
-
"mcpToolCall",
|
|
2013
|
-
"dynamicToolCall",
|
|
2014
|
-
"collabAgentToolCall",
|
|
2015
|
-
"webSearch",
|
|
2016
|
-
"imageView",
|
|
2017
|
-
"imageGeneration"
|
|
2018
|
-
]);
|
|
2019
|
-
function toGenericTranscriptItem(item) {
|
|
2020
|
-
let type = CODEX_MESSAGE_TYPES.get(item.type);
|
|
2021
|
-
if (!type && CODEX_TOOL_TYPES.has(item.type)) type = item.result !== void 0 || Boolean(item.aggregatedOutput) ? "toolResult" : "toolCall";
|
|
2022
|
-
type ??= "other";
|
|
2023
|
-
const fallback = item.title ?? item.name ?? item.tool ?? item.command ?? item.query ?? void 0;
|
|
2024
|
-
const resultText = item.aggregatedOutput || (item.result === void 0 ? void 0 : JSON.stringify(item.result, null, 2));
|
|
2025
|
-
const changesText = Array.isArray(item.changes) ? item.changes.map((change) => `${change.kind}: ${change.path}`).join("\n") || void 0 : void 0;
|
|
2026
|
-
const text = item.type === "userMessage" ? projectCodexUserItemText(item) : item.text || resultText || changesText || fallback;
|
|
2027
|
-
return {
|
|
2028
|
-
id: item.id,
|
|
2029
|
-
type,
|
|
2030
|
-
...text ? { text } : {},
|
|
2031
|
-
raw: item
|
|
2032
|
-
};
|
|
2033
|
-
}
|
|
2034
|
-
//#endregion
|
|
2035
|
-
//#region extensions/codex/src/session-catalog-transcript.ts
|
|
2036
|
-
const TURN_ITEM_CURSOR_PREFIX = "turn-item:";
|
|
2037
|
-
const transcriptPageSchema = z.strictObject({
|
|
2038
|
-
items: z.array(z.strictObject({
|
|
2039
|
-
id: z.string(),
|
|
2040
|
-
type: z.enum([
|
|
2041
|
-
"userMessage",
|
|
2042
|
-
"agentMessage",
|
|
2043
|
-
"reasoning",
|
|
2044
|
-
"toolCall",
|
|
2045
|
-
"toolResult",
|
|
2046
|
-
"other"
|
|
2047
|
-
]),
|
|
2048
|
-
text: z.string().optional(),
|
|
2049
|
-
raw: z.record(z.string(), z.json()).optional(),
|
|
2050
|
-
truncated: z.boolean().optional()
|
|
2051
|
-
})),
|
|
2052
|
-
nextCursor: z.string().optional()
|
|
2053
|
-
});
|
|
2054
|
-
function parseCodexCatalogTranscriptPage(value) {
|
|
2055
|
-
return transcriptPageSchema.parse(value);
|
|
2056
|
-
}
|
|
2057
|
-
function encodeTurnItemCursor(turnCursor, itemId) {
|
|
2058
|
-
return TURN_ITEM_CURSOR_PREFIX + Buffer.from(JSON.stringify([turnCursor, itemId])).toString("base64url");
|
|
2059
|
-
}
|
|
2060
|
-
function decodeTurnItemCursor(cursor) {
|
|
2061
|
-
if (!cursor?.startsWith(TURN_ITEM_CURSOR_PREFIX)) return { turnCursor: cursor };
|
|
2062
|
-
let value;
|
|
2063
|
-
try {
|
|
2064
|
-
value = JSON.parse(Buffer.from(cursor.slice(10), "base64url").toString());
|
|
2065
|
-
} catch {
|
|
2066
|
-
throw new CatalogParamsError("invalid Codex transcript item cursor");
|
|
2067
|
-
}
|
|
2068
|
-
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "string" || !value[0] || typeof value[1] !== "string" || !value[1]) throw new CatalogParamsError("invalid Codex transcript item cursor");
|
|
2069
|
-
return {
|
|
2070
|
-
turnCursor: value[0],
|
|
2071
|
-
itemId: value[1]
|
|
2072
|
-
};
|
|
2073
|
-
}
|
|
2074
|
-
function projectTranscriptPage(items, limit) {
|
|
2075
|
-
const projected = items.map(toGenericTranscriptItem);
|
|
2076
|
-
const page = sessionCatalogPaging.boundTranscriptPage(projected.toReversed(), limit, 0).items;
|
|
2077
|
-
for (const [index, item] of page.entries()) if (item.text !== projected[index]?.text && projected[index]?.text) item.truncated = true;
|
|
2078
|
-
return page;
|
|
2079
|
-
}
|
|
2080
|
-
function pageFitsNodeTransport(page) {
|
|
2081
|
-
return Buffer.byteLength(JSON.stringify({ payloadJSON: JSON.stringify(page) }), "utf8") <= MAX_TRANSCRIPT_PAGE_BYTES;
|
|
2082
|
-
}
|
|
2083
|
-
/** The legacy API can anchor a turn, but cannot continue within that turn. */
|
|
2084
|
-
async function readLegacyCodexTranscriptPage(readTurns, request) {
|
|
2085
|
-
const { turnCursor, itemId } = decodeTurnItemCursor(request.cursor);
|
|
2086
|
-
const page = parseTranscriptPage(await readTurns({
|
|
2087
|
-
threadId: request.threadId,
|
|
2088
|
-
limit: 1,
|
|
2089
|
-
sortDirection: "desc",
|
|
2090
|
-
itemsView: "full",
|
|
2091
|
-
...turnCursor ? { cursor: turnCursor } : {}
|
|
2092
|
-
}));
|
|
2093
|
-
const source = page.data.flatMap((turn) => turn.items.toReversed());
|
|
2094
|
-
const anchorIndex = itemId ? source.findIndex((item) => item.id === itemId) : -1;
|
|
2095
|
-
if (itemId && anchorIndex < 0) throw new CatalogParamsError("Codex transcript changed; refresh the session before loading older items");
|
|
2096
|
-
const remaining = source.slice(anchorIndex + 1);
|
|
2097
|
-
const items = projectTranscriptPage(remaining, request.limit);
|
|
2098
|
-
const result = () => {
|
|
2099
|
-
const lastSource = remaining[items.length - 1];
|
|
2100
|
-
const partial = lastSource !== void 0 && items.length < remaining.length;
|
|
2101
|
-
const nativeCursor = partial ? page.backwardsCursor : page.nextCursor;
|
|
2102
|
-
const cursor = nativeCursor ? partial ? encodeTurnItemCursor(nativeCursor, lastSource.id) : nativeCursor : void 0;
|
|
2103
|
-
if (partial && !cursor) throw new Error("Codex app-server did not provide a transcript continuation anchor");
|
|
2104
|
-
return {
|
|
2105
|
-
items,
|
|
2106
|
-
...cursor ? { nextCursor: cursor } : {}
|
|
2107
|
-
};
|
|
2108
|
-
};
|
|
2109
|
-
let bounded = result();
|
|
2110
|
-
while (!pageFitsNodeTransport(bounded)) {
|
|
2111
|
-
if (items.length <= 1) throw new Error("Codex transcript item exceeds the safe response size");
|
|
2112
|
-
items.pop();
|
|
2113
|
-
bounded = result();
|
|
2114
|
-
}
|
|
2115
|
-
return bounded;
|
|
2116
|
-
}
|
|
2117
|
-
/** Uses the native store's item cursor whenever that store supports item history. */
|
|
2118
|
-
async function readCodexCatalogTranscriptPage(control, request) {
|
|
2119
|
-
if ((await control.requireEligibleThread(request.threadId)).historyMode !== "paginated") return readLegacyCodexTranscriptPage((params) => control.listTurnPage(params), request);
|
|
2120
|
-
let limit = request.limit;
|
|
2121
|
-
for (;;) {
|
|
2122
|
-
const page = await control.listItemPage({
|
|
2123
|
-
threadId: request.threadId,
|
|
2124
|
-
limit,
|
|
2125
|
-
sortDirection: "desc",
|
|
2126
|
-
...request.cursor ? { cursor: request.cursor } : {}
|
|
2127
|
-
});
|
|
2128
|
-
const nextCursor = readControlCursor(page.nextCursor, "transcript next response");
|
|
2129
|
-
const items = projectTranscriptPage(page.data.map(({ item }) => item), limit);
|
|
2130
|
-
const result = {
|
|
2131
|
-
items,
|
|
2132
|
-
...nextCursor ? { nextCursor } : {}
|
|
2133
|
-
};
|
|
2134
|
-
const fittingCount = items.length - (pageFitsNodeTransport(result) ? 0 : 1);
|
|
2135
|
-
if (fittingCount === page.data.length) return result;
|
|
2136
|
-
if (fittingCount < 1) throw new Error("Codex transcript item exceeds the safe response size");
|
|
2137
|
-
limit = fittingCount;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
//#endregion
|
|
2141
|
-
//#region extensions/codex/src/session-catalog-listing.ts
|
|
2142
|
-
async function listVisiblePage(params) {
|
|
2143
|
-
const excluded = params.excludedThreadIds;
|
|
2144
|
-
const sessions = [];
|
|
2145
|
-
let cursor = params.cursor;
|
|
2146
|
-
let nextCursor;
|
|
2147
|
-
let backwardsCursor;
|
|
2148
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
2149
|
-
for (let pageIndex = 0; pageIndex < 20; pageIndex += 1) {
|
|
2150
|
-
params.signal?.throwIfAborted();
|
|
2151
|
-
let excludedFromPage = false;
|
|
2152
|
-
const rawPage = await params.control.listPage({
|
|
2153
|
-
limit: params.limit - sessions.length,
|
|
2154
|
-
...cursor ? { cursor } : {},
|
|
2155
|
-
...params.searchTerm ? { searchTerm: params.searchTerm } : {},
|
|
2156
|
-
...params.cwd ? { cwd: params.cwd } : {}
|
|
2157
|
-
});
|
|
2158
|
-
params.signal?.throwIfAborted();
|
|
2159
|
-
const page = filterCatalogPageByTitle(parseCatalogPage(rawPage), params.searchTerm);
|
|
2160
|
-
if (pageIndex === 0) backwardsCursor = page.backwardsCursor;
|
|
2161
|
-
for (const managed of rawPage.managedThreads ?? []) {
|
|
2162
|
-
excludedFromPage = true;
|
|
2163
|
-
params.signal?.throwIfAborted();
|
|
2164
|
-
await params.onExcludedThread?.(managed);
|
|
2165
|
-
}
|
|
2166
|
-
for (const session of page.sessions) {
|
|
2167
|
-
if (!excluded?.has(session.threadId)) {
|
|
2168
|
-
sessions.push(session);
|
|
2169
|
-
continue;
|
|
2170
|
-
}
|
|
2171
|
-
excludedFromPage = true;
|
|
2172
|
-
params.signal?.throwIfAborted();
|
|
2173
|
-
await params.onExcludedThread?.({ threadId: session.threadId });
|
|
2174
|
-
}
|
|
2175
|
-
nextCursor = page.nextCursor;
|
|
2176
|
-
if (!nextCursor || sessions.length >= params.limit || !excludedFromPage) break;
|
|
2177
|
-
if (seenCursors.has(nextCursor)) throw new Error("Codex session catalog returned a repeated exclusion cursor");
|
|
2178
|
-
seenCursors.add(nextCursor);
|
|
2179
|
-
cursor = nextCursor;
|
|
2180
|
-
}
|
|
2181
|
-
return {
|
|
2182
|
-
sessions: sessions.slice(0, params.limit),
|
|
2183
|
-
...nextCursor ? { nextCursor } : {},
|
|
2184
|
-
...backwardsCursor ? { backwardsCursor } : {}
|
|
2185
|
-
};
|
|
2186
|
-
}
|
|
2187
|
-
async function listGatewayHost(params) {
|
|
2188
|
-
const hostId = params.source?.hostId ?? "gateway:local";
|
|
2189
|
-
const label = params.source?.label ?? "Local Codex";
|
|
2190
|
-
const sourceHomeId = params.source?.sourceHomeId ?? "gateway:local";
|
|
2191
|
-
try {
|
|
2192
|
-
const page = await listVisiblePage({
|
|
2193
|
-
control: params.control,
|
|
2194
|
-
cursor: params.query.cursors?.[hostId],
|
|
2195
|
-
excludedThreadIds: params.excludedThreadIds,
|
|
2196
|
-
limit: params.query.limitPerHost,
|
|
2197
|
-
onExcludedThread: params.onExcludedThread,
|
|
2198
|
-
searchTerm: params.query.search,
|
|
2199
|
-
signal: params.signal
|
|
2200
|
-
});
|
|
2201
|
-
params.signal?.throwIfAborted();
|
|
2202
|
-
const adoptedSessions = await listAdoptedSessionEntries({
|
|
2203
|
-
agentId: params.agentId,
|
|
2204
|
-
bindingStore: params.bindingStore,
|
|
2205
|
-
config: params.config,
|
|
2206
|
-
runtime: params.runtime,
|
|
2207
|
-
sessionEntries: params.sessionEntries
|
|
2208
|
-
});
|
|
2209
|
-
return {
|
|
2210
|
-
hostId,
|
|
2211
|
-
label,
|
|
2212
|
-
kind: "gateway",
|
|
2213
|
-
connected: true,
|
|
2214
|
-
...page,
|
|
2215
|
-
sessions: page.sessions.map((session) => {
|
|
2216
|
-
const adopted = adoptedSessions.get(sessionCatalogAdoptedSourceKey(sourceHomeId, session.threadId)) ?? (hostId === "gateway:local" ? adoptedSessions.get(sessionCatalogAdoptedSourceKey("gateway:local", session.threadId)) : void 0);
|
|
2217
|
-
const sourced = params.source ? Object.assign({}, session, { sourceHomeId: params.source.sourceHomeId }) : session;
|
|
2218
|
-
return adopted ? Object.assign({}, sourced, { sessionKey: adopted.key }) : sourced;
|
|
2219
|
-
})
|
|
2220
|
-
};
|
|
2221
|
-
} catch (error) {
|
|
2222
|
-
return {
|
|
2223
|
-
hostId,
|
|
2224
|
-
label,
|
|
2225
|
-
kind: "gateway",
|
|
2226
|
-
connected: false,
|
|
2227
|
-
sessions: [],
|
|
2228
|
-
error: catalogError("APP_SERVER_UNAVAILABLE", error)
|
|
2229
|
-
};
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
/** Lists Gateway-local and paired-node Codex sessions with per-host failures. */
|
|
2233
|
-
async function listCodexSessionCatalog(params) {
|
|
2234
|
-
const agentId = resolveSessionAgentIdsStrict({
|
|
2235
|
-
config: params.config ?? {},
|
|
2236
|
-
agentId: params.agentId
|
|
2237
|
-
}).sessionAgentId;
|
|
2238
|
-
const query = readGatewayParams(params.query);
|
|
2239
|
-
const requestedHostIds = query.hostIds ? new Set(query.hostIds) : void 0;
|
|
2240
|
-
const localSources = params.localHomes?.filter((source) => !requestedHostIds || requestedHostIds.has(source.hostId)) ?? (params.includeLocal !== false && (!requestedHostIds || requestedHostIds.has("gateway:local")) ? [void 0] : []);
|
|
2241
|
-
const managedThreads = await params.bindingStore.managedThreads?.snapshot();
|
|
2242
|
-
params.signal?.throwIfAborted();
|
|
2243
|
-
const fallbackSource = params.control.homesForAgent(agentId)[0];
|
|
2244
|
-
const localHosts = localSources.map((source) => (() => {
|
|
2245
|
-
const ownershipSource = source ?? fallbackSource;
|
|
2246
|
-
const managedThreadIds = ownershipSource ? managedThreads?.get(ownershipSource.sourceHomeId) : void 0;
|
|
2247
|
-
return listGatewayHost({
|
|
2248
|
-
agentId,
|
|
2249
|
-
bindingStore: params.bindingStore,
|
|
2250
|
-
config: params.config,
|
|
2251
|
-
control: params.control.forRequest(agentId, ownershipSource),
|
|
2252
|
-
query,
|
|
2253
|
-
runtime: params.runtime,
|
|
2254
|
-
sessionEntries: params.sessionEntries,
|
|
2255
|
-
signal: params.signal,
|
|
2256
|
-
excludedThreadIds: managedThreadIds,
|
|
2257
|
-
...ownershipSource && params.bindingStore.managedThreads ? { onExcludedThread: async ({ threadId, rolloutPath }) => {
|
|
2258
|
-
if (!managedThreadIds?.has(threadId)) await params.bindingStore.managedThreads?.mark({
|
|
2259
|
-
sourceHomeId: ownershipSource.sourceHomeId,
|
|
2260
|
-
threadId,
|
|
2261
|
-
...rolloutPath ? { rolloutPath } : {}
|
|
2262
|
-
});
|
|
2263
|
-
} } : {},
|
|
2264
|
-
...source ? { source } : {}
|
|
2265
|
-
});
|
|
2266
|
-
})());
|
|
2267
|
-
for (const host of localHosts) publishSessionCatalogHost(params, host);
|
|
2268
|
-
if (!(!requestedHostIds || query.hostIds?.some((hostId) => hostId.startsWith("node:")))) return { hosts: await Promise.all(localHosts) };
|
|
2269
|
-
let nodes;
|
|
2270
|
-
try {
|
|
2271
|
-
nodes = (await (params.listNodes?.() ?? params.runtime.nodes.list())).nodes.filter((node) => node.gatewayLocal !== true && (node.commands?.includes("codex.appServer.threads.list.v1") || codexNodeTerminalCapability(node).canStartTerminal) && (!requestedHostIds || requestedHostIds.has(`node:${node.nodeId}`))).slice(0, 100 - localHosts.length);
|
|
2272
|
-
} catch (error) {
|
|
2273
|
-
const registryHost = {
|
|
2274
|
-
hostId: "node:registry",
|
|
2275
|
-
label: "Paired nodes",
|
|
2276
|
-
kind: "node",
|
|
2277
|
-
connected: false,
|
|
2278
|
-
canStartTerminal: false,
|
|
2279
|
-
sessions: [],
|
|
2280
|
-
error: catalogError("NODE_LIST_FAILED", error)
|
|
2281
|
-
};
|
|
2282
|
-
params.onHost?.(registryHost);
|
|
2283
|
-
return { hosts: [...await Promise.all(localHosts), registryHost] };
|
|
2284
|
-
}
|
|
2285
|
-
params.signal?.throwIfAborted();
|
|
2286
|
-
const adoptedNodeSessions = listNodeAdoptedSessionEntries({
|
|
2287
|
-
agentId,
|
|
2288
|
-
config: params.config,
|
|
2289
|
-
runtime: params.runtime,
|
|
2290
|
-
sessionEntries: params.sessionEntries
|
|
2291
|
-
});
|
|
2292
|
-
const nodeHosts = nodes.toSorted(compareNodeLabels).map((node) => listPairedNode({
|
|
2293
|
-
agentId,
|
|
2294
|
-
runtime: params.runtime,
|
|
2295
|
-
node,
|
|
2296
|
-
query,
|
|
2297
|
-
adoptedSessions: adoptedNodeSessions,
|
|
2298
|
-
terminalCapabilities: codexNodeTerminalCapability(node),
|
|
2299
|
-
waitUntil: params.waitUntil,
|
|
2300
|
-
signal: params.signal,
|
|
2301
|
-
...params.onHost ? { onHost: params.onHost } : {}
|
|
2302
|
-
}));
|
|
2303
|
-
return { hosts: await Promise.all([...localHosts, ...nodeHosts]) };
|
|
2304
|
-
}
|
|
2305
|
-
/** Builds the node-local read-only Codex app-server catalog command. */
|
|
2306
|
-
function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources, bindingStore) {
|
|
2307
|
-
const bindRequest = (paramsJSON) => {
|
|
2308
|
-
const parsed = parseJsonParams(paramsJSON);
|
|
2309
|
-
if (!isRecord(parsed)) throw new CatalogParamsError("Codex session catalog parameters must be an object");
|
|
2310
|
-
const requestedAgentId = readBoundedOptionalString(parsed, "agentId", 256);
|
|
2311
|
-
const config = configSources.getRuntimeConfig() ?? {};
|
|
2312
|
-
const agentId = resolveSessionAgentIdsStrict({
|
|
2313
|
-
config,
|
|
2314
|
-
agentId: requestedAgentId
|
|
2315
|
-
}).sessionAgentId;
|
|
2316
|
-
if (!listAgentIds$1(config).includes(agentId)) throw new CatalogParamsError(`unknown Codex session catalog agent: ${agentId}`);
|
|
2317
|
-
const request = { ...parsed };
|
|
2318
|
-
delete request.agentId;
|
|
2319
|
-
const source = controlFactory.homesForAgent(agentId)[0];
|
|
2320
|
-
return {
|
|
2321
|
-
agentId,
|
|
2322
|
-
control: controlFactory.forRequest(agentId, source),
|
|
2323
|
-
sourceHomeId: source?.sourceHomeId,
|
|
2324
|
-
params: request,
|
|
2325
|
-
paramsJSON: JSON.stringify(request)
|
|
2326
|
-
};
|
|
2327
|
-
};
|
|
2328
|
-
const commands = [
|
|
2329
|
-
{
|
|
2330
|
-
command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
2331
|
-
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
2332
|
-
dangerous: false,
|
|
2333
|
-
handle: async (paramsJSON) => {
|
|
2334
|
-
const request = bindRequest(paramsJSON);
|
|
2335
|
-
const pageParams = readPageParams(request.params);
|
|
2336
|
-
try {
|
|
2337
|
-
const managedThreads = await bindingStore?.managedThreads?.snapshot();
|
|
2338
|
-
const sourceHomeId = request.sourceHomeId;
|
|
2339
|
-
const managedThreadIds = sourceHomeId ? managedThreads?.get(sourceHomeId) : void 0;
|
|
2340
|
-
const page = await listVisiblePage({
|
|
2341
|
-
control: request.control,
|
|
2342
|
-
cursor: pageParams.cursor,
|
|
2343
|
-
cwd: pageParams.cwd,
|
|
2344
|
-
excludedThreadIds: managedThreadIds,
|
|
2345
|
-
limit: pageParams.limit,
|
|
2346
|
-
...sourceHomeId && bindingStore?.managedThreads ? { onExcludedThread: async ({ threadId, rolloutPath }) => {
|
|
2347
|
-
if (!managedThreadIds?.has(threadId)) await bindingStore.managedThreads?.mark({
|
|
2348
|
-
sourceHomeId,
|
|
2349
|
-
threadId,
|
|
2350
|
-
...rolloutPath ? { rolloutPath } : {}
|
|
2351
|
-
});
|
|
2352
|
-
} } : {},
|
|
2353
|
-
searchTerm: pageParams.searchTerm
|
|
2354
|
-
});
|
|
2355
|
-
return JSON.stringify(page);
|
|
2356
|
-
} catch {
|
|
2357
|
-
throw new Error("Codex app-server catalog is unavailable");
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
},
|
|
2361
|
-
{
|
|
2362
|
-
command: CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
2363
|
-
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
2364
|
-
dangerous: false,
|
|
2365
|
-
handle: async (paramsJSON) => {
|
|
2366
|
-
const request = bindRequest(paramsJSON);
|
|
2367
|
-
const action = readNodeTranscriptParams(request.params);
|
|
2368
|
-
try {
|
|
2369
|
-
await request.control.requireEligibleThread(action.threadId);
|
|
2370
|
-
const page = parseTranscriptPage(await request.control.listTurnPage({
|
|
2371
|
-
threadId: action.threadId,
|
|
2372
|
-
limit: action.limit,
|
|
2373
|
-
sortDirection: "desc",
|
|
2374
|
-
itemsView: "full",
|
|
2375
|
-
...action.cursor ? { cursor: action.cursor } : {}
|
|
2376
|
-
}));
|
|
2377
|
-
return JSON.stringify(page);
|
|
2378
|
-
} catch (error) {
|
|
2379
|
-
if (error instanceof CatalogParamsError) throw error;
|
|
2380
|
-
throw new Error("Codex app-server transcript is unavailable", { cause: error });
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
},
|
|
2384
|
-
{
|
|
2385
|
-
command: CODEX_CATALOG_TRANSCRIPT_READ_COMMAND,
|
|
2386
|
-
cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
|
|
2387
|
-
dangerous: false,
|
|
2388
|
-
handle: async (paramsJSON) => {
|
|
2389
|
-
const request = bindRequest(paramsJSON);
|
|
2390
|
-
const action = readNodeTranscriptParams(request.params);
|
|
2391
|
-
try {
|
|
2392
|
-
return JSON.stringify(await readCodexCatalogTranscriptPage(request.control, action));
|
|
2393
|
-
} catch (error) {
|
|
2394
|
-
if (error instanceof CatalogParamsError) throw error;
|
|
2395
|
-
throw new Error("Codex app-server transcript is unavailable", { cause: error });
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
},
|
|
2399
|
-
createCodexTerminalNodeHostCommand(bindRequest, configSources),
|
|
2400
|
-
createCodexTerminalStartNodeHostCommand()
|
|
2401
|
-
];
|
|
2402
|
-
return process.env.OPENCLAW_NODE_EXEC_HOST?.trim().toLowerCase() === "app" ? commands.filter(({ command }) => command !== CODEX_CATALOG_TRANSCRIPT_READ_COMMAND) : commands;
|
|
2403
|
-
}
|
|
2404
|
-
function readNodeTranscriptParams(value) {
|
|
2405
|
-
if (!isRecord(value)) throw new CatalogParamsError("Codex session read parameters must be an object");
|
|
2406
|
-
requireOnlyKeys(value, /* @__PURE__ */ new Set([
|
|
2407
|
-
"threadId",
|
|
2408
|
-
"cursor",
|
|
2409
|
-
"limit"
|
|
2410
|
-
]));
|
|
2411
|
-
const threadId = readBoundedOptionalString(value, "threadId", 256);
|
|
2412
|
-
if (!threadId) throw new CatalogParamsError("threadId is required");
|
|
2413
|
-
const cursor = readBoundedOptionalString(value, "cursor", MAX_CURSOR_LENGTH);
|
|
2414
|
-
return {
|
|
2415
|
-
threadId,
|
|
2416
|
-
limit: readBoundedLimit(value.limit, "limit", 20, 50),
|
|
2417
|
-
...cursor ? { cursor } : {}
|
|
2418
|
-
};
|
|
2419
|
-
}
|
|
2420
|
-
function readBoundedLimit(value, key, fallback, max) {
|
|
2421
|
-
if (value === void 0) return fallback;
|
|
2422
|
-
if (!Number.isInteger(value) || value < 1 || value > max) throw new CatalogParamsError(`${key} must be an integer from 1 to ${max}`);
|
|
2423
|
-
return value;
|
|
2424
|
-
}
|
|
2425
|
-
/** Reads the persisted transcript for a Gateway-local or paired-node Codex session. */
|
|
2426
|
-
async function readCodexSessionTranscript(params) {
|
|
2427
|
-
const cursor = readControlCursor(params.cursor, "transcript request");
|
|
2428
|
-
const limit = readBoundedLimit(params.limit, "limit", 20, 50);
|
|
2429
|
-
if (params.source || params.hostId === "gateway:local") {
|
|
2430
|
-
const page = await readCodexCatalogTranscriptPage(params.control, {
|
|
2431
|
-
threadId: params.threadId,
|
|
2432
|
-
limit,
|
|
2433
|
-
cursor
|
|
2434
|
-
});
|
|
2435
|
-
return {
|
|
2436
|
-
hostId: params.hostId,
|
|
2437
|
-
label: params.source?.label ?? "Local Codex",
|
|
2438
|
-
threadId: params.threadId,
|
|
2439
|
-
...page
|
|
2440
|
-
};
|
|
2441
|
-
}
|
|
2442
|
-
const nodeId = params.hostId.slice(5);
|
|
2443
|
-
const node = (await params.runtime.nodes.list()).nodes.find((candidate) => candidate.nodeId === nodeId && candidate.connected === true && candidate.commands?.includes("codex.appServer.thread.turns.list.v1"));
|
|
2444
|
-
if (!node) throw new CatalogParamsError("paired-node Codex session host is offline or unavailable");
|
|
2445
|
-
const invoke = async (command, request) => unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
|
|
2446
|
-
nodeId,
|
|
2447
|
-
command,
|
|
2448
|
-
params: {
|
|
2449
|
-
agentId: params.agentId,
|
|
2450
|
-
threadId: params.threadId,
|
|
2451
|
-
...request
|
|
2452
|
-
},
|
|
2453
|
-
timeoutMs: NODE_INVOKE_TIMEOUT_MS,
|
|
2454
|
-
scopes: ["operator.write"]
|
|
2455
|
-
}));
|
|
2456
|
-
const page = node.commands?.includes("codex.sessionCatalog.transcript.read.v1") ? parseCodexCatalogTranscriptPage(await invoke(CODEX_CATALOG_TRANSCRIPT_READ_COMMAND, {
|
|
2457
|
-
cursor,
|
|
2458
|
-
limit
|
|
2459
|
-
})) : await readLegacyCodexTranscriptPage(async ({ cursor: turnCursor, limit: turnLimit }) => parseTranscriptPage(await invoke(CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND, {
|
|
2460
|
-
cursor: turnCursor,
|
|
2461
|
-
limit: turnLimit
|
|
2462
|
-
})), {
|
|
2463
|
-
threadId: params.threadId,
|
|
2464
|
-
cursor,
|
|
2465
|
-
limit
|
|
2466
|
-
});
|
|
2467
|
-
return {
|
|
2468
|
-
hostId: params.hostId,
|
|
2469
|
-
label: nodeLabel(node),
|
|
2470
|
-
threadId: params.threadId,
|
|
2471
|
-
...page
|
|
2472
|
-
};
|
|
2473
|
-
}
|
|
2474
|
-
//#endregion
|
|
2475
|
-
//#region extensions/codex/src/session-upstream-activity.ts
|
|
2476
|
-
const CODEX_UPSTREAM_TURN_LIMIT = 100;
|
|
2477
|
-
function readMarker(probe) {
|
|
2478
|
-
if (!isRecord(probe.marker)) return;
|
|
2479
|
-
const turnId = probe.marker.turnId;
|
|
2480
|
-
if (turnId !== null && typeof turnId !== "string") return;
|
|
2481
|
-
const count = probe.marker.userMessageCount;
|
|
2482
|
-
if (count !== void 0 && (!Number.isSafeInteger(count) || count < 0)) return;
|
|
2483
|
-
return {
|
|
2484
|
-
turnId,
|
|
2485
|
-
...count === void 0 ? {} : { userMessageCount: count }
|
|
2486
|
-
};
|
|
2487
|
-
}
|
|
2488
|
-
function upstreamConnectionFingerprint(probe) {
|
|
2489
|
-
return isRecord(probe.upstreamRef) && typeof probe.upstreamRef.connectionFingerprint === "string" ? probe.upstreamRef.connectionFingerprint : void 0;
|
|
2490
|
-
}
|
|
2491
|
-
function classifyCodexUpstreamTurns(params) {
|
|
2492
|
-
const marker = readMarker(params.probe);
|
|
2493
|
-
if (!marker) return;
|
|
2494
|
-
const newest = params.turns[0];
|
|
2495
|
-
if (!newest?.id) return;
|
|
2496
|
-
const markerIndex = marker.turnId === null ? -1 : params.turns.findIndex((turn) => turn.id === marker.turnId);
|
|
2497
|
-
const candidateTurns = markerIndex < 0 ? params.turns : params.turns.slice(0, markerIndex + 1);
|
|
2498
|
-
const newestUserMessageCount = countUserMessages(newest);
|
|
2499
|
-
if (!(marker.turnId !== newest.id || marker.userMessageCount === void 0 || newestUserMessageCount > marker.userMessageCount)) return;
|
|
2500
|
-
const ownTexts = new Set(params.probe.ownRecentUserTexts);
|
|
2501
|
-
let humanTurns = 0;
|
|
2502
|
-
let occurredAt;
|
|
2503
|
-
for (const turn of candidateTurns) {
|
|
2504
|
-
const userMessages = turn.items.filter((item) => item.type === "userMessage");
|
|
2505
|
-
const alreadySeen = turn.id === marker.turnId ? marker.userMessageCount ?? userMessages.length : 0;
|
|
2506
|
-
for (const item of userMessages.slice(alreadySeen)) {
|
|
2507
|
-
const texts = normalizeUserMessageTexts(item);
|
|
2508
|
-
if (ownTexts.has(texts.join(" ")) || texts.length > 1 && texts.every((text) => ownTexts.has(text))) continue;
|
|
2509
|
-
humanTurns += 1;
|
|
2510
|
-
if (occurredAt === void 0) {
|
|
2511
|
-
const timestampSeconds = turn.completedAt ?? turn.startedAt;
|
|
2512
|
-
occurredAt = typeof timestampSeconds === "number" && Number.isFinite(timestampSeconds) ? timestampSeconds * 1e3 : params.now ?? Date.now();
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
const activityId = `${newest.id}:${newestUserMessageCount}`;
|
|
2517
|
-
return {
|
|
2518
|
-
kind: "activity",
|
|
2519
|
-
sessionKey: params.probe.sessionKey,
|
|
2520
|
-
humanTurns,
|
|
2521
|
-
nextMarker: {
|
|
2522
|
-
turnId: newest.id,
|
|
2523
|
-
userMessageCount: newestUserMessageCount
|
|
2524
|
-
},
|
|
2525
|
-
...humanTurns > 0 ? {
|
|
2526
|
-
occurredAt: occurredAt ?? params.now ?? Date.now(),
|
|
2527
|
-
dedupeId: activityId
|
|
2528
|
-
} : {}
|
|
2529
|
-
};
|
|
2530
|
-
}
|
|
2531
|
-
function countUserMessages(turn) {
|
|
2532
|
-
return turn.items.filter((item) => item.type === "userMessage").length;
|
|
2533
|
-
}
|
|
2534
|
-
function normalizeUserMessageTexts(item) {
|
|
2535
|
-
const typed = item;
|
|
2536
|
-
const contentTexts = typed.content?.filter((input) => input.type === "text").map((input) => input.text.trim().replace(/\s+/g, " ")).filter(Boolean);
|
|
2537
|
-
return contentTexts?.length ? contentTexts : [(typed.text ?? "").trim().replace(/\s+/g, " ")];
|
|
2538
|
-
}
|
|
2539
|
-
async function checkCodexUpstreamActivity(probes, control, resolveThreadId = (probe) => probe.threadId) {
|
|
2540
|
-
return await control.withPinnedConnection(async (pinned) => {
|
|
2541
|
-
const activities = [];
|
|
2542
|
-
for (const probe of probes) {
|
|
2543
|
-
const fingerprint = upstreamConnectionFingerprint(probe);
|
|
2544
|
-
if (probe.upstreamKind !== "codex-app-server" || !fingerprint || fingerprint !== pinned.connectionFingerprint) continue;
|
|
2545
|
-
try {
|
|
2546
|
-
const threadId = resolveThreadId(probe);
|
|
2547
|
-
const page = await pinned.listTurnPage({
|
|
2548
|
-
threadId,
|
|
2549
|
-
limit: CODEX_UPSTREAM_TURN_LIMIT,
|
|
2550
|
-
sortDirection: "desc",
|
|
2551
|
-
itemsView: "full"
|
|
2552
|
-
});
|
|
2553
|
-
const marker = readMarker(probe);
|
|
2554
|
-
if (page.data.length === 0 && marker) {
|
|
2555
|
-
try {
|
|
2556
|
-
await pinned.readThread(threadId, false);
|
|
2557
|
-
} catch (error) {
|
|
2558
|
-
if (isCodexThreadReadMissingError(error, threadId)) activities.push({
|
|
2559
|
-
kind: "missing",
|
|
2560
|
-
sessionKey: probe.sessionKey
|
|
2561
|
-
});
|
|
2562
|
-
}
|
|
2563
|
-
continue;
|
|
2564
|
-
}
|
|
2565
|
-
const activity = classifyCodexUpstreamTurns({
|
|
2566
|
-
probe,
|
|
2567
|
-
turns: page.data
|
|
2568
|
-
});
|
|
2569
|
-
if (activity) activities.push(activity);
|
|
2570
|
-
} catch {}
|
|
2571
|
-
}
|
|
2572
|
-
return activities;
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
function createChecker(params) {
|
|
2576
|
-
const resolveThreadId = (probe) => {
|
|
2577
|
-
const config = params.getRuntimeConfig();
|
|
2578
|
-
const sessionId = params.api.runtime.agent.session.getSessionEntry({
|
|
2579
|
-
agentId: probe.agentId,
|
|
2580
|
-
sessionKey: probe.sessionKey,
|
|
2581
|
-
readConsistency: "latest"
|
|
2582
|
-
})?.sessionId?.trim();
|
|
2583
|
-
if (!sessionId) return probe.threadId;
|
|
2584
|
-
const binding = params.bindingStore.read(sessionBindingIdentity({
|
|
2585
|
-
sessionId,
|
|
2586
|
-
sessionKey: probe.sessionKey,
|
|
2587
|
-
config
|
|
2588
|
-
}));
|
|
2589
|
-
return binding?.connectionScope === "supervision" && binding.supervisionSourceThreadId === probe.threadId ? binding.threadId : probe.threadId;
|
|
2590
|
-
};
|
|
2591
|
-
return async (probes) => {
|
|
2592
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2593
|
-
for (const probe of probes) {
|
|
2594
|
-
const fingerprint = upstreamConnectionFingerprint(probe);
|
|
2595
|
-
if (!fingerprint) continue;
|
|
2596
|
-
const control = params.control.forUpstream(probe.agentId, fingerprint);
|
|
2597
|
-
if (!control) continue;
|
|
2598
|
-
const key = `${probe.agentId}\0${fingerprint}`;
|
|
2599
|
-
const group = groups.get(key) ?? {
|
|
2600
|
-
control,
|
|
2601
|
-
probes: []
|
|
2602
|
-
};
|
|
2603
|
-
group.probes.push(probe);
|
|
2604
|
-
groups.set(key, group);
|
|
2605
|
-
}
|
|
2606
|
-
return (await Promise.all([...groups.values()].map((group) => checkCodexUpstreamActivity(group.probes, group.control, resolveThreadId)))).flat();
|
|
2607
|
-
};
|
|
2608
|
-
}
|
|
2609
|
-
//#endregion
|
|
2610
|
-
//#region extensions/codex/src/session-catalog-homes.ts
|
|
2611
|
-
function existingCatalogHomeCandidates(value, label) {
|
|
2612
|
-
const codexHome = canonicalCodexCatalogHome(value);
|
|
2613
|
-
try {
|
|
2614
|
-
if (!fsSync.statSync(codexHome).isDirectory()) return [];
|
|
2615
|
-
} catch {
|
|
2616
|
-
return [];
|
|
2617
|
-
}
|
|
2618
|
-
return [{
|
|
2619
|
-
codexHome,
|
|
2620
|
-
label: `Local Codex · ${label ?? path.basename(codexHome)}`
|
|
2621
|
-
}];
|
|
2622
|
-
}
|
|
2623
|
-
/** Resolves every local Codex store the operator already owns, without path disclosure. */
|
|
2624
|
-
function resolveCodexCatalogHomes(params) {
|
|
2625
|
-
const { config, env, ownerAgentId, pluginConfig } = params;
|
|
2626
|
-
const ownerAgentDir = resolveAgentDir$2(config, ownerAgentId, env);
|
|
2627
|
-
const configuredHomes = readCodexPluginConfig(pluginConfig).sessionCatalog?.homes ?? [];
|
|
2628
|
-
const base = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
2629
|
-
pluginConfig,
|
|
2630
|
-
env,
|
|
2631
|
-
agentDir: ownerAgentDir,
|
|
2632
|
-
config
|
|
2633
|
-
});
|
|
2634
|
-
const primaryCodexHome = canonicalCodexCatalogHome(resolveCodexAppServerLocalHomeDir(base.start, ownerAgentDir, env));
|
|
2635
|
-
const processUserHome = canonicalCodexCatalogHome(resolveCodexAppServerUserHomeDir(env));
|
|
2636
|
-
const processHomeConfigured = Boolean(env.CODEX_HOME?.trim());
|
|
2637
|
-
const candidates = [{
|
|
2638
|
-
codexHome: primaryCodexHome,
|
|
2639
|
-
label: "Local Codex",
|
|
2640
|
-
usesProcessHomeFallback: base.start.transport === "stdio" && base.start.homeScope === "user" && !processHomeConfigured
|
|
2641
|
-
}];
|
|
2642
|
-
if (base.start.transport === "stdio") {
|
|
2643
|
-
candidates.push({
|
|
2644
|
-
codexHome: processUserHome,
|
|
2645
|
-
label: "Local Codex · user",
|
|
2646
|
-
usesProcessHomeFallback: !processHomeConfigured
|
|
2647
|
-
});
|
|
2648
|
-
const agentIds = listAgentIds$1(config).toSorted((left, right) => left === ownerAgentId ? -1 : right === ownerAgentId ? 1 : left.localeCompare(right));
|
|
2649
|
-
candidates.push(...agentIds.flatMap((agentId) => existingCatalogHomeCandidates(resolveCodexAppServerHomeDir(resolveAgentDir$2(config, agentId, env)), agentId)), ...configuredHomes.flatMap((entry) => {
|
|
2650
|
-
const { path: home, label } = typeof entry === "string" ? { path: entry } : entry;
|
|
2651
|
-
return existingCatalogHomeCandidates(home, label);
|
|
2652
|
-
}));
|
|
2653
|
-
}
|
|
2654
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2655
|
-
const homes = [];
|
|
2656
|
-
for (const candidate of candidates) {
|
|
2657
|
-
if (seen.has(candidate.codexHome)) continue;
|
|
2658
|
-
seen.add(candidate.codexHome);
|
|
2659
|
-
const sourceHomeId = codexCatalogHomeId(candidate.codexHome);
|
|
2660
|
-
const primary = homes.length === 0;
|
|
2661
|
-
homes.push({
|
|
2662
|
-
sourceHomeId,
|
|
2663
|
-
hostId: primary ? CODEX_LOCAL_SESSION_HOST_ID : `${CODEX_LOCAL_SESSION_HOST_ID}:${sourceHomeId}`,
|
|
2664
|
-
label: candidate.label,
|
|
2665
|
-
agentDir: ownerAgentDir,
|
|
2666
|
-
appServer: primary ? base : {
|
|
2667
|
-
...base,
|
|
2668
|
-
start: {
|
|
2669
|
-
...base.start,
|
|
2670
|
-
homeScope: "user",
|
|
2671
|
-
env: {
|
|
2672
|
-
...base.start.env,
|
|
2673
|
-
CODEX_HOME: candidate.codexHome
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
|
-
},
|
|
2677
|
-
...base.connectionClass === "remote" ? {} : { localSessionsRoot: path.join(candidate.codexHome, "sessions") },
|
|
2678
|
-
usesProcessHomeFallback: candidate.usesProcessHomeFallback ?? false
|
|
2679
|
-
});
|
|
2680
|
-
if (homes.length >= 100) break;
|
|
2681
|
-
}
|
|
2682
|
-
return homes;
|
|
2683
|
-
}
|
|
2684
|
-
/** Discovers Codex homes once per immutable Gateway config generation. */
|
|
2685
|
-
function createCodexCatalogHomeResolver(params) {
|
|
2686
|
-
const env = params.env ?? process.env;
|
|
2687
|
-
const homesByConfig = /* @__PURE__ */ new WeakMap();
|
|
2688
|
-
const buildSnapshot = (config) => {
|
|
2689
|
-
const pluginConfig = params.getPluginConfig();
|
|
2690
|
-
const homesByAgent = new Map(listAgentIds$1(config).map((agentId) => [agentId, resolveCodexCatalogHomes({
|
|
2691
|
-
config,
|
|
2692
|
-
pluginConfig,
|
|
2693
|
-
ownerAgentId: agentId,
|
|
2694
|
-
env
|
|
2695
|
-
})]));
|
|
2696
|
-
replaceCodexCatalogConnectionHomes([...homesByAgent.values()].flatMap((homes) => homes.filter((home) => home.appServer.start.transport === "stdio").map((home) => ({
|
|
2697
|
-
agentDir: home.agentDir,
|
|
2698
|
-
fingerprint: buildCodexAppServerConnectionFingerprint(home.appServer, home.agentDir),
|
|
2699
|
-
codexHome: resolveCodexAppServerLocalHomeDir(home.appServer.start, home.agentDir, env)
|
|
2700
|
-
}))));
|
|
2701
|
-
homesByConfig.set(config, homesByAgent);
|
|
2702
|
-
return homesByAgent;
|
|
2703
|
-
};
|
|
2704
|
-
let lastSnapshot = buildSnapshot(params.config);
|
|
2705
|
-
return { forAgent(agentId) {
|
|
2706
|
-
const config = params.getRuntimeConfig();
|
|
2707
|
-
if (!config) return lastSnapshot.get(agentId) ?? [];
|
|
2708
|
-
const cached = homesByConfig.get(config);
|
|
2709
|
-
if (cached) return cached.get(agentId) ?? [];
|
|
2710
|
-
lastSnapshot = buildSnapshot(config);
|
|
2711
|
-
return lastSnapshot.get(agentId) ?? [];
|
|
2712
|
-
} };
|
|
2713
|
-
}
|
|
2714
|
-
//#endregion
|
|
2715
|
-
//#region extensions/codex/src/session-catalog-control.ts
|
|
2716
|
-
const CODEX_SESSION_CATALOG_LIST_TTL_MS = 32e3;
|
|
2717
|
-
const CODEX_SESSION_CATALOG_LIST_CACHE_MAX_ENTRIES = 32;
|
|
2718
|
-
function codexCatalogPageCacheKey(params, agentId, source) {
|
|
2719
|
-
return JSON.stringify([
|
|
2720
|
-
agentId,
|
|
2721
|
-
source?.sourceHomeId ?? null,
|
|
2722
|
-
params.cursor ?? null,
|
|
2723
|
-
params.limit ?? null,
|
|
2724
|
-
params.searchTerm?.trim().toLocaleLowerCase() || null,
|
|
2725
|
-
params.cwd?.trim() || null
|
|
2726
|
-
]);
|
|
2727
|
-
}
|
|
2728
|
-
function createCodexCatalogRequestSnapshot(requestTimeoutMs, request) {
|
|
2729
|
-
return {
|
|
2730
|
-
requestTimeoutMs,
|
|
2731
|
-
listThreads: (params, timeoutMs) => request(CODEX_CONTROL_METHODS.listThreads, params, timeoutMs),
|
|
2732
|
-
listThreadTurns: (params) => request(CODEX_CONTROL_METHODS.listThreadTurns, params),
|
|
2733
|
-
listThreadItems: (params) => request(CODEX_CONTROL_METHODS.listThreadItems, params),
|
|
2734
|
-
forkThread: (params, assertCurrent) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params), void 0, assertCurrent),
|
|
2735
|
-
readThread: async (threadId, includeTurns, timeoutMs) => (await request(CODEX_CONTROL_METHODS.readThread, {
|
|
2736
|
-
threadId,
|
|
2737
|
-
includeTurns
|
|
2738
|
-
}, timeoutMs)).thread,
|
|
2739
|
-
archiveThread: async (threadId, assertCurrent) => {
|
|
2740
|
-
await request(CODEX_CONTROL_METHODS.archiveThread, { threadId }, void 0, assertCurrent);
|
|
2741
|
-
}
|
|
2742
|
-
};
|
|
2743
|
-
}
|
|
2744
|
-
function createCodexSessionCatalogControlFromRequests(params) {
|
|
2745
|
-
return {
|
|
2746
|
-
forkContext: params.forkContext,
|
|
2747
|
-
...params.clientId ? { clientId: params.clientId } : {},
|
|
2748
|
-
...params.connectionFingerprint ? { connectionFingerprint: params.connectionFingerprint } : {},
|
|
2749
|
-
withPinnedConnection: params.withPinnedConnection,
|
|
2750
|
-
async requireEligibleThread(threadId) {
|
|
2751
|
-
const requests = params.createRequestSnapshot();
|
|
2752
|
-
const deadline = params.now() + requests.requestTimeoutMs;
|
|
2753
|
-
const unverified = () => new CatalogParamsError("Codex session eligibility could not be verified. Refresh the catalog and verify the session in its native Codex home before retrying.");
|
|
2754
|
-
const remaining = () => {
|
|
2755
|
-
const timeoutMs = Math.ceil(deadline - params.now());
|
|
2756
|
-
if (timeoutMs <= 0) throw unverified();
|
|
2757
|
-
return timeoutMs;
|
|
2758
|
-
};
|
|
2759
|
-
const verify = async () => {
|
|
2760
|
-
if (params.sourceHomeId && await params.managedThreads?.has(params.sourceHomeId, threadId)) throw unverified();
|
|
2761
|
-
const root = params.localSessionsRoot;
|
|
2762
|
-
const thread = root ? await requests.readThread(threadId, false, remaining()) : void 0;
|
|
2763
|
-
if (root && (!thread || thread.id !== threadId || !isInteractiveThreadSource(thread.source))) throw unverified();
|
|
2764
|
-
let cursor;
|
|
2765
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
2766
|
-
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
2767
|
-
const page = await requests.listThreads({
|
|
2768
|
-
archived: false,
|
|
2769
|
-
limit: 100,
|
|
2770
|
-
modelProviders: [],
|
|
2771
|
-
sortKey: root ? "recency_at" : "updated_at",
|
|
2772
|
-
sortDirection: "desc",
|
|
2773
|
-
...root ? {
|
|
2774
|
-
useStateDbOnly: true,
|
|
2775
|
-
...thread?.cwd ? { cwd: thread.cwd } : {}
|
|
2776
|
-
} : {},
|
|
2777
|
-
...cursor ? { cursor } : {}
|
|
2778
|
-
}, remaining());
|
|
2779
|
-
remaining();
|
|
2780
|
-
const candidate = page.data.find((value) => value.id === threadId);
|
|
2781
|
-
if (candidate) {
|
|
2782
|
-
if (!isInteractiveThreadSource(candidate.source)) throw unverified();
|
|
2783
|
-
if (root && thread) {
|
|
2784
|
-
const rolloutPath = thread.path;
|
|
2785
|
-
if (!rolloutPath || !candidate.path || rolloutPath.replace(/\.zst$/u, "") !== candidate.path.replace(/\.zst$/u, "")) throw unverified();
|
|
2786
|
-
const metadata = await readCodexSessionMeta(root, rolloutPath, threadId);
|
|
2787
|
-
remaining();
|
|
2788
|
-
if (!metadata || !isInteractiveThreadSource(metadata.source) || metadata.originator === "openclaw") throw unverified();
|
|
2789
|
-
return thread;
|
|
2790
|
-
}
|
|
2791
|
-
return candidate;
|
|
2792
|
-
}
|
|
2793
|
-
const nextCursor = readControlCursor(page.nextCursor, "next response");
|
|
2794
|
-
if (!nextCursor || seenCursors.has(nextCursor)) throw unverified();
|
|
2795
|
-
seenCursors.add(nextCursor);
|
|
2796
|
-
cursor = nextCursor;
|
|
2797
|
-
}
|
|
2798
|
-
throw unverified();
|
|
2799
|
-
};
|
|
2800
|
-
return await withTimeout(verify(), requests.requestTimeoutMs, "Codex session eligibility could not be verified", unverified);
|
|
2801
|
-
},
|
|
2802
|
-
retireConnection: params.retireConnection,
|
|
2803
|
-
async listPage(pageParams) {
|
|
2804
|
-
const limit = normalizeLimit(pageParams.limit, "limit");
|
|
2805
|
-
const search = pageParams.searchTerm?.trim().toLocaleLowerCase() || void 0;
|
|
2806
|
-
const cwd = pageParams.cwd?.trim() || void 0;
|
|
2807
|
-
const maxPages = search ? 20 : 1;
|
|
2808
|
-
const sessions = [];
|
|
2809
|
-
const managedThreads = [];
|
|
2810
|
-
let cursor = readControlCursor(pageParams.cursor, "request");
|
|
2811
|
-
let nextCursor;
|
|
2812
|
-
let backwardsCursor;
|
|
2813
|
-
const seenCursors = new Set(cursor ? [cursor] : []);
|
|
2814
|
-
const requests = params.createRequestSnapshot();
|
|
2815
|
-
const deadline = params.now() + requests.requestTimeoutMs;
|
|
2816
|
-
for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {
|
|
2817
|
-
const remainingTimeoutMs = Math.ceil(deadline - params.now());
|
|
2818
|
-
if (remainingTimeoutMs <= 0) throw new Error("Codex session catalog listing timed out");
|
|
2819
|
-
const response = await requests.listThreads({
|
|
2820
|
-
archived: false,
|
|
2821
|
-
limit: limit - sessions.length,
|
|
2822
|
-
modelProviders: [],
|
|
2823
|
-
sortKey: "updated_at",
|
|
2824
|
-
sortDirection: "desc",
|
|
2825
|
-
...cwd ? { cwd } : {},
|
|
2826
|
-
...cursor ? { cursor } : {}
|
|
2827
|
-
}, remainingTimeoutMs);
|
|
2828
|
-
if (pageIndex === 0) backwardsCursor = readControlCursor(response.backwardsCursor, "backwards response");
|
|
2829
|
-
for (const thread of response.data) {
|
|
2830
|
-
if (await isOpenClawManagedCodexThread(thread, params.localSessionsRoot)) {
|
|
2831
|
-
const rolloutPath = typeof thread.path === "string" ? thread.path.trim() : "";
|
|
2832
|
-
managedThreads.push({
|
|
2833
|
-
threadId: thread.id,
|
|
2834
|
-
...rolloutPath ? { rolloutPath } : {}
|
|
2835
|
-
});
|
|
2836
|
-
continue;
|
|
2837
|
-
}
|
|
2838
|
-
const session = toCatalogSession(thread, false);
|
|
2839
|
-
if (session && (!search || (session.name ?? session.fallbackName)?.toLocaleLowerCase().includes(search))) sessions.push(session);
|
|
2840
|
-
}
|
|
2841
|
-
nextCursor = readControlCursor(response.nextCursor, "next response");
|
|
2842
|
-
if (!nextCursor || sessions.length >= limit) break;
|
|
2843
|
-
if (seenCursors.has(nextCursor)) throw new Error("Codex session catalog returned a repeated search cursor");
|
|
2844
|
-
seenCursors.add(nextCursor);
|
|
2845
|
-
cursor = nextCursor;
|
|
2846
|
-
}
|
|
2847
|
-
return {
|
|
2848
|
-
sessions,
|
|
2849
|
-
...managedThreads.length > 0 ? { managedThreads } : {},
|
|
2850
|
-
...nextCursor ? { nextCursor } : {},
|
|
2851
|
-
...backwardsCursor ? { backwardsCursor } : {}
|
|
2852
|
-
};
|
|
2853
|
-
},
|
|
2854
|
-
async listDescendantPage(listParams) {
|
|
2855
|
-
const requests = params.createRequestSnapshot();
|
|
2856
|
-
return await requests.listThreads(listParams, requests.requestTimeoutMs);
|
|
2857
|
-
},
|
|
2858
|
-
async readThread(threadId, includeTurns = false) {
|
|
2859
|
-
return await params.createRequestSnapshot().readThread(threadId, includeTurns);
|
|
2860
|
-
},
|
|
2861
|
-
async listTurnPage(listParams) {
|
|
2862
|
-
return await params.createRequestSnapshot().listThreadTurns(listParams);
|
|
2863
|
-
},
|
|
2864
|
-
listItemPage: (listParams) => params.createRequestSnapshot().listThreadItems(listParams),
|
|
2865
|
-
async forkThread(forkParams, assertCurrent) {
|
|
2866
|
-
return await params.createRequestSnapshot().forkThread(forkParams, assertCurrent);
|
|
2867
|
-
},
|
|
2868
|
-
async archiveThread(threadId, assertCurrent) {
|
|
2869
|
-
await params.createRequestSnapshot().archiveThread(threadId, assertCurrent);
|
|
2870
|
-
}
|
|
2871
|
-
};
|
|
2872
|
-
}
|
|
2873
|
-
/** Builds the passive catalog over the Codex plugin's canonical shared client. */
|
|
2874
|
-
function createCodexSessionCatalogControl(params) {
|
|
2875
|
-
const now = params.now ?? Date.now;
|
|
2876
|
-
const getPluginConfig = () => params.getPluginConfig();
|
|
2877
|
-
const homeResolver = createCodexCatalogHomeResolver({
|
|
2878
|
-
config: params.getRuntimeConfig() ?? params.config ?? {},
|
|
2879
|
-
getRuntimeConfig: params.getRuntimeConfig,
|
|
2880
|
-
getPluginConfig: params.getPluginConfig,
|
|
2881
|
-
...params.env ? { env: params.env } : {}
|
|
2882
|
-
});
|
|
2883
|
-
const requestOptionsByConfig = /* @__PURE__ */ new WeakMap();
|
|
2884
|
-
const catalogPagesByConfig = /* @__PURE__ */ new WeakMap();
|
|
2885
|
-
const resolveRequestOptions = (startOptions, agentId, source) => {
|
|
2886
|
-
const runtimeConfig = params.getRuntimeConfig();
|
|
2887
|
-
const agentDir = source?.agentDir ?? resolveAgentDir$2(runtimeConfig ?? {}, agentId);
|
|
2888
|
-
const resolvedStartOptions = source?.appServer.start ?? startOptions;
|
|
2889
|
-
if (!runtimeConfig) return {
|
|
2890
|
-
agentDir,
|
|
2891
|
-
config: void 0,
|
|
2892
|
-
startOptions: structuredClone(resolvedStartOptions)
|
|
2893
|
-
};
|
|
2894
|
-
let byAgent = requestOptionsByConfig.get(runtimeConfig);
|
|
2895
|
-
const cacheKey = `${agentId ?? ""}\0${source?.sourceHomeId ?? ""}`;
|
|
2896
|
-
const cached = byAgent?.get(cacheKey);
|
|
2897
|
-
if (cached) return cached;
|
|
2898
|
-
const resolved = {
|
|
2899
|
-
agentDir,
|
|
2900
|
-
config: structuredClone(runtimeConfig),
|
|
2901
|
-
startOptions: structuredClone(resolvedStartOptions)
|
|
2902
|
-
};
|
|
2903
|
-
if (!byAgent) {
|
|
2904
|
-
byAgent = /* @__PURE__ */ new Map();
|
|
2905
|
-
requestOptionsByConfig.set(runtimeConfig, byAgent);
|
|
2906
|
-
}
|
|
2907
|
-
byAgent.set(cacheKey, resolved);
|
|
2908
|
-
return resolved;
|
|
2909
|
-
};
|
|
2910
|
-
const createRequestSnapshot = (agentId, source) => {
|
|
2911
|
-
const pluginConfig = getPluginConfig();
|
|
2912
|
-
const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
|
|
2913
|
-
const requestOptions = resolveRequestOptions(runtime.start, agentId, source);
|
|
2914
|
-
return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await codexControlRequest(pluginConfig, method, requestParams, {
|
|
2915
|
-
...requestOptions,
|
|
2916
|
-
assertCurrent,
|
|
2917
|
-
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
2918
|
-
}));
|
|
2919
|
-
};
|
|
2920
|
-
const forRequest = (agentId, source) => {
|
|
2921
|
-
const withPinnedConnection = async (run) => {
|
|
2922
|
-
const pluginConfig = getPluginConfig();
|
|
2923
|
-
const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
|
|
2924
|
-
const { agentDir, config: runtimeConfig, startOptions } = resolveRequestOptions(runtime.start, agentId, source);
|
|
2925
|
-
const client = await getLeasedSharedCodexAppServerClient({
|
|
2926
|
-
agentDir,
|
|
2927
|
-
config: runtimeConfig,
|
|
2928
|
-
startOptions,
|
|
2929
|
-
timeoutMs: runtime.requestTimeoutMs
|
|
2930
|
-
});
|
|
2931
|
-
try {
|
|
2932
|
-
const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await requestCodexAppServerClientJson({
|
|
2933
|
-
client,
|
|
2934
|
-
method,
|
|
2935
|
-
requestParams,
|
|
2936
|
-
config: runtimeConfig,
|
|
2937
|
-
timeoutMs: timeoutMs ?? runtime.requestTimeoutMs,
|
|
2938
|
-
assertCurrent
|
|
2939
|
-
}));
|
|
2940
|
-
const pinnedControl = createCodexSessionCatalogControlFromRequests({
|
|
2941
|
-
forkContext: {
|
|
2942
|
-
client,
|
|
2943
|
-
appServer: runtime,
|
|
2944
|
-
pluginConfig,
|
|
2945
|
-
agentDir,
|
|
2946
|
-
localSessionsRoot: source?.localSessionsRoot
|
|
2947
|
-
},
|
|
2948
|
-
clientId: resolveCodexAppServerClientInstanceId(client),
|
|
2949
|
-
retireConnection: () => {
|
|
2950
|
-
retireSharedCodexAppServerClientIfCurrent(client);
|
|
2951
|
-
},
|
|
2952
|
-
connectionFingerprint: buildCodexAppServerConnectionFingerprint(runtime, agentDir),
|
|
2953
|
-
createRequestSnapshot: () => requests,
|
|
2954
|
-
...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
|
|
2955
|
-
sourceHomeId: source?.sourceHomeId,
|
|
2956
|
-
managedThreads: params.managedThreads,
|
|
2957
|
-
now,
|
|
2958
|
-
withPinnedConnection: async (nestedRun) => await nestedRun(pinnedControl)
|
|
2959
|
-
});
|
|
2960
|
-
return await run(pinnedControl);
|
|
2961
|
-
} finally {
|
|
2962
|
-
releaseLeasedSharedCodexAppServerClient(client);
|
|
2963
|
-
}
|
|
2964
|
-
};
|
|
2965
|
-
const control = createCodexSessionCatalogControlFromRequests({
|
|
2966
|
-
createRequestSnapshot: () => createRequestSnapshot(agentId, source),
|
|
2967
|
-
...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
|
|
2968
|
-
now,
|
|
2969
|
-
withPinnedConnection
|
|
2970
|
-
});
|
|
2971
|
-
return {
|
|
2972
|
-
...control,
|
|
2973
|
-
requireEligibleThread: (threadId) => withPinnedConnection((pinned) => pinned.requireEligibleThread(threadId)),
|
|
2974
|
-
async listPage(pageParams) {
|
|
2975
|
-
const runtimeConfig = params.getRuntimeConfig();
|
|
2976
|
-
if (!runtimeConfig) return await control.listPage(pageParams);
|
|
2977
|
-
let cache = catalogPagesByConfig.get(runtimeConfig);
|
|
2978
|
-
if (!cache) {
|
|
2979
|
-
cache = /* @__PURE__ */ new Map();
|
|
2980
|
-
catalogPagesByConfig.set(runtimeConfig, cache);
|
|
2981
|
-
}
|
|
2982
|
-
const key = codexCatalogPageCacheKey(pageParams, agentId, source);
|
|
2983
|
-
const cached = cache.get(key);
|
|
2984
|
-
if (cached) {
|
|
2985
|
-
cache.delete(key);
|
|
2986
|
-
cache.set(key, cached);
|
|
2987
|
-
if (cached.expiresAt > now()) return cached.value ?? await cached.page;
|
|
2988
|
-
}
|
|
2989
|
-
if (cached) cache.delete(key);
|
|
2990
|
-
const page = control.listPage(pageParams);
|
|
2991
|
-
const staleValue = cached?.value;
|
|
2992
|
-
const entry = {
|
|
2993
|
-
expiresAt: Number.POSITIVE_INFINITY,
|
|
2994
|
-
page,
|
|
2995
|
-
...staleValue ? { value: staleValue } : {}
|
|
2996
|
-
};
|
|
2997
|
-
cache.set(key, entry);
|
|
2998
|
-
pruneMapToMaxSize(cache, CODEX_SESSION_CATALOG_LIST_CACHE_MAX_ENTRIES);
|
|
2999
|
-
const settle = (value) => {
|
|
3000
|
-
if (cache.get(key) === entry) {
|
|
3001
|
-
entry.value = value;
|
|
3002
|
-
entry.expiresAt = now() + CODEX_SESSION_CATALOG_LIST_TTL_MS;
|
|
3003
|
-
}
|
|
3004
|
-
return value;
|
|
3005
|
-
};
|
|
3006
|
-
const restore = () => {
|
|
3007
|
-
if (cache.get(key) !== entry) return;
|
|
3008
|
-
if (staleValue) cache.set(key, {
|
|
3009
|
-
expiresAt: now(),
|
|
3010
|
-
page: Promise.resolve(staleValue),
|
|
3011
|
-
value: staleValue
|
|
3012
|
-
});
|
|
3013
|
-
else cache.delete(key);
|
|
3014
|
-
};
|
|
3015
|
-
if (staleValue) {
|
|
3016
|
-
page.then(settle, restore);
|
|
3017
|
-
return staleValue;
|
|
3018
|
-
}
|
|
3019
|
-
try {
|
|
3020
|
-
return settle(await page);
|
|
3021
|
-
} catch (error) {
|
|
3022
|
-
restore();
|
|
3023
|
-
throw error;
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3026
|
-
};
|
|
3027
|
-
};
|
|
3028
|
-
const homesForAgent = (agentId) => homeResolver.forAgent(agentId);
|
|
3029
|
-
const forUpstream = (agentId, connectionFingerprint) => {
|
|
3030
|
-
const source = homesForAgent(agentId).find((home) => buildCodexAppServerConnectionFingerprint(home.appServer, home.agentDir) === connectionFingerprint);
|
|
3031
|
-
return source ? forRequest(agentId, source) : void 0;
|
|
3032
|
-
};
|
|
3033
|
-
return {
|
|
3034
|
-
forRequest,
|
|
3035
|
-
forUpstream,
|
|
3036
|
-
homesForAgent
|
|
3037
|
-
};
|
|
3038
|
-
}
|
|
3039
|
-
//#endregion
|
|
3040
|
-
//#region extensions/codex/src/session-catalog.ts
|
|
3041
|
-
/** Allows read-only catalog and transcript commands on supported paired-node platforms. */
|
|
3042
|
-
function createCodexSessionCatalogNodeInvokePolicies() {
|
|
3043
|
-
return [{
|
|
3044
|
-
commands: [
|
|
3045
|
-
CODEX_APP_SERVER_THREADS_LIST_COMMAND,
|
|
3046
|
-
CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
|
|
3047
|
-
CODEX_CATALOG_TRANSCRIPT_READ_COMMAND,
|
|
3048
|
-
CODEX_TERMINAL_RESUME_COMMAND,
|
|
3049
|
-
CODEX_TERMINAL_START_COMMAND
|
|
3050
|
-
],
|
|
3051
|
-
defaultPlatforms: [
|
|
3052
|
-
"macos",
|
|
3053
|
-
"linux",
|
|
3054
|
-
"windows"
|
|
3055
|
-
],
|
|
3056
|
-
handle: (context) => {
|
|
3057
|
-
if (context.command === "codex.terminal.start.v1") return context.client?.scopes?.includes("operator.admin") && context.config.gateway?.cliAgents?.enabled === true && context.config.gateway?.terminal?.enabled !== false ? { ok: true } : {
|
|
3058
|
-
ok: false,
|
|
3059
|
-
message: "Native terminal start requires operator.admin and enabled CLI agents and terminals"
|
|
3060
|
-
};
|
|
3061
|
-
return context.command === "codex.terminal.resume.v1" ? { ok: true } : context.invokeNode();
|
|
3062
|
-
}
|
|
3063
|
-
}];
|
|
3064
|
-
}
|
|
3065
|
-
function toGenericCatalogHost(host, localTerminalAvailable) {
|
|
3066
|
-
const local = isLocalCodexCatalogHost(host.hostId);
|
|
3067
|
-
return {
|
|
3068
|
-
hostId: host.hostId,
|
|
3069
|
-
label: host.label,
|
|
3070
|
-
kind: host.kind,
|
|
3071
|
-
connected: host.connected,
|
|
3072
|
-
...host.nodeId ? { nodeId: host.nodeId } : {},
|
|
3073
|
-
sessions: host.sessions.map((session) => {
|
|
3074
|
-
const continuableStatus = !session.archived && (session.status === "idle" || session.status === "notLoaded");
|
|
3075
|
-
const canContinue = (local || host.canContinueCodex === true) && continuableStatus && isInteractiveThreadSource(session.source);
|
|
3076
|
-
const canArchive = local && continuableStatus && isInteractiveThreadSource(session.source);
|
|
3077
|
-
const canOpenTerminal = isInteractiveThreadSource(session.source) && (local ? localTerminalAvailable : host.canOpenTerminalCodex === true);
|
|
3078
|
-
const name = session.name ?? session.fallbackName;
|
|
3079
|
-
return {
|
|
3080
|
-
threadId: session.threadId,
|
|
3081
|
-
...session.sourceHomeId ? { sourceHomeId: session.sourceHomeId } : {},
|
|
3082
|
-
...name ? { name } : {},
|
|
3083
|
-
...session.cwd ? { cwd: session.cwd } : {},
|
|
3084
|
-
status: session.status,
|
|
3085
|
-
...session.createdAt != null ? { createdAt: session.createdAt } : {},
|
|
3086
|
-
...session.updatedAt != null ? { updatedAt: session.updatedAt } : {},
|
|
3087
|
-
...session.recencyAt != null ? { recencyAt: session.recencyAt } : {},
|
|
3088
|
-
...session.source ? { source: session.source } : {},
|
|
3089
|
-
...session.modelProvider ? { modelProvider: session.modelProvider } : {},
|
|
3090
|
-
...session.cliVersion ? { cliVersion: session.cliVersion } : {},
|
|
3091
|
-
...session.gitBranch ? { gitBranch: session.gitBranch } : {},
|
|
3092
|
-
archived: session.archived,
|
|
3093
|
-
...session.sessionKey ? { sessionKey: session.sessionKey } : {},
|
|
3094
|
-
canContinue,
|
|
3095
|
-
canArchive,
|
|
3096
|
-
canOpenTerminal
|
|
3097
|
-
};
|
|
3098
|
-
}),
|
|
3099
|
-
...host.nextCursor ? { nextCursor: host.nextCursor } : {},
|
|
3100
|
-
...host.error ? { error: host.error } : {}
|
|
3101
|
-
};
|
|
3102
|
-
}
|
|
3103
|
-
function isLocalCodexCatalogHost(hostId) {
|
|
3104
|
-
return hostId === "gateway:local" || hostId.startsWith(`gateway:local:`);
|
|
3105
|
-
}
|
|
3106
|
-
function resolveLocalCatalogHomeForThread(params) {
|
|
3107
|
-
if (params.homes.length === 0) throw new CatalogParamsError("local Codex sessions are unavailable in isolated state");
|
|
3108
|
-
const exact = params.sourceHomeId ? params.homes.filter((home) => home.sourceHomeId === params.sourceHomeId) : params.homes.filter((home) => home.hostId === params.hostId);
|
|
3109
|
-
if (exact.length === 0 || params.sourceHomeId && exact[0]?.hostId !== params.hostId) throw new CatalogParamsError("Codex session source home is unavailable");
|
|
3110
|
-
return exact[0];
|
|
3111
|
-
}
|
|
3112
|
-
function registerCodexSessionCatalog(params) {
|
|
3113
|
-
const catalogHomes = (agentId, allowProcessHomeFallback) => {
|
|
3114
|
-
const homes = params.control.homesForAgent(agentId);
|
|
3115
|
-
return allowProcessHomeFallback === false ? homes.filter((home) => !home.usesProcessHomeFallback) : homes;
|
|
3116
|
-
};
|
|
3117
|
-
const resolveRequestAgentId = (agentId) => resolveSessionAgentIdsStrict({
|
|
3118
|
-
config: params.getRuntimeConfig() ?? params.api.config,
|
|
3119
|
-
agentId
|
|
3120
|
-
}).sessionAgentId;
|
|
3121
|
-
const bindRequest = (request) => {
|
|
3122
|
-
const agentId = resolveRequestAgentId(request.agentId);
|
|
3123
|
-
const source = isLocalCodexCatalogHost(request.hostId) ? resolveLocalCatalogHomeForThread({
|
|
3124
|
-
homes: [...catalogHomes(agentId, request.allowProcessHomeFallback)],
|
|
3125
|
-
hostId: request.hostId,
|
|
3126
|
-
...request.sourceHomeId ? { sourceHomeId: request.sourceHomeId } : {}
|
|
3127
|
-
}) : void 0;
|
|
3128
|
-
return {
|
|
3129
|
-
agentId,
|
|
3130
|
-
source,
|
|
3131
|
-
control: params.control.forRequest(agentId, source)
|
|
3132
|
-
};
|
|
3133
|
-
};
|
|
3134
|
-
const bindLocalRequest = (request) => {
|
|
3135
|
-
const bound = bindRequest(request);
|
|
3136
|
-
if (!bound.source) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
3137
|
-
return {
|
|
3138
|
-
...bound,
|
|
3139
|
-
source: bound.source
|
|
3140
|
-
};
|
|
3141
|
-
};
|
|
3142
|
-
const checkUpstreamActivity = createChecker(params);
|
|
3143
|
-
params.api.registerSessionCatalog({
|
|
3144
|
-
id: "codex",
|
|
3145
|
-
label: "Codex",
|
|
3146
|
-
supportsProcessHomeIsolation: true,
|
|
3147
|
-
resolveCreateSession: ({ agentId }) => resolveCodexCatalogCreateSession(params.getRuntimeConfig() ?? params.api.config, agentId),
|
|
3148
|
-
list: async (query) => {
|
|
3149
|
-
const localTerminalAvailable = resolveLocalCodexTerminalExecutable() !== void 0;
|
|
3150
|
-
const { agentId: requestedAgentId, allowProcessHomeFallback, listNodes, onHost, waitUntil, signal, sessionEntries, ...gatewayQuery } = query;
|
|
3151
|
-
const agentId = resolveRequestAgentId(requestedAgentId);
|
|
3152
|
-
const localHomes = [...catalogHomes(agentId, allowProcessHomeFallback)];
|
|
3153
|
-
const mapHost = (host) => ({
|
|
3154
|
-
...toGenericCatalogHost(host, localTerminalAvailable),
|
|
3155
|
-
canStartTerminal: host.kind === "gateway" ? localTerminalAvailable && localHomes.some((home) => home.hostId === host.hostId && home.appServer.start.transport === "stdio") : host.canStartTerminal === true
|
|
3156
|
-
});
|
|
3157
|
-
return (await listCodexSessionCatalog({
|
|
3158
|
-
agentId,
|
|
3159
|
-
bindingStore: params.bindingStore,
|
|
3160
|
-
config: params.getRuntimeConfig(),
|
|
3161
|
-
runtime: params.api.runtime,
|
|
3162
|
-
control: params.control,
|
|
3163
|
-
query: gatewayQuery,
|
|
3164
|
-
listNodes,
|
|
3165
|
-
waitUntil,
|
|
3166
|
-
signal,
|
|
3167
|
-
sessionEntries,
|
|
3168
|
-
localHomes,
|
|
3169
|
-
...onHost ? { onHost: (host) => onHost(mapHost(host)) } : {}
|
|
3170
|
-
})).hosts.map(mapHost);
|
|
3171
|
-
},
|
|
3172
|
-
read: async (request) => {
|
|
3173
|
-
const { agentId, source, control } = bindRequest(request);
|
|
3174
|
-
return await readCodexSessionTranscript({
|
|
3175
|
-
agentId,
|
|
3176
|
-
runtime: params.api.runtime,
|
|
3177
|
-
control,
|
|
3178
|
-
hostId: request.hostId,
|
|
3179
|
-
threadId: request.threadId,
|
|
3180
|
-
cursor: request.cursor,
|
|
3181
|
-
limit: request.limit ?? 20,
|
|
3182
|
-
...source ? { source } : {}
|
|
3183
|
-
});
|
|
3184
|
-
},
|
|
3185
|
-
continueSession: async (request) => {
|
|
3186
|
-
const config = params.getRuntimeConfig();
|
|
3187
|
-
if (!config) throw new Error("OpenClaw runtime config is unavailable");
|
|
3188
|
-
if (request.hostId.startsWith("node:")) return await continueNodeCodexSession({
|
|
3189
|
-
agentId: resolveRequestAgentId(request.agentId),
|
|
3190
|
-
api: params.api,
|
|
3191
|
-
config,
|
|
3192
|
-
hostId: request.hostId,
|
|
3193
|
-
threadId: request.threadId,
|
|
3194
|
-
clientScopes: request.clientScopes
|
|
3195
|
-
});
|
|
3196
|
-
if (!isLocalCodexCatalogHost(request.hostId)) throw new CatalogParamsError("Codex session catalog hostId is invalid");
|
|
3197
|
-
const { agentId, source, control } = bindLocalRequest(request);
|
|
3198
|
-
let upstreamBaseline;
|
|
3199
|
-
const continued = await continueLocalCodexSession({
|
|
3200
|
-
agentId,
|
|
3201
|
-
api: params.api,
|
|
3202
|
-
bindingStore: params.bindingStore,
|
|
3203
|
-
config,
|
|
3204
|
-
control,
|
|
3205
|
-
threadId: request.threadId,
|
|
3206
|
-
hostId: source.hostId,
|
|
3207
|
-
sourceHomeId: source.sourceHomeId,
|
|
3208
|
-
...source.hostId === "gateway:local" ? { allowLegacy: true } : {},
|
|
3209
|
-
onContinued: (baseline) => {
|
|
3210
|
-
upstreamBaseline = baseline;
|
|
3211
|
-
}
|
|
3212
|
-
});
|
|
3213
|
-
return codexUpstreamContinueResult(continued.sessionKey, request.threadId, upstreamBaseline);
|
|
3214
|
-
},
|
|
3215
|
-
checkUpstreamActivity: (probes, policy) => checkUpstreamActivity(probes.filter((probe) => !isLocalCodexCatalogHost(probe.hostId) || policy?.allowProcessHomeFallback !== false || catalogHomes(probe.agentId, false).some((home) => home.hostId === probe.hostId))),
|
|
3216
|
-
archive: async (request) => {
|
|
3217
|
-
if (request.confirmNoOtherRunner !== true) throw new CatalogParamsError("archive requires confirmation that no other runner is active");
|
|
3218
|
-
if (!isLocalCodexCatalogHost(request.hostId)) throw new CatalogParamsError("paired-node Codex sessions are view-only");
|
|
3219
|
-
const config = params.getRuntimeConfig();
|
|
3220
|
-
if (!config) throw new Error("OpenClaw runtime config is unavailable");
|
|
3221
|
-
const { agentId, source, control } = bindLocalRequest(request);
|
|
3222
|
-
await archiveLocalCodexSession({
|
|
3223
|
-
agentId,
|
|
3224
|
-
bindingStore: params.bindingStore,
|
|
3225
|
-
config,
|
|
3226
|
-
control,
|
|
3227
|
-
runtime: params.api.runtime,
|
|
3228
|
-
threadId: request.threadId,
|
|
3229
|
-
hostId: source.hostId,
|
|
3230
|
-
sourceHomeId: source.sourceHomeId,
|
|
3231
|
-
...source.hostId === "gateway:local" ? { allowLegacy: true } : {}
|
|
3232
|
-
});
|
|
3233
|
-
return { ok: true };
|
|
3234
|
-
},
|
|
3235
|
-
openTerminal: async (request) => {
|
|
3236
|
-
const { agentId, source, control } = bindRequest(request);
|
|
3237
|
-
return await openCodexCatalogTerminal({
|
|
3238
|
-
api: params.api,
|
|
3239
|
-
control,
|
|
3240
|
-
getPluginConfig: params.getPluginConfig,
|
|
3241
|
-
getRuntimeConfig: params.getRuntimeConfig,
|
|
3242
|
-
parseCatalogPage,
|
|
3243
|
-
...source ? { source } : {},
|
|
3244
|
-
...request,
|
|
3245
|
-
agentId
|
|
3246
|
-
});
|
|
3247
|
-
},
|
|
3248
|
-
startTerminalSession: async (request) => {
|
|
3249
|
-
const source = request.nodeId ? void 0 : resolveLocalCatalogHomeForThread({
|
|
3250
|
-
homes: [...catalogHomes(request.agentId, request.allowProcessHomeFallback)],
|
|
3251
|
-
hostId: request.hostId ?? "gateway:local"
|
|
3252
|
-
});
|
|
3253
|
-
if (source && source.appServer.start.transport !== "stdio") throw new CatalogParamsError("Native terminal start requires a local Codex source");
|
|
3254
|
-
return await startCodexCatalogTerminal({
|
|
3255
|
-
getPluginConfig: params.getPluginConfig,
|
|
3256
|
-
getRuntimeConfig: params.getRuntimeConfig,
|
|
3257
|
-
...request,
|
|
3258
|
-
source
|
|
3259
|
-
});
|
|
3260
|
-
}
|
|
3261
|
-
});
|
|
3262
|
-
}
|
|
3263
|
-
const codexSessionCatalogRuntime = {
|
|
3264
|
-
register: registerCodexSessionCatalog,
|
|
3265
|
-
list: listCodexSessionCatalog,
|
|
3266
|
-
readTranscript: readCodexSessionTranscript,
|
|
3267
|
-
continueLocal: continueLocalCodexSession,
|
|
3268
|
-
continueNode: continueNodeCodexSession,
|
|
3269
|
-
archiveLocal: archiveLocalCodexSession
|
|
3270
|
-
};
|
|
3271
|
-
//#endregion
|
|
3272
|
-
export { CODEX_LOCAL_SESSION_HOST_ID as a, readCodexStatusProbes as c, createCodexCliSessionNodeHostCommands as d, createCodexCliSessionNodeInvokePolicies as f, resumeCodexCliSessionOnNode as g, resolveCodexCliSessionForBindingOnNode as h, createCodexSessionCatalogNodeHostCommands as i, requestOptions as l, listCodexCliSessionsOnNode as m, createCodexSessionCatalogNodeInvokePolicies as n, assertCodexArchiveDescendantsUnowned as o, formatCodexCliSessions as p, createCodexSessionCatalogControl as r, codexControlRequest as s, codexSessionCatalogRuntime as t, safeCodexControlRequest as u };
|