@openclaw/acpx 2026.8.1-beta.2 → 2026.8.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 +43 -53
- package/dist/{pi-session-catalog-runtime-BiTQtT7s.js → pi-session-catalog-runtime-g6A6ADEf.js} +200 -531
- 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-DFRkXzZ_.js} +13 -141
- package/dist/register.runtime.js +1 -1
- package/dist/{runtime-BmzHUTK8.js → runtime-mnaLaFBR.js} +20 -20
- package/dist/{service-LTeZyc7q.js → service-BFWEiHbQ.js} +6 -5
- package/package.json +8 -8
- package/skills/acp-router/SKILL.md +2 -2
package/dist/{pi-session-catalog-runtime-BiTQtT7s.js → pi-session-catalog-runtime-g6A6ADEf.js}
RENAMED
|
@@ -1,15 +1,17 @@
|
|
|
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 { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
12
12
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
13
|
+
import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
|
|
14
|
+
import { isPathStrictlyInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
13
15
|
//#region extensions/acpx/src/pi-session-timestamp.ts
|
|
14
16
|
/** Preserve Pi JSONL's date-first string contract while accepting numeric millisecond values. */
|
|
15
17
|
function parsePiSessionTimestampMs(value) {
|
|
@@ -26,7 +28,7 @@ const APPEND_PROOF_EDGE_BYTES = 64 * 1024;
|
|
|
26
28
|
const IO_CONCURRENCY = 8;
|
|
27
29
|
const PI_FILE_CANDIDATE_CACHE_TTL_MS = 32e3;
|
|
28
30
|
const PI_FILE_CANDIDATE_CACHE_MAX_ENTRIES = 8;
|
|
29
|
-
const SESSION_ID_PATTERN
|
|
31
|
+
const SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
|
|
30
32
|
const summaryCache = /* @__PURE__ */ new Map();
|
|
31
33
|
const threadFileCache = /* @__PURE__ */ new Map();
|
|
32
34
|
const piFileCandidateCache = /* @__PURE__ */ new Map();
|
|
@@ -94,38 +96,30 @@ async function realpathOrResolve(value) {
|
|
|
94
96
|
return path.resolve(value);
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
async function mapConcurrent(values, limit, mapper) {
|
|
98
|
-
const results = [];
|
|
99
|
-
results.length = values.length;
|
|
100
|
-
let nextIndex = 0;
|
|
101
|
-
const workers = Array.from({ length: Math.min(limit, values.length) }, async () => {
|
|
102
|
-
while (nextIndex < values.length) {
|
|
103
|
-
const index = nextIndex++;
|
|
104
|
-
results[index] = await mapper(values[index]);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
await Promise.all(workers);
|
|
108
|
-
return results;
|
|
109
|
-
}
|
|
110
99
|
async function scanPiFileCandidates(env) {
|
|
111
100
|
const { root, files } = await discoverPiSessionFiles(env);
|
|
112
101
|
const configuredAcpRoot = piAcpSessionStoreRoot(env);
|
|
113
102
|
const acpRoot = configuredAcpRoot ? await realpathOrResolve(configuredAcpRoot) : void 0;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
103
|
+
const { results: candidates } = await runTasksWithConcurrency({
|
|
104
|
+
tasks: files.map((file) => async () => {
|
|
105
|
+
try {
|
|
106
|
+
const stats = await fs$1.stat(file);
|
|
107
|
+
return stats.isFile() ? {
|
|
108
|
+
file,
|
|
109
|
+
storeRoot: root,
|
|
110
|
+
identity: `${String(stats.dev)}:${String(stats.ino)}:${String(stats.birthtimeMs)}`,
|
|
111
|
+
mtimeMs: stats.mtimeMs,
|
|
112
|
+
size: stats.size,
|
|
113
|
+
resumable: acpRoot ? isPathStrictlyInside(acpRoot, file) : false
|
|
114
|
+
} : void 0;
|
|
115
|
+
} catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
limit: IO_CONCURRENCY,
|
|
120
|
+
throwOnError: true
|
|
121
|
+
});
|
|
122
|
+
return candidates.filter((candidate) => candidate !== void 0).toSorted((left, right) => right.mtimeMs - left.mtimeMs);
|
|
129
123
|
}
|
|
130
124
|
async function piFileCandidates(env) {
|
|
131
125
|
const store = piSessionStore(env);
|
|
@@ -155,10 +149,6 @@ async function piFileCandidates(env) {
|
|
|
155
149
|
throw error;
|
|
156
150
|
}
|
|
157
151
|
}
|
|
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
152
|
function parsePiJsonLines(content) {
|
|
163
153
|
return content.split(/\r?\n/u).flatMap((line) => {
|
|
164
154
|
if (!line.trim()) return [];
|
|
@@ -277,7 +267,7 @@ async function readPiSessionSummary(candidate) {
|
|
|
277
267
|
const { header, name, firstMessage } = projectedState;
|
|
278
268
|
const version = header?.type === "session" && typeof header.version === "number" ? header.version : 1;
|
|
279
269
|
const threadId = header?.type === "session" ? normalizeBoundedOptionalString(header.id, 256) : void 0;
|
|
280
|
-
if (header && threadId && SESSION_ID_PATTERN
|
|
270
|
+
if (header && threadId && SESSION_ID_PATTERN.test(threadId)) {
|
|
281
271
|
const cwd = normalizeBoundedOptionalString(header.cwd, 4096);
|
|
282
272
|
const createdAt = parsePiSessionTimestampMs(header.timestamp);
|
|
283
273
|
summary = {
|
|
@@ -326,7 +316,11 @@ async function listPiSummaryPage(env, params) {
|
|
|
326
316
|
const matches = [];
|
|
327
317
|
const needle = params.searchTerm?.toLocaleLowerCase();
|
|
328
318
|
for (let index = 0; index < candidates.length && matches.length < target; index += SUMMARY_SCAN_BATCH_SIZE) {
|
|
329
|
-
const summaries = await
|
|
319
|
+
const { results: summaries } = await runTasksWithConcurrency({
|
|
320
|
+
tasks: candidates.slice(index, index + SUMMARY_SCAN_BATCH_SIZE).map((candidate) => () => readPiSessionSummary(candidate)),
|
|
321
|
+
limit: IO_CONCURRENCY,
|
|
322
|
+
throwOnError: true
|
|
323
|
+
});
|
|
330
324
|
for (const summary of summaries) if (summary && summaryMatches(summary, needle)) {
|
|
331
325
|
matches.push(summary);
|
|
332
326
|
if (matches.length >= target) break;
|
|
@@ -340,7 +334,12 @@ async function listPiSummaryPage(env, params) {
|
|
|
340
334
|
async function findPiSummary(threadId, env) {
|
|
341
335
|
const candidates = await piFileCandidates(env);
|
|
342
336
|
for (let index = 0; index < candidates.length; index += SUMMARY_SCAN_BATCH_SIZE) {
|
|
343
|
-
const
|
|
337
|
+
const { results: summaries } = await runTasksWithConcurrency({
|
|
338
|
+
tasks: candidates.slice(index, index + SUMMARY_SCAN_BATCH_SIZE).map((candidate) => () => readPiSessionSummary(candidate)),
|
|
339
|
+
limit: IO_CONCURRENCY,
|
|
340
|
+
throwOnError: true
|
|
341
|
+
});
|
|
342
|
+
const match = summaries.find((summary) => summary?.threadId === threadId);
|
|
344
343
|
if (match) return match;
|
|
345
344
|
}
|
|
346
345
|
}
|
|
@@ -380,86 +379,8 @@ async function readPiSessionById(threadId, env) {
|
|
|
380
379
|
}
|
|
381
380
|
//#endregion
|
|
382
381
|
//#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
382
|
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
|
-
}
|
|
383
|
+
const isExactPiSessionCursor = sessionCatalogPaging.isExactCursor;
|
|
463
384
|
function textFromContent$1(content) {
|
|
464
385
|
if (typeof content === "string") return content;
|
|
465
386
|
if (!Array.isArray(content)) return "";
|
|
@@ -473,44 +394,20 @@ function textFromContent$1(content) {
|
|
|
473
394
|
return [];
|
|
474
395
|
}).join("\n");
|
|
475
396
|
}
|
|
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
|
-
}
|
|
397
|
+
const PI_PARAMETER_MESSAGES = {
|
|
398
|
+
listNotObject: "Pi session list parameters must be an object",
|
|
399
|
+
unknownListParameter: (key) => `unknown Pi session list parameter: ${key}`,
|
|
400
|
+
invalidSearchTerm: "searchTerm is invalid",
|
|
401
|
+
readNotObject: "Pi session read parameters must be an object",
|
|
402
|
+
unknownReadParameter: (key) => `unknown Pi session read parameter: ${key}`,
|
|
403
|
+
invalidThreadId: "threadId is invalid"
|
|
404
|
+
};
|
|
511
405
|
async function listLocalPiSessionPage(value) {
|
|
512
|
-
const params = parseListParams(value
|
|
513
|
-
|
|
406
|
+
const params = sessionCatalogPaging.parseListParams(value, {
|
|
407
|
+
searchMaxLength: MAX_SEARCH_LENGTH,
|
|
408
|
+
messages: PI_PARAMETER_MESSAGES
|
|
409
|
+
});
|
|
410
|
+
const offset = sessionCatalogPaging.decodeCursor(params.cursor);
|
|
514
411
|
const { summaries, hasMore } = await listPiSummaryPage(process.env, {
|
|
515
412
|
offset,
|
|
516
413
|
limit: params.limit,
|
|
@@ -519,7 +416,7 @@ async function listLocalPiSessionPage(value) {
|
|
|
519
416
|
const page = summaries.map(({ file: _file, version: _version, ...session }) => session);
|
|
520
417
|
return {
|
|
521
418
|
sessions: page,
|
|
522
|
-
...hasMore ? { nextCursor: encodeCursor(offset + page.length) } : {}
|
|
419
|
+
...hasMore ? { nextCursor: sessionCatalogPaging.encodeCursor(offset + page.length) } : {}
|
|
523
420
|
};
|
|
524
421
|
}
|
|
525
422
|
function isoTimestamp(message, entry) {
|
|
@@ -670,11 +567,16 @@ function piTranscriptItems(entries) {
|
|
|
670
567
|
});
|
|
671
568
|
}
|
|
672
569
|
async function readLocalPiTranscriptPage(value) {
|
|
673
|
-
const params = parseReadParams(value
|
|
674
|
-
|
|
675
|
-
|
|
570
|
+
const params = sessionCatalogPaging.parseReadParams(value, {
|
|
571
|
+
threadIdMaxLength: 256,
|
|
572
|
+
threadIdPattern: PI_SESSION_ID_PATTERN,
|
|
573
|
+
messages: PI_PARAMETER_MESSAGES
|
|
574
|
+
});
|
|
575
|
+
const offset = sessionCatalogPaging.decodeCursor(params.cursor);
|
|
576
|
+
const items = piTranscriptItems(await readPiSessionById(params.threadId, process.env));
|
|
577
|
+
const page = sessionCatalogPaging.boundTranscriptPage(items, params.limit, offset);
|
|
676
578
|
return {
|
|
677
|
-
hostId:
|
|
579
|
+
hostId: PI_LOCAL_SESSION_HOST_ID,
|
|
678
580
|
label: "Local Pi",
|
|
679
581
|
threadId: params.threadId,
|
|
680
582
|
...page
|
|
@@ -798,201 +700,17 @@ async function checkPiUpstreamActivity(probes) {
|
|
|
798
700
|
}
|
|
799
701
|
//#endregion
|
|
800
702
|
//#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
703
|
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
704
|
const ACPX_BACKEND_ID = "acpx";
|
|
815
705
|
const PI_ACP_AGENT_ID = "pi";
|
|
816
706
|
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
707
|
async function requireLocalPiSession(threadId) {
|
|
990
|
-
const
|
|
708
|
+
const session = (await listLocalPiSessionPage({
|
|
991
709
|
searchTerm: threadId,
|
|
992
|
-
limit:
|
|
993
|
-
})).sessions.find((
|
|
994
|
-
if (!
|
|
995
|
-
return
|
|
710
|
+
limit: 100
|
|
711
|
+
})).sessions.find((candidate) => candidate.threadId === threadId);
|
|
712
|
+
if (!session) throw new Error("Pi session is unavailable");
|
|
713
|
+
return session;
|
|
996
714
|
}
|
|
997
715
|
function currentPiCatalogConfig(api) {
|
|
998
716
|
return api.runtime.config?.current?.() ?? api.config ?? {};
|
|
@@ -1013,8 +731,9 @@ function resolvePiContinuationAvailability(api) {
|
|
|
1013
731
|
message: "Pi CLI is unavailable"
|
|
1014
732
|
};
|
|
1015
733
|
}
|
|
1016
|
-
function listAdoptedPiSessions(api, sessionEntries) {
|
|
734
|
+
function listAdoptedPiSessions(api, agentId, sessionEntries) {
|
|
1017
735
|
return listAdoptedSessionCatalogSessions({
|
|
736
|
+
...agentId ? { agentId } : {},
|
|
1018
737
|
config: currentPiCatalogConfig(api),
|
|
1019
738
|
pluginId: api.id,
|
|
1020
739
|
runtime: api.runtime,
|
|
@@ -1023,198 +742,148 @@ function listAdoptedPiSessions(api, sessionEntries) {
|
|
|
1023
742
|
const acpx = isRecord(entry.pluginExtensions?.acpx) ? entry.pluginExtensions.acpx : void 0;
|
|
1024
743
|
const marker = acpx && isRecord(acpx.piSessionCatalog) ? acpx.piSessionCatalog : void 0;
|
|
1025
744
|
return marker && typeof marker.sourceThreadId === "string" ? {
|
|
1026
|
-
hostId:
|
|
745
|
+
hostId: PI_LOCAL_SESSION_HOST_ID,
|
|
1027
746
|
threadId: marker.sourceThreadId
|
|
1028
747
|
} : void 0;
|
|
1029
748
|
}
|
|
1030
749
|
});
|
|
1031
750
|
}
|
|
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 } : {}
|
|
751
|
+
async function createAdoptedPiSession(params) {
|
|
752
|
+
const config = currentPiCatalogConfig(params.api);
|
|
753
|
+
const marker = { sourceThreadId: params.threadId };
|
|
754
|
+
return { sessionKey: (await params.api.runtime.agent.session.createSessionEntry({
|
|
755
|
+
cfg: config,
|
|
756
|
+
key: sessionCatalogAdoptedSessionKey(PI_ADOPTED_SESSION_KEY_PREFIX, params.threadId),
|
|
757
|
+
agentId: params.agentId,
|
|
758
|
+
recoverMatchingInitialEntry: true,
|
|
759
|
+
...params.session.name ? { displayName: params.session.name } : {},
|
|
760
|
+
...params.session.cwd ? { spawnedCwd: params.session.cwd } : {},
|
|
761
|
+
initialEntry: {
|
|
762
|
+
acpBackendId: ACPX_BACKEND_ID,
|
|
763
|
+
acpSessionBinding: {
|
|
764
|
+
acpAgentId: PI_ACP_AGENT_ID,
|
|
765
|
+
agentSessionId: params.threadId
|
|
1166
766
|
},
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
767
|
+
pluginExtensions: { acpx: { piSessionCatalog: marker } }
|
|
768
|
+
},
|
|
769
|
+
afterCreate: async (entry) => {
|
|
770
|
+
await importSessionCatalogHistory({
|
|
771
|
+
catalogId: "pi",
|
|
772
|
+
threadId: params.threadId,
|
|
773
|
+
read: async ({ cursor, limit }) => await readLocalPiTranscriptPage({
|
|
774
|
+
threadId: params.threadId,
|
|
775
|
+
limit,
|
|
776
|
+
...cursor ? { cursor } : {}
|
|
777
|
+
}),
|
|
778
|
+
sessionId: entry.sessionId,
|
|
779
|
+
sessionKey: entry.key,
|
|
780
|
+
agentId: entry.agentId,
|
|
781
|
+
...params.session.cwd ? { cwd: params.session.cwd } : {},
|
|
782
|
+
config
|
|
783
|
+
});
|
|
784
|
+
return { pluginExtensions: { acpx: { piSessionCatalog: marker } } };
|
|
785
|
+
}
|
|
786
|
+
})).key };
|
|
1173
787
|
}
|
|
1174
788
|
function assertPiLocalAccess(hostId, allowProcessHomeFallback) {
|
|
1175
|
-
if (hostId ===
|
|
789
|
+
if (hostId === "gateway" && allowProcessHomeFallback === false && piSessionStore(process.env).usesProcessHomeFallback) throw new Error("local Pi sessions are unavailable in isolated state");
|
|
1176
790
|
}
|
|
1177
|
-
async function listPiSessions(
|
|
1178
|
-
return
|
|
791
|
+
async function listPiSessions(params) {
|
|
792
|
+
return await listLocalPiSessionPage(params);
|
|
1179
793
|
}
|
|
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));
|
|
794
|
+
async function readPiSession(params) {
|
|
795
|
+
return await readLocalPiTranscriptPage(params);
|
|
1200
796
|
}
|
|
1201
797
|
function createPiSessionCatalogRuntime(api) {
|
|
1202
|
-
return {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
798
|
+
return createSessionCatalogFamily({
|
|
799
|
+
runtime: api.runtime,
|
|
800
|
+
local: {
|
|
801
|
+
hostId: PI_LOCAL_SESSION_HOST_ID,
|
|
802
|
+
label: "Local Pi",
|
|
803
|
+
available: (query) => {
|
|
804
|
+
const store = piSessionStore(process.env);
|
|
805
|
+
return (query.allowProcessHomeFallback !== false || !store.usesProcessHomeFallback) && piSessionStoreAvailable(process.env, store);
|
|
806
|
+
},
|
|
807
|
+
list: async (query) => await listLocalPiSessionPage({
|
|
808
|
+
limit: query.limitPerHost,
|
|
809
|
+
...query.search ? { searchTerm: query.search } : {},
|
|
810
|
+
cursor: query.cursors?.[PI_LOCAL_SESSION_HOST_ID]
|
|
811
|
+
}),
|
|
812
|
+
read: async (request) => await readLocalPiTranscriptPage({
|
|
813
|
+
threadId: request.threadId,
|
|
814
|
+
...request.limit ? { limit: request.limit } : {},
|
|
815
|
+
...request.cursor !== void 0 ? { cursor: request.cursor } : {}
|
|
816
|
+
}),
|
|
817
|
+
assertAccess: assertPiLocalAccess
|
|
1208
818
|
},
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
819
|
+
node: {
|
|
820
|
+
listCommand: PI_SESSIONS_LIST_COMMAND,
|
|
821
|
+
readCommand: PI_SESSION_READ_COMMAND,
|
|
822
|
+
terminalCommand: PI_TERMINAL_RESUME_COMMAND,
|
|
823
|
+
timeoutMs: NODE_TIMEOUT_MS,
|
|
824
|
+
maxHosts: 100,
|
|
825
|
+
maxPageLimit: 100,
|
|
826
|
+
sessionIdPattern: PI_SESSION_ID_PATTERN
|
|
827
|
+
},
|
|
828
|
+
capabilities: {
|
|
829
|
+
local: () => ({
|
|
830
|
+
canContinue: resolvePiContinuationAvailability(api).available,
|
|
831
|
+
canOpenTerminal: resolveNodeHostExecutable("pi", {
|
|
832
|
+
env: process.env,
|
|
833
|
+
pathEnv: process.env.PATH ?? "",
|
|
834
|
+
strategy: "fallback"
|
|
835
|
+
}) !== void 0
|
|
836
|
+
}),
|
|
837
|
+
node: (node) => {
|
|
838
|
+
return {
|
|
839
|
+
canContinue: false,
|
|
840
|
+
canOpenTerminal: (node.invocableCommands ?? node.commands)?.includes(PI_TERMINAL_RESUME_COMMAND) === true
|
|
841
|
+
};
|
|
842
|
+
},
|
|
843
|
+
project: (session, capabilities) => ({
|
|
844
|
+
...session,
|
|
845
|
+
canContinue: capabilities.canContinue && session.canContinue,
|
|
846
|
+
canOpenTerminal: capabilities.canOpenTerminal
|
|
847
|
+
})
|
|
848
|
+
},
|
|
849
|
+
messages: {
|
|
850
|
+
invalidNodeCursor: "Pi node returned an invalid cursor",
|
|
851
|
+
invalidNodeSessionPage: "Pi node returned an invalid session page",
|
|
852
|
+
invalidNodeTranscriptPage: "Pi node returned an invalid transcript page",
|
|
853
|
+
invalidHostId: "Pi session catalog hostId is invalid",
|
|
854
|
+
localReadFailed: "Local Pi sessions are unavailable",
|
|
855
|
+
nodeInvokeFailed: "Paired node Pi sessions are unavailable",
|
|
856
|
+
nodeReadUnavailable: "paired-node Pi session host is unavailable",
|
|
857
|
+
nodeTerminalUnavailable: "paired-node Pi terminal is unavailable",
|
|
858
|
+
sessionUnavailable: "Pi session is unavailable"
|
|
859
|
+
},
|
|
860
|
+
continuation: {
|
|
861
|
+
resolveAgentId: (agentId) => resolveSessionAgentIdsStrict({
|
|
862
|
+
config: api.config,
|
|
863
|
+
agentId
|
|
864
|
+
}).sessionAgentId,
|
|
865
|
+
availability: () => resolvePiContinuationAvailability(api),
|
|
866
|
+
listAdopted: (agentId, sessionEntries) => listAdoptedPiSessions(api, agentId, sessionEntries),
|
|
867
|
+
loadSession: requireLocalPiSession,
|
|
868
|
+
validateSession: (session) => {
|
|
869
|
+
if (!session.canContinue) throw new Error("Pi session is outside the session store supported by pi-acp");
|
|
870
|
+
},
|
|
871
|
+
create: async (params) => await createAdoptedPiSession({
|
|
872
|
+
api,
|
|
873
|
+
...params
|
|
874
|
+
}),
|
|
875
|
+
complete: async (continued, threadId) => await linkContinuedPiSession(continued.sessionKey, threadId),
|
|
876
|
+
nodeReadOnlyMessage: "paired-node Pi session rows are view-only"
|
|
877
|
+
},
|
|
878
|
+
terminal: {
|
|
879
|
+
executable: "pi",
|
|
880
|
+
args: (threadId) => ["--session", threadId],
|
|
881
|
+
title: (threadId) => `pi --session ${threadId.slice(0, 12)}…`,
|
|
882
|
+
requireLocalSession: requireLocalPiSession,
|
|
883
|
+
unavailableMessage: "Pi CLI is unavailable"
|
|
884
|
+
},
|
|
885
|
+
checkUpstreamActivity: (probes, policy) => checkPiUpstreamActivity(probes.filter((probe) => probe.hostId !== "gateway" || policy?.allowProcessHomeFallback !== false || !piSessionStore(process.env).usesProcessHomeFallback))
|
|
886
|
+
}, isExactPiSessionCursor);
|
|
1218
887
|
}
|
|
1219
888
|
//#endregion
|
|
1220
|
-
export { createPiSessionCatalogRuntime, listPiSessions, readPiSession,
|
|
889
|
+
export { createPiSessionCatalogRuntime, listPiSessions, readPiSession, requireLocalPiSession };
|