@openclaw/acpx 2026.8.1-beta.2 → 2026.9.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +39 -52
- package/dist/{pi-session-catalog-runtime-BiTQtT7s.js → pi-session-catalog-runtime-BIu-8uRZ.js} +169 -502
- package/dist/{pi-session-paths-DgYt8LUg.js → pi-session-paths-EMbd4Hkz.js} +4 -1
- package/dist/{process-reaper-DzVuCxl3.js → process-reaper-DduWm_7N.js} +3 -11
- package/dist/{register.runtime-C29AY8LI.js → register.runtime-Dj29TKFy.js} +13 -141
- package/dist/register.runtime.js +1 -1
- package/dist/{runtime-BmzHUTK8.js → runtime-BmOxa15I.js} +4 -1
- package/dist/{service-LTeZyc7q.js → service-PRlUtXMf.js} +6 -5
- package/package.json +8 -8
- package/skills/acp-router/SKILL.md +2 -2
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { t as createAcpxRuntimeService } from "./register.runtime-
|
|
1
|
+
import { t as createAcpxRuntimeService } from "./register.runtime-Dj29TKFy.js";
|
|
2
2
|
import "./config-schema-DN_uAi4R.js";
|
|
3
|
-
import { a as PI_SESSION_READ_COMMAND,
|
|
3
|
+
import { a as PI_SESSIONS_CAPABILITY, c as PI_SESSION_READ_COMMAND, l as PI_TERMINAL_RESUME_COMMAND, o as PI_SESSIONS_LIST_COMMAND, r as piSessionStoreAvailable, s as PI_SESSION_ID_PATTERN } from "./pi-session-paths-EMbd4Hkz.js";
|
|
4
4
|
import { tryDispatchAcpReplyHook } from "openclaw/plugin-sdk/acp-runtime-backend";
|
|
5
5
|
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
|
6
6
|
import { createLazyRuntimeModule, createLazyRuntimeSurface } from "openclaw/plugin-sdk/lazy-runtime";
|
|
7
7
|
import { resolveNodeHostExecutable } from "openclaw/plugin-sdk/node-host";
|
|
8
|
+
import { createSessionCatalogNodeHostBindings } from "openclaw/plugin-sdk/session-catalog";
|
|
8
9
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
9
10
|
//#region extensions/acpx/src/pi-session-catalog-plugin.ts
|
|
10
|
-
const
|
|
11
|
-
const loadPiSessionCatalogModule = createLazyRuntimeModule(() => import("./pi-session-catalog-runtime-BiTQtT7s.js"));
|
|
11
|
+
const loadPiSessionCatalogModule = createLazyRuntimeModule(() => import("./pi-session-catalog-runtime-BIu-8uRZ.js"));
|
|
12
12
|
function fullConfigCatalogEnabled(config) {
|
|
13
13
|
if (!isRecord(config) || !isRecord(config.plugins) || !isRecord(config.plugins.entries)) return true;
|
|
14
14
|
const entry = config.plugins.entries.acpx;
|
|
@@ -18,54 +18,40 @@ function fullConfigCatalogEnabled(config) {
|
|
|
18
18
|
function isPiSessionCatalogEnabled(pluginConfig) {
|
|
19
19
|
return !isRecord(pluginConfig) || !isRecord(pluginConfig.piSessionCatalog) || pluginConfig.piSessionCatalog.enabled !== false;
|
|
20
20
|
}
|
|
21
|
-
function
|
|
21
|
+
function createPiSessionNodeHostBindings() {
|
|
22
22
|
const storeAvailable = ({ config, env }) => fullConfigCatalogEnabled(config) && piSessionStoreAvailable(env);
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
return createSessionCatalogNodeHostBindings({
|
|
24
|
+
capability: PI_SESSIONS_CAPABILITY,
|
|
25
|
+
listCommand: PI_SESSIONS_LIST_COMMAND,
|
|
26
|
+
readCommand: PI_SESSION_READ_COMMAND,
|
|
27
|
+
terminalCommand: PI_TERMINAL_RESUME_COMMAND,
|
|
28
|
+
sessionIdPattern: PI_SESSION_ID_PATTERN,
|
|
29
|
+
executable: "pi",
|
|
30
|
+
args: (threadId) => ["--session", threadId],
|
|
31
|
+
listAvailable: storeAvailable,
|
|
32
|
+
terminalAvailable: ({ config, env }) => storeAvailable({
|
|
33
|
+
config,
|
|
34
|
+
env
|
|
35
|
+
}) && Boolean(resolveNodeHostExecutable("pi", {
|
|
36
|
+
env,
|
|
37
|
+
pathEnv: env.PATH ?? env.Path ?? "",
|
|
38
|
+
strategy: "direct"
|
|
39
|
+
})),
|
|
40
|
+
parseParams: (paramsJSON) => {
|
|
41
|
+
if (!paramsJSON) return;
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(paramsJSON);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error("Pi session parameters must be valid JSON", { cause: error });
|
|
46
|
+
}
|
|
30
47
|
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
command: PI_TERMINAL_RESUME_COMMAND,
|
|
40
|
-
cap: PI_SESSIONS_CAPABILITY,
|
|
41
|
-
dangerous: false,
|
|
42
|
-
duplex: true,
|
|
43
|
-
isAvailable: ({ config, env }) => storeAvailable({
|
|
44
|
-
config,
|
|
45
|
-
env
|
|
46
|
-
}) && Boolean(resolveNodeHostExecutable("pi", {
|
|
47
|
-
env,
|
|
48
|
-
pathEnv: env.PATH ?? env.Path ?? "",
|
|
49
|
-
strategy: "direct"
|
|
50
|
-
})),
|
|
51
|
-
handle: async (paramsJSON, io) => await (await loadPiSessionCatalogModule()).resumePiSession(paramsJSON, io)
|
|
52
|
-
}
|
|
53
|
-
];
|
|
54
|
-
}
|
|
55
|
-
function createPiSessionNodeInvokePolicies() {
|
|
56
|
-
return [{
|
|
57
|
-
commands: [
|
|
58
|
-
PI_SESSIONS_LIST_COMMAND,
|
|
59
|
-
PI_SESSION_READ_COMMAND,
|
|
60
|
-
PI_TERMINAL_RESUME_COMMAND
|
|
61
|
-
],
|
|
62
|
-
defaultPlatforms: [
|
|
63
|
-
"macos",
|
|
64
|
-
"linux",
|
|
65
|
-
"windows"
|
|
66
|
-
],
|
|
67
|
-
handle: (context) => context.command === "acpx.pi.terminal.resume.v1" ? { ok: true } : context.invokeNode()
|
|
68
|
-
}];
|
|
48
|
+
list: async (params) => await (await loadPiSessionCatalogModule()).listPiSessions(params),
|
|
49
|
+
read: async (params) => await (await loadPiSessionCatalogModule()).readPiSession(params),
|
|
50
|
+
requireSession: async (threadId) => await (await loadPiSessionCatalogModule()).requireLocalPiSession(threadId),
|
|
51
|
+
terminalIoRequiredMessage: "Pi terminal command requires duplex transport",
|
|
52
|
+
terminalUnavailableMessage: "Pi CLI is unavailable",
|
|
53
|
+
invalidThreadIdMessage: "INVALID_REQUEST: threadId is invalid"
|
|
54
|
+
});
|
|
69
55
|
}
|
|
70
56
|
function registerPiSessionCatalog(api) {
|
|
71
57
|
if (!isPiSessionCatalogEnabled(api.pluginConfig)) return;
|
|
@@ -80,8 +66,9 @@ function registerPiSessionCatalog(api) {
|
|
|
80
66
|
checkUpstreamActivity: async (probes, policy) => await (await loadCatalogRuntime()).checkUpstreamActivity(probes, policy),
|
|
81
67
|
openTerminal: async (request) => await (await loadCatalogRuntime()).openTerminal(request)
|
|
82
68
|
});
|
|
83
|
-
|
|
84
|
-
for (const
|
|
69
|
+
const nodeHost = createPiSessionNodeHostBindings();
|
|
70
|
+
for (const command of nodeHost.commands) api.registerNodeHostCommand(command);
|
|
71
|
+
for (const policy of nodeHost.policies) api.registerNodeInvokePolicy(policy);
|
|
85
72
|
}
|
|
86
73
|
//#endregion
|
|
87
74
|
//#region extensions/acpx/index.ts
|
package/dist/{pi-session-catalog-runtime-BiTQtT7s.js → pi-session-catalog-runtime-BIu-8uRZ.js}
RENAMED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as PI_SESSION_READ_COMMAND, i as PI_LOCAL_SESSION_HOST_ID, l as PI_TERMINAL_RESUME_COMMAND, n as piSessionStore, o as PI_SESSIONS_LIST_COMMAND, r as piSessionStoreAvailable, s as PI_SESSION_ID_PATTERN, t as piAcpSessionStoreRoot } from "./pi-session-paths-EMbd4Hkz.js";
|
|
2
2
|
import { parseDateFirstTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveNodeHostExecutable } from "openclaw/plugin-sdk/node-host";
|
|
4
|
+
import { createSessionCatalogFamily, importSessionCatalogHistory, isExternalUserText, listAdoptedSessionCatalogSessions, sessionCatalogAdoptedSessionKey, sessionCatalogPaging } from "openclaw/plugin-sdk/session-catalog";
|
|
4
5
|
import { isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
6
|
import { createReadStream } from "node:fs";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import fs$1 from "node:fs/promises";
|
|
8
9
|
import process from "node:process";
|
|
9
10
|
import { resolveAcpSessionAvailability } from "openclaw/plugin-sdk/acp-runtime";
|
|
10
|
-
import {
|
|
11
|
-
import { createSessionCatalogAdoptionCoordinator, importSessionCatalogHistory, isExternalUserText, listAdoptedSessionCatalogSessions, sessionCatalogAdoptedSessionKey, sessionCatalogAdoptedSourceKey } from "openclaw/plugin-sdk/session-catalog";
|
|
11
|
+
import { resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
12
12
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
13
|
+
import { isPathStrictlyInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
13
14
|
//#region extensions/acpx/src/pi-session-timestamp.ts
|
|
14
15
|
/** Preserve Pi JSONL's date-first string contract while accepting numeric millisecond values. */
|
|
15
16
|
function parsePiSessionTimestampMs(value) {
|
|
@@ -26,7 +27,7 @@ const APPEND_PROOF_EDGE_BYTES = 64 * 1024;
|
|
|
26
27
|
const IO_CONCURRENCY = 8;
|
|
27
28
|
const PI_FILE_CANDIDATE_CACHE_TTL_MS = 32e3;
|
|
28
29
|
const PI_FILE_CANDIDATE_CACHE_MAX_ENTRIES = 8;
|
|
29
|
-
const SESSION_ID_PATTERN
|
|
30
|
+
const SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
|
|
30
31
|
const summaryCache = /* @__PURE__ */ new Map();
|
|
31
32
|
const threadFileCache = /* @__PURE__ */ new Map();
|
|
32
33
|
const piFileCandidateCache = /* @__PURE__ */ new Map();
|
|
@@ -120,7 +121,7 @@ async function scanPiFileCandidates(env) {
|
|
|
120
121
|
identity: `${String(stats.dev)}:${String(stats.ino)}:${String(stats.birthtimeMs)}`,
|
|
121
122
|
mtimeMs: stats.mtimeMs,
|
|
122
123
|
size: stats.size,
|
|
123
|
-
resumable: acpRoot ?
|
|
124
|
+
resumable: acpRoot ? isPathStrictlyInside(acpRoot, file) : false
|
|
124
125
|
} : void 0;
|
|
125
126
|
} catch {
|
|
126
127
|
return;
|
|
@@ -155,10 +156,6 @@ async function piFileCandidates(env) {
|
|
|
155
156
|
throw error;
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
|
-
function pathIsWithin(root, candidate) {
|
|
159
|
-
const relative = path.relative(root, candidate);
|
|
160
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
161
|
-
}
|
|
162
159
|
function parsePiJsonLines(content) {
|
|
163
160
|
return content.split(/\r?\n/u).flatMap((line) => {
|
|
164
161
|
if (!line.trim()) return [];
|
|
@@ -277,7 +274,7 @@ async function readPiSessionSummary(candidate) {
|
|
|
277
274
|
const { header, name, firstMessage } = projectedState;
|
|
278
275
|
const version = header?.type === "session" && typeof header.version === "number" ? header.version : 1;
|
|
279
276
|
const threadId = header?.type === "session" ? normalizeBoundedOptionalString(header.id, 256) : void 0;
|
|
280
|
-
if (header && threadId && SESSION_ID_PATTERN
|
|
277
|
+
if (header && threadId && SESSION_ID_PATTERN.test(threadId)) {
|
|
281
278
|
const cwd = normalizeBoundedOptionalString(header.cwd, 4096);
|
|
282
279
|
const createdAt = parsePiSessionTimestampMs(header.timestamp);
|
|
283
280
|
summary = {
|
|
@@ -380,86 +377,8 @@ async function readPiSessionById(threadId, env) {
|
|
|
380
377
|
}
|
|
381
378
|
//#endregion
|
|
382
379
|
//#region extensions/acpx/src/pi-session-catalog.ts
|
|
383
|
-
const LOCAL_HOST_ID$1 = "gateway";
|
|
384
|
-
const DEFAULT_PAGE_LIMIT = 20;
|
|
385
|
-
const MAX_PAGE_LIMIT$1 = 100;
|
|
386
380
|
const MAX_SEARCH_LENGTH = 500;
|
|
387
|
-
const
|
|
388
|
-
const MAX_TRANSCRIPT_ITEM_BYTES = 512 * 1024;
|
|
389
|
-
const MAX_TRANSCRIPT_PAGE_BYTES = 20 * 1024 * 1024;
|
|
390
|
-
const SESSION_ID_PATTERN$1 = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
|
|
391
|
-
function boundedLimit(value, fallback = DEFAULT_PAGE_LIMIT) {
|
|
392
|
-
if (value === void 0) return fallback;
|
|
393
|
-
if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > MAX_PAGE_LIMIT$1) throw new Error(`limit must be an integer between 1 and ${String(MAX_PAGE_LIMIT$1)}`);
|
|
394
|
-
return Number(value);
|
|
395
|
-
}
|
|
396
|
-
function encodeCursor(offset) {
|
|
397
|
-
return Buffer.from(JSON.stringify({ offset }), "utf8").toString("base64url");
|
|
398
|
-
}
|
|
399
|
-
function optionalRawCursor(value) {
|
|
400
|
-
if (value === void 0) return;
|
|
401
|
-
if (typeof value !== "string" || value.length === 0 || value.length > MAX_CURSOR_LENGTH) throw new Error("cursor is invalid");
|
|
402
|
-
return value;
|
|
403
|
-
}
|
|
404
|
-
function decodeCursor(value) {
|
|
405
|
-
const cursor = optionalRawCursor(value);
|
|
406
|
-
if (cursor === void 0) return 0;
|
|
407
|
-
try {
|
|
408
|
-
const bytes = Buffer.from(cursor, "base64url");
|
|
409
|
-
if (bytes.toString("base64url") !== cursor) throw new Error("non-canonical base64url");
|
|
410
|
-
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
411
|
-
if (!isRecord(parsed) || !Number.isSafeInteger(parsed.offset) || Number(parsed.offset) < 0) throw new Error("invalid offset");
|
|
412
|
-
const offset = Number(parsed.offset);
|
|
413
|
-
if (encodeCursor(offset) !== cursor) throw new Error("non-canonical cursor payload");
|
|
414
|
-
return offset;
|
|
415
|
-
} catch (error) {
|
|
416
|
-
throw new Error("cursor is invalid", { cause: error });
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
function isExactPiSessionCursor(value) {
|
|
420
|
-
if (typeof value !== "string") return false;
|
|
421
|
-
try {
|
|
422
|
-
decodeCursor(value);
|
|
423
|
-
return true;
|
|
424
|
-
} catch {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
function truncateUtf8(text, maxBytes) {
|
|
429
|
-
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
430
|
-
let low = 0;
|
|
431
|
-
let high = text.length;
|
|
432
|
-
while (low < high) {
|
|
433
|
-
const middle = Math.ceil((low + high) / 2);
|
|
434
|
-
if (Buffer.byteLength(text.slice(0, middle), "utf8") <= maxBytes - 3) low = middle;
|
|
435
|
-
else high = middle - 1;
|
|
436
|
-
}
|
|
437
|
-
const end = low > 0 && /[\uD800-\uDBFF]/u.test(text.charAt(low - 1)) ? low - 1 : low;
|
|
438
|
-
return `${text.slice(0, end)}…`;
|
|
439
|
-
}
|
|
440
|
-
function transcriptPage(items, limit, offset) {
|
|
441
|
-
const end = Math.max(0, items.length - offset);
|
|
442
|
-
const start = Math.max(0, end - limit);
|
|
443
|
-
const page = [];
|
|
444
|
-
let pageBytes = 2;
|
|
445
|
-
for (let index = end - 1; index >= start; index -= 1) {
|
|
446
|
-
const item = items[index];
|
|
447
|
-
if (!item) continue;
|
|
448
|
-
const bounded = {
|
|
449
|
-
...item,
|
|
450
|
-
text: truncateUtf8(item.text ?? "", MAX_TRANSCRIPT_ITEM_BYTES)
|
|
451
|
-
};
|
|
452
|
-
const itemBytes = Buffer.byteLength(JSON.stringify(bounded), "utf8") + 1;
|
|
453
|
-
if (page.length > 0 && pageBytes + itemBytes > MAX_TRANSCRIPT_PAGE_BYTES) break;
|
|
454
|
-
page.unshift(bounded);
|
|
455
|
-
pageBytes += itemBytes;
|
|
456
|
-
}
|
|
457
|
-
const consumed = offset + page.length;
|
|
458
|
-
return {
|
|
459
|
-
items: page,
|
|
460
|
-
...consumed < items.length ? { nextCursor: encodeCursor(consumed) } : {}
|
|
461
|
-
};
|
|
462
|
-
}
|
|
381
|
+
const isExactPiSessionCursor = sessionCatalogPaging.isExactCursor;
|
|
463
382
|
function textFromContent$1(content) {
|
|
464
383
|
if (typeof content === "string") return content;
|
|
465
384
|
if (!Array.isArray(content)) return "";
|
|
@@ -473,44 +392,20 @@ function textFromContent$1(content) {
|
|
|
473
392
|
return [];
|
|
474
393
|
}).join("\n");
|
|
475
394
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (unknown) throw new Error(`unknown Pi session list parameter: ${unknown}`);
|
|
485
|
-
const searchTerm = normalizeBoundedOptionalString(value.searchTerm, MAX_SEARCH_LENGTH);
|
|
486
|
-
if (value.searchTerm !== void 0 && !searchTerm) throw new Error("searchTerm is invalid");
|
|
487
|
-
const cursor = optionalRawCursor(value.cursor);
|
|
488
|
-
return {
|
|
489
|
-
limit: boundedLimit(value.limit),
|
|
490
|
-
...searchTerm ? { searchTerm } : {},
|
|
491
|
-
...cursor ? { cursor } : {}
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
function parseReadParams(value) {
|
|
495
|
-
if (!isRecord(value)) throw new Error("Pi session read parameters must be an object");
|
|
496
|
-
const unknown = Object.keys(value).find((key) => ![
|
|
497
|
-
"threadId",
|
|
498
|
-
"limit",
|
|
499
|
-
"cursor"
|
|
500
|
-
].includes(key));
|
|
501
|
-
if (unknown) throw new Error(`unknown Pi session read parameter: ${unknown}`);
|
|
502
|
-
const threadId = normalizeBoundedOptionalString(value.threadId, 256);
|
|
503
|
-
if (!threadId || !SESSION_ID_PATTERN$1.test(threadId)) throw new Error("threadId is invalid");
|
|
504
|
-
const cursor = optionalRawCursor(value.cursor);
|
|
505
|
-
return {
|
|
506
|
-
threadId,
|
|
507
|
-
limit: boundedLimit(value.limit),
|
|
508
|
-
...cursor ? { cursor } : {}
|
|
509
|
-
};
|
|
510
|
-
}
|
|
395
|
+
const PI_PARAMETER_MESSAGES = {
|
|
396
|
+
listNotObject: "Pi session list parameters must be an object",
|
|
397
|
+
unknownListParameter: (key) => `unknown Pi session list parameter: ${key}`,
|
|
398
|
+
invalidSearchTerm: "searchTerm is invalid",
|
|
399
|
+
readNotObject: "Pi session read parameters must be an object",
|
|
400
|
+
unknownReadParameter: (key) => `unknown Pi session read parameter: ${key}`,
|
|
401
|
+
invalidThreadId: "threadId is invalid"
|
|
402
|
+
};
|
|
511
403
|
async function listLocalPiSessionPage(value) {
|
|
512
|
-
const params = parseListParams(value
|
|
513
|
-
|
|
404
|
+
const params = sessionCatalogPaging.parseListParams(value, {
|
|
405
|
+
searchMaxLength: MAX_SEARCH_LENGTH,
|
|
406
|
+
messages: PI_PARAMETER_MESSAGES
|
|
407
|
+
});
|
|
408
|
+
const offset = sessionCatalogPaging.decodeCursor(params.cursor);
|
|
514
409
|
const { summaries, hasMore } = await listPiSummaryPage(process.env, {
|
|
515
410
|
offset,
|
|
516
411
|
limit: params.limit,
|
|
@@ -519,7 +414,7 @@ async function listLocalPiSessionPage(value) {
|
|
|
519
414
|
const page = summaries.map(({ file: _file, version: _version, ...session }) => session);
|
|
520
415
|
return {
|
|
521
416
|
sessions: page,
|
|
522
|
-
...hasMore ? { nextCursor: encodeCursor(offset + page.length) } : {}
|
|
417
|
+
...hasMore ? { nextCursor: sessionCatalogPaging.encodeCursor(offset + page.length) } : {}
|
|
523
418
|
};
|
|
524
419
|
}
|
|
525
420
|
function isoTimestamp(message, entry) {
|
|
@@ -670,11 +565,16 @@ function piTranscriptItems(entries) {
|
|
|
670
565
|
});
|
|
671
566
|
}
|
|
672
567
|
async function readLocalPiTranscriptPage(value) {
|
|
673
|
-
const params = parseReadParams(value
|
|
674
|
-
|
|
675
|
-
|
|
568
|
+
const params = sessionCatalogPaging.parseReadParams(value, {
|
|
569
|
+
threadIdMaxLength: 256,
|
|
570
|
+
threadIdPattern: PI_SESSION_ID_PATTERN,
|
|
571
|
+
messages: PI_PARAMETER_MESSAGES
|
|
572
|
+
});
|
|
573
|
+
const offset = sessionCatalogPaging.decodeCursor(params.cursor);
|
|
574
|
+
const items = piTranscriptItems(await readPiSessionById(params.threadId, process.env));
|
|
575
|
+
const page = sessionCatalogPaging.boundTranscriptPage(items, params.limit, offset);
|
|
676
576
|
return {
|
|
677
|
-
hostId:
|
|
577
|
+
hostId: PI_LOCAL_SESSION_HOST_ID,
|
|
678
578
|
label: "Local Pi",
|
|
679
579
|
threadId: params.threadId,
|
|
680
580
|
...page
|
|
@@ -798,201 +698,17 @@ async function checkPiUpstreamActivity(probes) {
|
|
|
798
698
|
}
|
|
799
699
|
//#endregion
|
|
800
700
|
//#region extensions/acpx/src/pi-session-catalog-runtime.ts
|
|
801
|
-
const LOCAL_HOST_ID = "gateway";
|
|
802
|
-
const MAX_PAGE_LIMIT = 100;
|
|
803
|
-
const MAX_HOSTS = 100;
|
|
804
701
|
const NODE_TIMEOUT_MS = 2e4;
|
|
805
|
-
const SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
|
|
806
|
-
const TRANSCRIPT_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
807
|
-
"userMessage",
|
|
808
|
-
"agentMessage",
|
|
809
|
-
"reasoning",
|
|
810
|
-
"toolCall",
|
|
811
|
-
"toolResult",
|
|
812
|
-
"other"
|
|
813
|
-
]);
|
|
814
702
|
const ACPX_BACKEND_ID = "acpx";
|
|
815
703
|
const PI_ACP_AGENT_ID = "pi";
|
|
816
704
|
const PI_ADOPTED_SESSION_KEY_PREFIX = "plugin:acpx:catalog-adopt:pi:";
|
|
817
|
-
var PiCatalogParamsError = class extends Error {};
|
|
818
|
-
const continueAdoption = createSessionCatalogAdoptionCoordinator();
|
|
819
|
-
function validatePiThreadId(value) {
|
|
820
|
-
if (typeof value !== "string" || !SESSION_ID_PATTERN.test(value)) throw new Error("INVALID_REQUEST: threadId is invalid");
|
|
821
|
-
return value;
|
|
822
|
-
}
|
|
823
|
-
function isOptionalString(value) {
|
|
824
|
-
return value === void 0 || typeof value === "string";
|
|
825
|
-
}
|
|
826
|
-
function isOptionalNumber(value) {
|
|
827
|
-
return value === void 0 || typeof value === "number";
|
|
828
|
-
}
|
|
829
|
-
function isNodeSession(value) {
|
|
830
|
-
return isRecord(value) && typeof value.threadId === "string" && SESSION_ID_PATTERN.test(value.threadId) && typeof value.status === "string" && value.status.length > 0 && typeof value.archived === "boolean" && typeof value.canContinue === "boolean" && typeof value.canArchive === "boolean" && isOptionalString(value.name) && isOptionalString(value.cwd) && isOptionalString(value.source) && isOptionalString(value.modelProvider) && isOptionalString(value.cliVersion) && isOptionalString(value.gitBranch) && isOptionalString(value.sessionKey) && isOptionalNumber(value.createdAt) && isOptionalNumber(value.updatedAt) && isOptionalNumber(value.recencyAt);
|
|
831
|
-
}
|
|
832
|
-
function isNodeTranscriptItem(value) {
|
|
833
|
-
return isRecord(value) && typeof value.type === "string" && TRANSCRIPT_ITEM_TYPES.has(value.type) && isOptionalString(value.id) && isOptionalString(value.text) && isOptionalString(value.timestamp) && isOptionalString(value.model) && (value.truncated === void 0 || typeof value.truncated === "boolean");
|
|
834
|
-
}
|
|
835
|
-
function parseNodeParams(paramsJSON) {
|
|
836
|
-
if (!paramsJSON) return;
|
|
837
|
-
try {
|
|
838
|
-
return JSON.parse(paramsJSON);
|
|
839
|
-
} catch (error) {
|
|
840
|
-
throw new Error("Pi session parameters must be valid JSON", { cause: error });
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
function nodeLabel(node) {
|
|
844
|
-
return node.displayName?.trim() || node.remoteIp?.trim() || node.nodeId;
|
|
845
|
-
}
|
|
846
|
-
function unwrapNodePayload(value) {
|
|
847
|
-
return isRecord(value) && typeof value.payloadJSON === "string" ? JSON.parse(value.payloadJSON) : value;
|
|
848
|
-
}
|
|
849
|
-
function setCatalogCapabilities(page, capabilities) {
|
|
850
|
-
for (const session of page.sessions) {
|
|
851
|
-
session.canContinue = capabilities.canContinue && session.canContinue;
|
|
852
|
-
session.canOpenTerminal = capabilities.canOpenTerminal;
|
|
853
|
-
}
|
|
854
|
-
return page;
|
|
855
|
-
}
|
|
856
|
-
function projectPiAdoptedSessions(page, adopted) {
|
|
857
|
-
return {
|
|
858
|
-
...page,
|
|
859
|
-
sessions: page.sessions.map((session) => {
|
|
860
|
-
const sessionKey = adopted.get(sessionCatalogAdoptedSourceKey(LOCAL_HOST_ID, session.threadId));
|
|
861
|
-
return sessionKey ? {
|
|
862
|
-
...session,
|
|
863
|
-
sessionKey
|
|
864
|
-
} : session;
|
|
865
|
-
})
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
async function listPiNodeHost(runtime, query, node) {
|
|
869
|
-
const hostId = `node:${node.nodeId}`;
|
|
870
|
-
const common = {
|
|
871
|
-
hostId,
|
|
872
|
-
label: nodeLabel(node),
|
|
873
|
-
kind: "node",
|
|
874
|
-
connected: node.connected === true,
|
|
875
|
-
nodeId: node.nodeId
|
|
876
|
-
};
|
|
877
|
-
if (node.connected !== true) return {
|
|
878
|
-
...common,
|
|
879
|
-
sessions: [],
|
|
880
|
-
error: {
|
|
881
|
-
code: "NODE_OFFLINE",
|
|
882
|
-
message: "Paired node is offline"
|
|
883
|
-
}
|
|
884
|
-
};
|
|
885
|
-
try {
|
|
886
|
-
const cursor = query.cursors?.[hostId];
|
|
887
|
-
if (cursor !== void 0 && !isExactPiSessionCursor(cursor)) throw new Error("cursor is invalid");
|
|
888
|
-
const page = parseNodeSessionPage(unwrapNodePayload(await runtime.nodes.invoke({
|
|
889
|
-
nodeId: node.nodeId,
|
|
890
|
-
command: PI_SESSIONS_LIST_COMMAND,
|
|
891
|
-
params: {
|
|
892
|
-
...query.limitPerHost ? { limit: query.limitPerHost } : {},
|
|
893
|
-
...query.search ? { searchTerm: query.search } : {},
|
|
894
|
-
...cursor !== void 0 ? { cursor } : {}
|
|
895
|
-
},
|
|
896
|
-
timeoutMs: NODE_TIMEOUT_MS,
|
|
897
|
-
scopes: ["operator.write"]
|
|
898
|
-
})));
|
|
899
|
-
const canOpenTerminal = (node.invocableCommands ?? node.commands)?.includes(PI_TERMINAL_RESUME_COMMAND) === true;
|
|
900
|
-
return {
|
|
901
|
-
...common,
|
|
902
|
-
...setCatalogCapabilities(page, {
|
|
903
|
-
canContinue: false,
|
|
904
|
-
canOpenTerminal
|
|
905
|
-
})
|
|
906
|
-
};
|
|
907
|
-
} catch {
|
|
908
|
-
return {
|
|
909
|
-
...common,
|
|
910
|
-
sessions: [],
|
|
911
|
-
error: {
|
|
912
|
-
code: "NODE_INVOKE_FAILED",
|
|
913
|
-
message: "Paired node Pi sessions are unavailable"
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
function parseNodeSessionPage(value) {
|
|
919
|
-
if (!isRecord(value) || !Array.isArray(value.sessions) || value.sessions.length > MAX_PAGE_LIMIT) throw new Error("Pi node returned an invalid session page");
|
|
920
|
-
if (!value.sessions.every(isNodeSession)) throw new Error("Pi node returned an invalid session page");
|
|
921
|
-
const sessions = value.sessions;
|
|
922
|
-
const nextCursor = value.nextCursor;
|
|
923
|
-
if (nextCursor !== void 0 && !isExactPiSessionCursor(nextCursor)) throw new Error("Pi node returned an invalid cursor");
|
|
924
|
-
return {
|
|
925
|
-
sessions,
|
|
926
|
-
...nextCursor !== void 0 ? { nextCursor } : {}
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
function parseNodeTranscriptPage(value, threadId) {
|
|
930
|
-
if (!isRecord(value) || value.threadId !== threadId || !Array.isArray(value.items) || value.items.length > MAX_PAGE_LIMIT || !value.items.every(isNodeTranscriptItem)) throw new Error("Pi node returned an invalid transcript page");
|
|
931
|
-
const nextCursor = value.nextCursor;
|
|
932
|
-
if (nextCursor !== void 0 && !isExactPiSessionCursor(nextCursor)) throw new Error("Pi node returned an invalid cursor");
|
|
933
|
-
return {
|
|
934
|
-
hostId: LOCAL_HOST_ID,
|
|
935
|
-
threadId,
|
|
936
|
-
items: value.items,
|
|
937
|
-
...nextCursor !== void 0 ? { nextCursor } : {}
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
async function listPiHosts(api, query) {
|
|
941
|
-
const runtime = api.runtime;
|
|
942
|
-
const canContinue = resolvePiContinuationAvailability(api).available;
|
|
943
|
-
const adopted = query.sessionEntries ? listAdoptedPiSessions(api, query.sessionEntries) : /* @__PURE__ */ new Map();
|
|
944
|
-
const requested = query.hostIds ? new Set(query.hostIds) : void 0;
|
|
945
|
-
const hosts = [];
|
|
946
|
-
const localStore = !requested || requested.has(LOCAL_HOST_ID) ? piSessionStore(process.env) : void 0;
|
|
947
|
-
if (localStore && (query.allowProcessHomeFallback !== false || !localStore.usesProcessHomeFallback) && piSessionStoreAvailable(process.env, localStore)) try {
|
|
948
|
-
hosts.push({
|
|
949
|
-
hostId: LOCAL_HOST_ID,
|
|
950
|
-
label: "Local Pi",
|
|
951
|
-
kind: "gateway",
|
|
952
|
-
connected: true,
|
|
953
|
-
...await listLocalPiSessionPage({
|
|
954
|
-
limit: query.limitPerHost,
|
|
955
|
-
...query.search ? { searchTerm: query.search } : {},
|
|
956
|
-
cursor: query.cursors?.[LOCAL_HOST_ID]
|
|
957
|
-
}).then((page) => projectPiAdoptedSessions(setCatalogCapabilities(page, {
|
|
958
|
-
canContinue,
|
|
959
|
-
canOpenTerminal: resolveNodeHostExecutable("pi", {
|
|
960
|
-
env: process.env,
|
|
961
|
-
pathEnv: process.env.PATH ?? "",
|
|
962
|
-
strategy: "fallback"
|
|
963
|
-
}) !== void 0
|
|
964
|
-
}), adopted))
|
|
965
|
-
});
|
|
966
|
-
} catch {
|
|
967
|
-
hosts.push({
|
|
968
|
-
hostId: LOCAL_HOST_ID,
|
|
969
|
-
label: "Local Pi",
|
|
970
|
-
kind: "gateway",
|
|
971
|
-
connected: true,
|
|
972
|
-
sessions: [],
|
|
973
|
-
error: {
|
|
974
|
-
code: "LOCAL_READ_FAILED",
|
|
975
|
-
message: "Local Pi sessions are unavailable"
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
let nodes;
|
|
980
|
-
try {
|
|
981
|
-
nodes = (await (query.listNodes?.() ?? runtime.nodes.list())).nodes;
|
|
982
|
-
} catch {
|
|
983
|
-
return hosts;
|
|
984
|
-
}
|
|
985
|
-
const eligible = nodes.filter((node) => node.commands?.includes("acpx.pi.sessions.list.v1") && (!requested || requested.has(`node:${node.nodeId}`))).toSorted((left, right) => nodeLabel(left).localeCompare(nodeLabel(right))).slice(0, MAX_HOSTS - hosts.length);
|
|
986
|
-
const nodeHosts = await Promise.all(eligible.map((node) => listPiNodeHost(runtime, query, node)));
|
|
987
|
-
return [...hosts, ...nodeHosts];
|
|
988
|
-
}
|
|
989
705
|
async function requireLocalPiSession(threadId) {
|
|
990
|
-
const
|
|
706
|
+
const session = (await listLocalPiSessionPage({
|
|
991
707
|
searchTerm: threadId,
|
|
992
|
-
limit:
|
|
993
|
-
})).sessions.find((
|
|
994
|
-
if (!
|
|
995
|
-
return
|
|
708
|
+
limit: 100
|
|
709
|
+
})).sessions.find((candidate) => candidate.threadId === threadId);
|
|
710
|
+
if (!session) throw new Error("Pi session is unavailable");
|
|
711
|
+
return session;
|
|
996
712
|
}
|
|
997
713
|
function currentPiCatalogConfig(api) {
|
|
998
714
|
return api.runtime.config?.current?.() ?? api.config ?? {};
|
|
@@ -1013,8 +729,9 @@ function resolvePiContinuationAvailability(api) {
|
|
|
1013
729
|
message: "Pi CLI is unavailable"
|
|
1014
730
|
};
|
|
1015
731
|
}
|
|
1016
|
-
function listAdoptedPiSessions(api, sessionEntries) {
|
|
732
|
+
function listAdoptedPiSessions(api, agentId, sessionEntries) {
|
|
1017
733
|
return listAdoptedSessionCatalogSessions({
|
|
734
|
+
...agentId ? { agentId } : {},
|
|
1018
735
|
config: currentPiCatalogConfig(api),
|
|
1019
736
|
pluginId: api.id,
|
|
1020
737
|
runtime: api.runtime,
|
|
@@ -1023,198 +740,148 @@ function listAdoptedPiSessions(api, sessionEntries) {
|
|
|
1023
740
|
const acpx = isRecord(entry.pluginExtensions?.acpx) ? entry.pluginExtensions.acpx : void 0;
|
|
1024
741
|
const marker = acpx && isRecord(acpx.piSessionCatalog) ? acpx.piSessionCatalog : void 0;
|
|
1025
742
|
return marker && typeof marker.sourceThreadId === "string" ? {
|
|
1026
|
-
hostId:
|
|
743
|
+
hostId: PI_LOCAL_SESSION_HOST_ID,
|
|
1027
744
|
threadId: marker.sourceThreadId
|
|
1028
745
|
} : void 0;
|
|
1029
746
|
}
|
|
1030
747
|
});
|
|
1031
748
|
}
|
|
1032
|
-
async function
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const config = currentPiCatalogConfig(api);
|
|
1048
|
-
const marker = { sourceThreadId: threadId };
|
|
1049
|
-
return { sessionKey: (await api.runtime.agent.session.createSessionEntry({
|
|
1050
|
-
cfg: config,
|
|
1051
|
-
key: sessionCatalogAdoptedSessionKey(PI_ADOPTED_SESSION_KEY_PREFIX, threadId),
|
|
1052
|
-
agentId: resolveDefaultAgentId(config),
|
|
1053
|
-
recoverMatchingInitialEntry: true,
|
|
1054
|
-
...record.name ? { label: record.name } : {},
|
|
1055
|
-
...record.cwd ? { spawnedCwd: record.cwd } : {},
|
|
1056
|
-
initialEntry: {
|
|
1057
|
-
acpBackendId: ACPX_BACKEND_ID,
|
|
1058
|
-
acpSessionBinding: {
|
|
1059
|
-
acpAgentId: PI_ACP_AGENT_ID,
|
|
1060
|
-
agentSessionId: threadId
|
|
1061
|
-
},
|
|
1062
|
-
pluginExtensions: { acpx: { piSessionCatalog: marker } }
|
|
1063
|
-
},
|
|
1064
|
-
afterCreate: async (entry) => {
|
|
1065
|
-
await importSessionCatalogHistory({
|
|
1066
|
-
catalogId: "pi",
|
|
1067
|
-
threadId,
|
|
1068
|
-
read: async ({ cursor, limit }) => await readPiTranscript(api.runtime, {
|
|
1069
|
-
hostId,
|
|
1070
|
-
threadId,
|
|
1071
|
-
limit,
|
|
1072
|
-
...cursor ? { cursor } : {}
|
|
1073
|
-
}),
|
|
1074
|
-
sessionId: entry.sessionId,
|
|
1075
|
-
sessionKey: entry.key,
|
|
1076
|
-
agentId: entry.agentId,
|
|
1077
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1078
|
-
config
|
|
1079
|
-
});
|
|
1080
|
-
return { pluginExtensions: { acpx: { piSessionCatalog: marker } } };
|
|
1081
|
-
}
|
|
1082
|
-
})).key };
|
|
1083
|
-
},
|
|
1084
|
-
complete: async (continued) => await linkContinuedPiSession(continued.sessionKey, threadId)
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
async function resolveNodePiSession(params) {
|
|
1088
|
-
const record = parseNodeSessionPage(unwrapNodePayload(await params.runtime.nodes.invoke({
|
|
1089
|
-
nodeId: params.nodeId,
|
|
1090
|
-
command: PI_SESSIONS_LIST_COMMAND,
|
|
1091
|
-
params: {
|
|
1092
|
-
searchTerm: params.threadId,
|
|
1093
|
-
limit: MAX_PAGE_LIMIT
|
|
1094
|
-
},
|
|
1095
|
-
timeoutMs: NODE_TIMEOUT_MS,
|
|
1096
|
-
scopes: ["operator.write"]
|
|
1097
|
-
}))).sessions.find((session) => session.threadId === params.threadId);
|
|
1098
|
-
if (!record) throw new Error("Pi session is unavailable");
|
|
1099
|
-
return record;
|
|
1100
|
-
}
|
|
1101
|
-
async function openPiTerminal(params) {
|
|
1102
|
-
const title = `pi --session ${params.threadId.slice(0, 12)}…`;
|
|
1103
|
-
if (params.hostId === LOCAL_HOST_ID) {
|
|
1104
|
-
const record = await requireLocalPiSession(params.threadId);
|
|
1105
|
-
const resolution = resolveNodeHostExecutable("pi", {
|
|
1106
|
-
env: process.env,
|
|
1107
|
-
pathEnv: process.env.PATH ?? "",
|
|
1108
|
-
strategy: "fallback"
|
|
1109
|
-
});
|
|
1110
|
-
if (!resolution) throw new Error("Pi CLI is unavailable");
|
|
1111
|
-
return {
|
|
1112
|
-
kind: "local",
|
|
1113
|
-
argv: [
|
|
1114
|
-
resolution.executable,
|
|
1115
|
-
"--session",
|
|
1116
|
-
params.threadId
|
|
1117
|
-
],
|
|
1118
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1119
|
-
...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
|
|
1120
|
-
title
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1123
|
-
if (!params.hostId.startsWith("node:")) throw new Error("hostId is invalid");
|
|
1124
|
-
const nodeId = params.hostId.slice(5);
|
|
1125
|
-
if (!(await params.runtime.nodes.list()).nodes.find((candidate) => {
|
|
1126
|
-
const commands = candidate.invocableCommands ?? candidate.commands;
|
|
1127
|
-
return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("acpx.pi.sessions.list.v1") === true && commands.includes("acpx.pi.terminal.resume.v1");
|
|
1128
|
-
})) throw new Error("paired-node Pi terminal is unavailable");
|
|
1129
|
-
const record = await resolveNodePiSession({
|
|
1130
|
-
runtime: params.runtime,
|
|
1131
|
-
nodeId,
|
|
1132
|
-
threadId: params.threadId
|
|
1133
|
-
});
|
|
1134
|
-
return {
|
|
1135
|
-
kind: "node",
|
|
1136
|
-
nodeId,
|
|
1137
|
-
command: PI_TERMINAL_RESUME_COMMAND,
|
|
1138
|
-
paramsJSON: JSON.stringify({ threadId: params.threadId }),
|
|
1139
|
-
...record.cwd ? { cwd: record.cwd } : {},
|
|
1140
|
-
title
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
async function readPiTranscript(runtime, request) {
|
|
1144
|
-
const cursor = request.cursor;
|
|
1145
|
-
if (cursor !== void 0 && !isExactPiSessionCursor(cursor)) throw new Error("cursor is invalid");
|
|
1146
|
-
if (request.hostId === LOCAL_HOST_ID) {
|
|
1147
|
-
assertPiLocalAccess(request.hostId, request.allowProcessHomeFallback);
|
|
1148
|
-
return await readLocalPiTranscriptPage({
|
|
1149
|
-
threadId: request.threadId,
|
|
1150
|
-
...request.limit ? { limit: request.limit } : {},
|
|
1151
|
-
...cursor !== void 0 ? { cursor } : {}
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
if (!request.hostId.startsWith("node:")) throw new Error("hostId is invalid");
|
|
1155
|
-
const nodeId = request.hostId.slice(5);
|
|
1156
|
-
const node = (await runtime.nodes.list()).nodes.find((candidate) => candidate.nodeId === nodeId && candidate.connected === true && candidate.commands?.includes("acpx.pi.sessions.read.v1"));
|
|
1157
|
-
if (!node) throw new Error("paired-node Pi session host is unavailable");
|
|
1158
|
-
return {
|
|
1159
|
-
...parseNodeTranscriptPage(unwrapNodePayload(await runtime.nodes.invoke({
|
|
1160
|
-
nodeId,
|
|
1161
|
-
command: PI_SESSION_READ_COMMAND,
|
|
1162
|
-
params: {
|
|
1163
|
-
threadId: request.threadId,
|
|
1164
|
-
...request.limit ? { limit: request.limit } : {},
|
|
1165
|
-
...cursor !== void 0 ? { cursor } : {}
|
|
749
|
+
async function createAdoptedPiSession(params) {
|
|
750
|
+
const config = currentPiCatalogConfig(params.api);
|
|
751
|
+
const marker = { sourceThreadId: params.threadId };
|
|
752
|
+
return { sessionKey: (await params.api.runtime.agent.session.createSessionEntry({
|
|
753
|
+
cfg: config,
|
|
754
|
+
key: sessionCatalogAdoptedSessionKey(PI_ADOPTED_SESSION_KEY_PREFIX, params.threadId),
|
|
755
|
+
agentId: params.agentId,
|
|
756
|
+
recoverMatchingInitialEntry: true,
|
|
757
|
+
...params.session.name ? { label: params.session.name } : {},
|
|
758
|
+
...params.session.cwd ? { spawnedCwd: params.session.cwd } : {},
|
|
759
|
+
initialEntry: {
|
|
760
|
+
acpBackendId: ACPX_BACKEND_ID,
|
|
761
|
+
acpSessionBinding: {
|
|
762
|
+
acpAgentId: PI_ACP_AGENT_ID,
|
|
763
|
+
agentSessionId: params.threadId
|
|
1166
764
|
},
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
765
|
+
pluginExtensions: { acpx: { piSessionCatalog: marker } }
|
|
766
|
+
},
|
|
767
|
+
afterCreate: async (entry) => {
|
|
768
|
+
await importSessionCatalogHistory({
|
|
769
|
+
catalogId: "pi",
|
|
770
|
+
threadId: params.threadId,
|
|
771
|
+
read: async ({ cursor, limit }) => await readLocalPiTranscriptPage({
|
|
772
|
+
threadId: params.threadId,
|
|
773
|
+
limit,
|
|
774
|
+
...cursor ? { cursor } : {}
|
|
775
|
+
}),
|
|
776
|
+
sessionId: entry.sessionId,
|
|
777
|
+
sessionKey: entry.key,
|
|
778
|
+
agentId: entry.agentId,
|
|
779
|
+
...params.session.cwd ? { cwd: params.session.cwd } : {},
|
|
780
|
+
config
|
|
781
|
+
});
|
|
782
|
+
return { pluginExtensions: { acpx: { piSessionCatalog: marker } } };
|
|
783
|
+
}
|
|
784
|
+
})).key };
|
|
1173
785
|
}
|
|
1174
786
|
function assertPiLocalAccess(hostId, allowProcessHomeFallback) {
|
|
1175
|
-
if (hostId ===
|
|
787
|
+
if (hostId === "gateway" && allowProcessHomeFallback === false && piSessionStore(process.env).usesProcessHomeFallback) throw new Error("local Pi sessions are unavailable in isolated state");
|
|
1176
788
|
}
|
|
1177
|
-
async function listPiSessions(
|
|
1178
|
-
return
|
|
789
|
+
async function listPiSessions(params) {
|
|
790
|
+
return await listLocalPiSessionPage(params);
|
|
1179
791
|
}
|
|
1180
|
-
async function readPiSession(
|
|
1181
|
-
return
|
|
1182
|
-
}
|
|
1183
|
-
async function resumePiSession(paramsJSON, io) {
|
|
1184
|
-
if (!io) throw new Error("Pi terminal command requires duplex transport");
|
|
1185
|
-
const params = decodeNodePtyResumeParams(paramsJSON, validatePiThreadId);
|
|
1186
|
-
const record = await requireLocalPiSession(params.threadId);
|
|
1187
|
-
const resolution = resolveNodeHostExecutable("pi", {
|
|
1188
|
-
env: process.env,
|
|
1189
|
-
pathEnv: process.env.PATH ?? process.env.Path ?? "",
|
|
1190
|
-
strategy: "direct"
|
|
1191
|
-
});
|
|
1192
|
-
if (!resolution) throw new Error("Pi CLI is unavailable");
|
|
1193
|
-
return JSON.stringify(await runNodePtyCommand({
|
|
1194
|
-
file: resolution.executable,
|
|
1195
|
-
args: ["--session", params.threadId],
|
|
1196
|
-
cwd: record.cwd,
|
|
1197
|
-
cols: params.cols,
|
|
1198
|
-
rows: params.rows
|
|
1199
|
-
}, io));
|
|
792
|
+
async function readPiSession(params) {
|
|
793
|
+
return await readLocalPiTranscriptPage(params);
|
|
1200
794
|
}
|
|
1201
795
|
function createPiSessionCatalogRuntime(api) {
|
|
1202
|
-
return {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
796
|
+
return createSessionCatalogFamily({
|
|
797
|
+
runtime: api.runtime,
|
|
798
|
+
local: {
|
|
799
|
+
hostId: PI_LOCAL_SESSION_HOST_ID,
|
|
800
|
+
label: "Local Pi",
|
|
801
|
+
available: (query) => {
|
|
802
|
+
const store = piSessionStore(process.env);
|
|
803
|
+
return (query.allowProcessHomeFallback !== false || !store.usesProcessHomeFallback) && piSessionStoreAvailable(process.env, store);
|
|
804
|
+
},
|
|
805
|
+
list: async (query) => await listLocalPiSessionPage({
|
|
806
|
+
limit: query.limitPerHost,
|
|
807
|
+
...query.search ? { searchTerm: query.search } : {},
|
|
808
|
+
cursor: query.cursors?.[PI_LOCAL_SESSION_HOST_ID]
|
|
809
|
+
}),
|
|
810
|
+
read: async (request) => await readLocalPiTranscriptPage({
|
|
811
|
+
threadId: request.threadId,
|
|
812
|
+
...request.limit ? { limit: request.limit } : {},
|
|
813
|
+
...request.cursor !== void 0 ? { cursor: request.cursor } : {}
|
|
814
|
+
}),
|
|
815
|
+
assertAccess: assertPiLocalAccess
|
|
1208
816
|
},
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
817
|
+
node: {
|
|
818
|
+
listCommand: PI_SESSIONS_LIST_COMMAND,
|
|
819
|
+
readCommand: PI_SESSION_READ_COMMAND,
|
|
820
|
+
terminalCommand: PI_TERMINAL_RESUME_COMMAND,
|
|
821
|
+
timeoutMs: NODE_TIMEOUT_MS,
|
|
822
|
+
maxHosts: 100,
|
|
823
|
+
maxPageLimit: 100,
|
|
824
|
+
sessionIdPattern: PI_SESSION_ID_PATTERN
|
|
825
|
+
},
|
|
826
|
+
capabilities: {
|
|
827
|
+
local: () => ({
|
|
828
|
+
canContinue: resolvePiContinuationAvailability(api).available,
|
|
829
|
+
canOpenTerminal: resolveNodeHostExecutable("pi", {
|
|
830
|
+
env: process.env,
|
|
831
|
+
pathEnv: process.env.PATH ?? "",
|
|
832
|
+
strategy: "fallback"
|
|
833
|
+
}) !== void 0
|
|
834
|
+
}),
|
|
835
|
+
node: (node) => {
|
|
836
|
+
return {
|
|
837
|
+
canContinue: false,
|
|
838
|
+
canOpenTerminal: (node.invocableCommands ?? node.commands)?.includes(PI_TERMINAL_RESUME_COMMAND) === true
|
|
839
|
+
};
|
|
840
|
+
},
|
|
841
|
+
project: (session, capabilities) => ({
|
|
842
|
+
...session,
|
|
843
|
+
canContinue: capabilities.canContinue && session.canContinue,
|
|
844
|
+
canOpenTerminal: capabilities.canOpenTerminal
|
|
845
|
+
})
|
|
846
|
+
},
|
|
847
|
+
messages: {
|
|
848
|
+
invalidNodeCursor: "Pi node returned an invalid cursor",
|
|
849
|
+
invalidNodeSessionPage: "Pi node returned an invalid session page",
|
|
850
|
+
invalidNodeTranscriptPage: "Pi node returned an invalid transcript page",
|
|
851
|
+
invalidHostId: "Pi session catalog hostId is invalid",
|
|
852
|
+
localReadFailed: "Local Pi sessions are unavailable",
|
|
853
|
+
nodeInvokeFailed: "Paired node Pi sessions are unavailable",
|
|
854
|
+
nodeReadUnavailable: "paired-node Pi session host is unavailable",
|
|
855
|
+
nodeTerminalUnavailable: "paired-node Pi terminal is unavailable",
|
|
856
|
+
sessionUnavailable: "Pi session is unavailable"
|
|
857
|
+
},
|
|
858
|
+
continuation: {
|
|
859
|
+
resolveAgentId: (agentId) => resolveSessionAgentIds({
|
|
860
|
+
config: api.config,
|
|
861
|
+
agentId
|
|
862
|
+
}).sessionAgentId,
|
|
863
|
+
availability: () => resolvePiContinuationAvailability(api),
|
|
864
|
+
listAdopted: (agentId, sessionEntries) => listAdoptedPiSessions(api, agentId, sessionEntries),
|
|
865
|
+
loadSession: requireLocalPiSession,
|
|
866
|
+
validateSession: (session) => {
|
|
867
|
+
if (!session.canContinue) throw new Error("Pi session is outside the session store supported by pi-acp");
|
|
868
|
+
},
|
|
869
|
+
create: async (params) => await createAdoptedPiSession({
|
|
870
|
+
api,
|
|
871
|
+
...params
|
|
872
|
+
}),
|
|
873
|
+
complete: async (continued, threadId) => await linkContinuedPiSession(continued.sessionKey, threadId),
|
|
874
|
+
nodeReadOnlyMessage: "paired-node Pi session rows are view-only"
|
|
875
|
+
},
|
|
876
|
+
terminal: {
|
|
877
|
+
executable: "pi",
|
|
878
|
+
args: (threadId) => ["--session", threadId],
|
|
879
|
+
title: (threadId) => `pi --session ${threadId.slice(0, 12)}…`,
|
|
880
|
+
requireLocalSession: requireLocalPiSession,
|
|
881
|
+
unavailableMessage: "Pi CLI is unavailable"
|
|
882
|
+
},
|
|
883
|
+
checkUpstreamActivity: (probes, policy) => checkPiUpstreamActivity(probes.filter((probe) => probe.hostId !== "gateway" || policy?.allowProcessHomeFallback !== false || !piSessionStore(process.env).usesProcessHomeFallback))
|
|
884
|
+
}, isExactPiSessionCursor);
|
|
1218
885
|
}
|
|
1219
886
|
//#endregion
|
|
1220
|
-
export { createPiSessionCatalogRuntime, listPiSessions, readPiSession,
|
|
887
|
+
export { createPiSessionCatalogRuntime, listPiSessions, readPiSession, requireLocalPiSession };
|
|
@@ -6,6 +6,9 @@ import path from "node:path";
|
|
|
6
6
|
const PI_SESSIONS_LIST_COMMAND = "acpx.pi.sessions.list.v1";
|
|
7
7
|
const PI_SESSION_READ_COMMAND = "acpx.pi.sessions.read.v1";
|
|
8
8
|
const PI_TERMINAL_RESUME_COMMAND = "acpx.pi.terminal.resume.v1";
|
|
9
|
+
const PI_SESSIONS_CAPABILITY = "pi-sessions";
|
|
10
|
+
const PI_LOCAL_SESSION_HOST_ID = "gateway";
|
|
11
|
+
const PI_SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
|
|
9
12
|
//#endregion
|
|
10
13
|
//#region extensions/acpx/src/pi-session-paths.ts
|
|
11
14
|
function piHome(env) {
|
|
@@ -78,4 +81,4 @@ function piSessionStoreAvailable(env, store) {
|
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
//#endregion
|
|
81
|
-
export {
|
|
84
|
+
export { PI_SESSIONS_CAPABILITY as a, PI_SESSION_READ_COMMAND as c, PI_LOCAL_SESSION_HOST_ID as i, PI_TERMINAL_RESUME_COMMAND as l, piSessionStore as n, PI_SESSIONS_LIST_COMMAND as o, piSessionStoreAvailable as r, PI_SESSION_ID_PATTERN as s, piAcpSessionStoreRoot as t };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { t as AcpxPluginConfigSchema } from "./config-schema-DN_uAi4R.js";
|
|
2
2
|
import { u as readAcpxProcessLeaseIdentity, v as splitCommandParts } from "./process-lease-Cwvj7WGe.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
|
|
5
4
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
5
|
import fs from "node:fs";
|
|
7
6
|
import path from "node:path";
|
|
8
|
-
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
7
|
+
import { isPidAlive, runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
9
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
|
|
10
10
|
//#region extensions/acpx/src/codex-adapter.ts
|
|
11
11
|
const CODEX_ACP_PACKAGE = "@agentclientprotocol/codex-acp";
|
|
12
12
|
const CODEX_ACP_BIN = "codex-acp";
|
|
@@ -355,14 +355,6 @@ function collectProcessTree(processes, rootPid) {
|
|
|
355
355
|
function uniquePids(processes) {
|
|
356
356
|
return Array.from(new Set(processes.map((processInfo) => processInfo.pid).filter((pid) => Number.isInteger(pid) && pid > 0 && pid !== process.pid)));
|
|
357
357
|
}
|
|
358
|
-
function isProcessAlive(pid) {
|
|
359
|
-
try {
|
|
360
|
-
process.kill(pid, 0);
|
|
361
|
-
return true;
|
|
362
|
-
} catch {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
358
|
async function terminatePids(pids, deps) {
|
|
367
359
|
const killProcess = deps?.killProcess ?? ((pid, signal) => process.kill(pid, signal));
|
|
368
360
|
const sleep = deps?.sleep ?? ((ms) => new Promise((resolve) => {
|
|
@@ -375,7 +367,7 @@ async function terminatePids(pids, deps) {
|
|
|
375
367
|
} catch {}
|
|
376
368
|
if (terminated.length === 0) return terminated;
|
|
377
369
|
await sleep(750);
|
|
378
|
-
for (const pid of terminated) if (deps?.killProcess ||
|
|
370
|
+
for (const pid of terminated) if (deps?.killProcess || isPidAlive(pid)) try {
|
|
379
371
|
killProcess(pid, "SIGKILL");
|
|
380
372
|
} catch {}
|
|
381
373
|
return terminated;
|
|
@@ -1,137 +1,14 @@
|
|
|
1
1
|
import { getAcpRuntimeBackend, registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "openclaw/plugin-sdk/acp-runtime-backend";
|
|
2
2
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//#region extensions/acpx/src/runtime-turn.ts
|
|
6
|
-
/**
|
|
7
|
-
* ACPX turn adapters. Modern runtimes can expose startTurn directly; legacy
|
|
8
|
-
* runtimes that only stream runTurn events are adapted to the newer contract.
|
|
9
|
-
*/
|
|
10
|
-
function isCancellationStopReason(stopReason) {
|
|
11
|
-
return stopReason === "cancel" || stopReason === "cancelled" || stopReason === "manual-cancel";
|
|
12
|
-
}
|
|
13
|
-
var LegacyRunTurnEventQueue = class {
|
|
14
|
-
constructor() {
|
|
15
|
-
this.items = [];
|
|
16
|
-
this.waits = [];
|
|
17
|
-
this.closed = false;
|
|
18
|
-
}
|
|
19
|
-
push(item) {
|
|
20
|
-
if (this.closed) return;
|
|
21
|
-
const waiter = this.waits.shift();
|
|
22
|
-
if (waiter) {
|
|
23
|
-
waiter.resolve(item);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
this.items.push(item);
|
|
27
|
-
}
|
|
28
|
-
clear() {
|
|
29
|
-
this.items.length = 0;
|
|
30
|
-
}
|
|
31
|
-
close() {
|
|
32
|
-
if (this.closed) return;
|
|
33
|
-
this.closed = true;
|
|
34
|
-
for (const waiter of this.waits.splice(0)) waiter.resolve(null);
|
|
35
|
-
}
|
|
36
|
-
fail(error) {
|
|
37
|
-
if (this.closed) return;
|
|
38
|
-
this.error = error;
|
|
39
|
-
this.closed = true;
|
|
40
|
-
for (const waiter of this.waits.splice(0)) waiter.reject(error);
|
|
41
|
-
}
|
|
42
|
-
async next() {
|
|
43
|
-
const item = this.items.shift();
|
|
44
|
-
if (item) return item;
|
|
45
|
-
if (this.error) throw toErrorObject(this.error, "Non-Error thrown");
|
|
46
|
-
if (this.closed) return null;
|
|
47
|
-
return await new Promise((resolve, reject) => {
|
|
48
|
-
this.waits.push({
|
|
49
|
-
resolve,
|
|
50
|
-
reject
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
async *iterate() {
|
|
55
|
-
for (;;) {
|
|
56
|
-
const item = await this.next();
|
|
57
|
-
if (!item) return;
|
|
58
|
-
yield item;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
function legacyRunTurnAsStartTurn(runtime, input) {
|
|
63
|
-
const result = createDeferred();
|
|
64
|
-
result.promise.catch(() => {});
|
|
65
|
-
const queue = new LegacyRunTurnEventQueue();
|
|
66
|
-
let resultSettled = false;
|
|
67
|
-
const settleResult = (next) => {
|
|
68
|
-
if (resultSettled) return;
|
|
69
|
-
resultSettled = true;
|
|
70
|
-
result.resolve(next);
|
|
71
|
-
};
|
|
72
|
-
(async () => {
|
|
73
|
-
try {
|
|
74
|
-
for await (const event of runtime.runTurn(input)) {
|
|
75
|
-
if (event.type === "done") {
|
|
76
|
-
settleResult({
|
|
77
|
-
status: event.status ?? (isCancellationStopReason(event.stopReason) ? "cancelled" : "completed"),
|
|
78
|
-
...event.stopReason ? { stopReason: event.stopReason } : {}
|
|
79
|
-
});
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (event.type === "error") {
|
|
83
|
-
settleResult({
|
|
84
|
-
status: "failed",
|
|
85
|
-
error: {
|
|
86
|
-
message: event.message,
|
|
87
|
-
...event.code ? { code: event.code } : {},
|
|
88
|
-
...event.detailCode ? { detailCode: event.detailCode } : {},
|
|
89
|
-
...event.retryable === void 0 ? {} : { retryable: event.retryable }
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
queue.push(event);
|
|
95
|
-
}
|
|
96
|
-
settleResult({
|
|
97
|
-
status: "failed",
|
|
98
|
-
error: {
|
|
99
|
-
code: "ACP_TURN_FAILED",
|
|
100
|
-
message: "ACP turn ended without a terminal done event."
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
} catch (error) {
|
|
104
|
-
result.reject(error);
|
|
105
|
-
queue.fail(error);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
queue.close();
|
|
109
|
-
})();
|
|
110
|
-
return {
|
|
111
|
-
requestId: input.requestId,
|
|
112
|
-
events: queue.iterate(),
|
|
113
|
-
result: result.promise,
|
|
114
|
-
async cancel(inputArgs) {
|
|
115
|
-
await runtime.cancel({
|
|
116
|
-
handle: input.handle,
|
|
117
|
-
reason: inputArgs?.reason
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
async closeStream() {
|
|
121
|
-
queue.clear();
|
|
122
|
-
queue.close();
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
/** Start an ACP turn, adapting legacy runTurn-only runtimes when needed. */
|
|
127
|
-
function startRuntimeTurn(runtime, input) {
|
|
128
|
-
return runtime.startTurn?.(input) ?? legacyRunTurnAsStartTurn(runtime, input);
|
|
129
|
-
}
|
|
130
|
-
/** Start an ACP turn through a lazy runtime resolver. */
|
|
3
|
+
//#region extensions/acpx/src/runtime-proxy.ts
|
|
4
|
+
/** Start an ACP turn through a lazy runtime resolver without awaiting resolution up front. */
|
|
131
5
|
function lazyStartRuntimeTurn(resolveRuntime, input) {
|
|
132
|
-
const turnPromise = resolveRuntime().then((runtime) =>
|
|
6
|
+
const turnPromise = resolveRuntime().then((runtime) => runtime.startTurn(input));
|
|
133
7
|
return {
|
|
134
8
|
requestId: input.requestId,
|
|
9
|
+
get promptStarted() {
|
|
10
|
+
return turnPromise.then((turn) => turn.promptStarted);
|
|
11
|
+
},
|
|
135
12
|
events: { async *[Symbol.asyncIterator]() {
|
|
136
13
|
yield* (await turnPromise).events;
|
|
137
14
|
} },
|
|
@@ -144,8 +21,6 @@ function lazyStartRuntimeTurn(resolveRuntime, input) {
|
|
|
144
21
|
}
|
|
145
22
|
};
|
|
146
23
|
}
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region extensions/acpx/src/runtime-proxy.ts
|
|
149
24
|
/** Create an ACP runtime facade backed by an async runtime resolver. */
|
|
150
25
|
function createLazyAcpRuntimeProxy(resolveRuntime) {
|
|
151
26
|
return {
|
|
@@ -159,25 +34,22 @@ function createLazyAcpRuntimeProxy(resolveRuntime) {
|
|
|
159
34
|
yield* (await resolveRuntime()).runTurn(input);
|
|
160
35
|
},
|
|
161
36
|
async getCapabilities(input) {
|
|
162
|
-
return await (await resolveRuntime()).getCapabilities
|
|
37
|
+
return await (await resolveRuntime()).getCapabilities(input);
|
|
163
38
|
},
|
|
164
39
|
async getStatus(input) {
|
|
165
|
-
return await (await resolveRuntime()).getStatus
|
|
40
|
+
return await (await resolveRuntime()).getStatus(input);
|
|
166
41
|
},
|
|
167
42
|
async setMode(input) {
|
|
168
|
-
await (await resolveRuntime()).setMode
|
|
43
|
+
await (await resolveRuntime()).setMode(input);
|
|
169
44
|
},
|
|
170
45
|
async setConfigOption(input) {
|
|
171
|
-
await (await resolveRuntime()).setConfigOption
|
|
46
|
+
await (await resolveRuntime()).setConfigOption(input);
|
|
172
47
|
},
|
|
173
48
|
async doctor() {
|
|
174
|
-
return await (await resolveRuntime()).doctor
|
|
175
|
-
ok: true,
|
|
176
|
-
message: "ok"
|
|
177
|
-
};
|
|
49
|
+
return await (await resolveRuntime()).doctor();
|
|
178
50
|
},
|
|
179
51
|
async prepareFreshSession(input) {
|
|
180
|
-
await (await resolveRuntime()).prepareFreshSession
|
|
52
|
+
await (await resolveRuntime()).prepareFreshSession(input);
|
|
181
53
|
},
|
|
182
54
|
async cancel(input) {
|
|
183
55
|
await (await resolveRuntime()).cancel(input);
|
|
@@ -194,7 +66,7 @@ function createLazyAcpRuntimeProxy(resolveRuntime) {
|
|
|
194
66
|
* immediately, then imports the heavier service only when a session needs it.
|
|
195
67
|
*/
|
|
196
68
|
const ACPX_BACKEND_ID = "acpx";
|
|
197
|
-
const loadServiceModule = createLazyRuntimeModule(() => import("./service-
|
|
69
|
+
const loadServiceModule = createLazyRuntimeModule(() => import("./service-PRlUtXMf.js"));
|
|
198
70
|
function unregisterOwnedRuntime(runtime) {
|
|
199
71
|
if (runtime && getAcpRuntimeBackend(ACPX_BACKEND_ID)?.runtime === runtime) unregisterAcpRuntimeBackend(ACPX_BACKEND_ID);
|
|
200
72
|
}
|
package/dist/register.runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createAcpxRuntimeService } from "./register.runtime-
|
|
1
|
+
import { t as createAcpxRuntimeService } from "./register.runtime-Dj29TKFy.js";
|
|
2
2
|
export { createAcpxRuntimeService };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { d as withAcpxLeaseEnvironment, i as createAcpxProcessLeaseId, o as hashAcpxProcessCommand, t as ACPX_PROBE_LEASE_SESSION_KEY, u as readAcpxProcessLeaseIdentity, v as splitCommandParts } from "./process-lease-Cwvj7WGe.js";
|
|
2
2
|
import { AcpRuntimeError } from "./runtime-api.js";
|
|
3
|
-
import { d as OPENCLAW_CODEX_CONFIG_ARG, l as CODEX_ACP_PACKAGE, n as cleanupOpenClawOwnedAcpxProcessTree, r as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxPendingLease } from "./process-reaper-
|
|
3
|
+
import { d as OPENCLAW_CODEX_CONFIG_ARG, l as CODEX_ACP_PACKAGE, n as cleanupOpenClawOwnedAcpxProcessTree, r as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxPendingLease } from "./process-reaper-DduWm_7N.js";
|
|
4
4
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
5
5
|
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
6
|
import path, { resolve } from "node:path";
|
|
@@ -940,6 +940,9 @@ var AcpxRuntime = class {
|
|
|
940
940
|
});
|
|
941
941
|
return {
|
|
942
942
|
requestId: input.requestId,
|
|
943
|
+
get promptStarted() {
|
|
944
|
+
return turnPromise.then(({ turn }) => turn.promptStarted);
|
|
945
|
+
},
|
|
943
946
|
events: { async *[Symbol.asyncIterator]() {
|
|
944
947
|
const { command, turn } = await turnPromise;
|
|
945
948
|
try {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { n as createLazyAcpRuntimeProxy } from "./register.runtime-
|
|
1
|
+
import { n as createLazyAcpRuntimeProxy } from "./register.runtime-Dj29TKFy.js";
|
|
2
2
|
import "./config-schema-DN_uAi4R.js";
|
|
3
3
|
import { _ as quoteCommandPart, a as createAcpxProcessLeaseStore, f as ACPX_GATEWAY_INSTANCE_KEY, g as normalizeAcpxGatewayInstanceRecord, l as openAcpxProcessLeaseStateStore, n as OPENCLAW_ACPX_LEASE_ID_ARG, p as ACPX_GATEWAY_INSTANCE_NAMESPACE, r as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, v as splitCommandParts } from "./process-lease-Cwvj7WGe.js";
|
|
4
|
-
import { a as resolveAcpxPluginConfig, c as CODEX_ACP_BIN, d as OPENCLAW_CODEX_CONFIG_ARG, i as reapStaleOpenClawOwnedAcpxOrphans, l as CODEX_ACP_PACKAGE, n as cleanupOpenClawOwnedAcpxProcessTree, o as resolveAcpxPluginRoot, s as toAcpMcpServers, t as cleanupOpenClawOwnedAcpxPendingLease, u as LEGACY_CODEX_ACP_PACKAGE } from "./process-reaper-
|
|
4
|
+
import { a as resolveAcpxPluginConfig, c as CODEX_ACP_BIN, d as OPENCLAW_CODEX_CONFIG_ARG, i as reapStaleOpenClawOwnedAcpxOrphans, l as CODEX_ACP_PACKAGE, n as cleanupOpenClawOwnedAcpxProcessTree, o as resolveAcpxPluginRoot, s as toAcpMcpServers, t as cleanupOpenClawOwnedAcpxPendingLease, u as LEGACY_CODEX_ACP_PACKAGE } from "./process-reaper-DduWm_7N.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
|
7
7
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
8
|
-
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
9
8
|
import { isRecord, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
10
9
|
import fs from "node:fs";
|
|
11
10
|
import os from "node:os";
|
|
@@ -13,6 +12,7 @@ import path from "node:path";
|
|
|
13
12
|
import fs$1 from "node:fs/promises";
|
|
14
13
|
import { randomUUID } from "node:crypto";
|
|
15
14
|
import { inspect } from "node:util";
|
|
15
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
16
16
|
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
|
17
17
|
import { parse, stringify } from "smol-toml";
|
|
18
18
|
//#region extensions/acpx/src/codex-trust-config.ts
|
|
@@ -1021,7 +1021,7 @@ async function prepareAcpxCodexAuthConfig(params) {
|
|
|
1021
1021
|
*/
|
|
1022
1022
|
const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
|
|
1023
1023
|
const SKIP_RUNTIME_PROBE_ENV = "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE";
|
|
1024
|
-
const loadRuntimeModule = createLazyRuntimeModule(() => import("./runtime-
|
|
1024
|
+
const loadRuntimeModule = createLazyRuntimeModule(() => import("./runtime-BmOxa15I.js"));
|
|
1025
1025
|
/** Convert ACPX timeout seconds into timer-safe milliseconds. */
|
|
1026
1026
|
function resolveAcpxTimerTimeoutMs(timeoutSeconds) {
|
|
1027
1027
|
if (timeoutSeconds === void 0) return;
|
|
@@ -1046,6 +1046,7 @@ function createLazyDefaultRuntime(params) {
|
|
|
1046
1046
|
openclawToolsMcpBridgeEnabled: params.pluginConfig.openClawToolsMcpBridge,
|
|
1047
1047
|
permissionMode: params.pluginConfig.permissionMode,
|
|
1048
1048
|
nonInteractivePermissions: params.pluginConfig.nonInteractivePermissions,
|
|
1049
|
+
elicitationModes: ["form", "url"],
|
|
1049
1050
|
timeoutMs: resolveAcpxTimerTimeoutMs(params.pluginConfig.timeoutSeconds)
|
|
1050
1051
|
});
|
|
1051
1052
|
return runtime;
|
|
@@ -1260,7 +1261,7 @@ function createAcpxRuntimeService(params) {
|
|
|
1260
1261
|
ctx.logger.info("embedded acpx runtime backend ready");
|
|
1261
1262
|
return;
|
|
1262
1263
|
}
|
|
1263
|
-
const doctorReport = await measureAcpxStartup(ctx, "probe.doctor", () => startedRuntime.doctor
|
|
1264
|
+
const doctorReport = await measureAcpxStartup(ctx, "probe.doctor", () => startedRuntime.doctor());
|
|
1264
1265
|
if (currentRevision !== lifecycleRevision) return;
|
|
1265
1266
|
detailAcpxStartup(ctx, "probe.result", [["healthyCount", 0]]);
|
|
1266
1267
|
ctx.logger.warn(`embedded acpx runtime backend probe failed: ${doctorReport ? formatDoctorFailureMessage(doctorReport) : "backend remained unhealthy after probe"}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/acpx",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.9.1-beta.1",
|
|
4
4
|
"description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@agentclientprotocol/claude-agent-acp": "0.
|
|
12
|
-
"@agentclientprotocol/codex-acp": "1.
|
|
13
|
-
"acpx": "0.13.
|
|
14
|
-
"smol-toml": "1.
|
|
11
|
+
"@agentclientprotocol/claude-agent-acp": "0.70.0",
|
|
12
|
+
"@agentclientprotocol/codex-acp": "1.6.0",
|
|
13
|
+
"acpx": "0.13.1",
|
|
14
|
+
"smol-toml": "1.8.0",
|
|
15
15
|
"zod": "4.4.3"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
"compat": {
|
|
46
|
-
"pluginApi": ">=2026.
|
|
46
|
+
"pluginApi": ">=2026.9.1-beta.1"
|
|
47
47
|
},
|
|
48
48
|
"build": {
|
|
49
|
-
"openclawVersion": "2026.
|
|
49
|
+
"openclawVersion": "2026.9.1-beta.1",
|
|
50
50
|
"staticAssets": [
|
|
51
51
|
{
|
|
52
52
|
"source": "./src/runtime-internals/mcp-proxy.mjs",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"skills/**"
|
|
75
75
|
],
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"openclaw": ">=2026.
|
|
77
|
+
"openclaw": ">=2026.9.1-beta.1"
|
|
78
78
|
},
|
|
79
79
|
"peerDependenciesMeta": {
|
|
80
80
|
"openclaw": {
|
|
@@ -209,8 +209,8 @@ ${ACPX_CMD} codex sessions close oc-codex-<conversationId>
|
|
|
209
209
|
Defaults are:
|
|
210
210
|
|
|
211
211
|
- `openclaw -> openclaw acp`
|
|
212
|
-
- `claude -> bundled @agentclientprotocol/claude-agent-acp@0.
|
|
213
|
-
- `codex -> bundled @agentclientprotocol/codex-acp@1.
|
|
212
|
+
- `claude -> bundled @agentclientprotocol/claude-agent-acp@0.70.0`
|
|
213
|
+
- `codex -> bundled @agentclientprotocol/codex-acp@1.6.0 through OpenClaw's isolated CODEX_HOME wrapper`
|
|
214
214
|
- `copilot -> copilot --acp --stdio`
|
|
215
215
|
- `cursor -> cursor-agent acp`
|
|
216
216
|
- `droid -> droid exec --output-format acp`
|