@hachej/boring-agent 0.1.94 → 0.1.96
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/DebugDrawer-2TO5SE2D.js +7 -0
- package/dist/{chunk-NITBKFG6.js → chunk-3OEDBRKY.js} +93 -58
- package/dist/{chunk-BOIV2I3N.js → chunk-46HJK2ES.js} +31 -16
- package/dist/{chunk-AQBXNPMD.js → chunk-4IQ4HHBG.js} +4 -0
- package/dist/{chunk-OFQ7J6SI.js → chunk-LSO2AO6Q.js} +57 -1
- package/dist/{chunk-N4AATPH7.js → chunk-NSE2RHU3.js} +8973 -7731
- package/dist/{chunk-2GBHYJVC.js → chunk-VXF5DWS6.js} +34 -0
- package/dist/{chunk-2TMS2RU2.js → chunk-WC5KBGP2.js} +518 -165
- package/dist/chunk-YGRQ4Q5P.js +20 -0
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +604 -7
- package/dist/{createHarness-UCD74XXK.js → createHarness-PW3A2MRB.js} +3 -3
- package/dist/{errors-G6T6S7PQ.d.ts → errors-cNC4HMpV.d.ts} +14 -7
- package/dist/eval/index.d.ts +5 -3
- package/dist/eval/index.js +31 -23
- package/dist/file-search-QxnanNjO.d.ts +87 -0
- package/dist/front/index.d.ts +87 -17
- package/dist/front/index.js +1232 -690
- package/dist/front/styles.css +1228 -2167
- package/dist/{harness-Cc48o6uS.d.ts → harness-DS8jLKIx.d.ts} +4 -2
- package/dist/{lease-CusB-uLg.d.ts → lease-D8WC6pQl.d.ts} +10 -89
- package/dist/{middleware-CzW8n7B7.d.ts → middleware-Cq_cYHjJ.d.ts} +1 -1
- package/dist/piChatEvent-B3ehh9V0.d.ts +384 -0
- package/dist/{piChatSessionService-C0KbcGw9.d.ts → piChatSessionService-xnv8rcdh.d.ts} +1 -1
- package/dist/{sandbox-DAD8cWQR.d.ts → sandbox-KQiEdz4W.d.ts} +12 -1
- package/dist/server/agent-host/testing/compositionRouteProof.d.ts +8 -0
- package/dist/server/agent-host/testing/compositionRouteProof.js +78 -0
- package/dist/server/agent-host/testing/gatewayConformance.d.ts +6 -6
- package/dist/server/agent-host/testing/gatewayConformance.js +17 -9
- package/dist/server/index.d.ts +570 -370
- package/dist/server/index.js +69 -17
- package/dist/server/worker/index.d.ts +2 -2
- package/dist/server/worker/index.js +9 -9
- package/dist/shared/index.d.ts +212 -56
- package/dist/shared/index.js +43 -7
- package/dist/{types-C-Eroi__.d.ts → types-D_SD-7_l.d.ts} +304 -87
- package/docs/AGENT_GATEWAY_V0.md +6 -3
- package/docs/AGENT_PACKAGES.md +80 -0
- package/docs/ERROR_CODES.md +58 -0
- package/docs/README.md +1 -0
- package/docs/runtime.md +19 -2
- package/package.json +34 -29
- package/dist/DebugDrawer-RYEBQ6CO.js +0 -6
- package/dist/chunk-EOMY2HDT.js +0 -619
- package/dist/piChatEvent-BsMp3MSo.d.ts +0 -368
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SAFE_NATIVE_SESSION_ID,
|
|
2
3
|
noopTelemetry,
|
|
3
4
|
safeCapture
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-4IQ4HHBG.js";
|
|
5
6
|
import {
|
|
6
7
|
ErrorCode
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VXF5DWS6.js";
|
|
8
9
|
|
|
9
10
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
10
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
11
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
12
|
-
import { extname, join as join2 } from "path";
|
|
11
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
12
|
import {
|
|
14
13
|
createAgentSession,
|
|
15
|
-
SessionManager,
|
|
14
|
+
SessionManager as SessionManager2,
|
|
16
15
|
AuthStorage,
|
|
17
16
|
ModelRegistry,
|
|
18
17
|
DefaultResourceLoader,
|
|
19
|
-
SettingsManager as SettingsManager2,
|
|
20
18
|
getAgentDir as getAgentDir2,
|
|
21
19
|
loadSkills
|
|
22
20
|
} from "@mariozechner/pi-coding-agent";
|
|
@@ -298,21 +296,21 @@ function createPiAgentSessionAdapter(session, options = {}) {
|
|
|
298
296
|
}
|
|
299
297
|
|
|
300
298
|
// src/server/harness/pi-coding-agent/sessions.ts
|
|
301
|
-
import { randomUUID } from "crypto";
|
|
299
|
+
import { randomUUID } from "node:crypto";
|
|
302
300
|
import {
|
|
303
301
|
readdir,
|
|
304
302
|
readFile,
|
|
305
|
-
stat as
|
|
303
|
+
stat as fsStat2,
|
|
306
304
|
rm,
|
|
307
305
|
mkdir,
|
|
308
306
|
writeFile,
|
|
309
307
|
appendFile,
|
|
310
308
|
rename,
|
|
311
|
-
open
|
|
312
|
-
} from "fs/promises";
|
|
313
|
-
import { closeSync, openSync, readFileSync, readSync, readdirSync, writeFileSync } from "fs";
|
|
314
|
-
import { join, basename, resolve } from "path";
|
|
315
|
-
import { homedir } from "os";
|
|
309
|
+
open as open4
|
|
310
|
+
} from "node:fs/promises";
|
|
311
|
+
import { closeSync, openSync, readFileSync, readSync, readdirSync, writeFileSync } from "node:fs";
|
|
312
|
+
import { join, basename, resolve } from "node:path";
|
|
313
|
+
import { homedir } from "node:os";
|
|
316
314
|
|
|
317
315
|
// src/server/config/env.ts
|
|
318
316
|
function getEnv(name) {
|
|
@@ -322,8 +320,253 @@ function getEnv(name) {
|
|
|
322
320
|
// src/server/harness/pi-coding-agent/sessions.ts
|
|
323
321
|
import {
|
|
324
322
|
parseSessionEntries,
|
|
325
|
-
CURRENT_SESSION_VERSION
|
|
323
|
+
CURRENT_SESSION_VERSION as CURRENT_SESSION_VERSION2
|
|
326
324
|
} from "@mariozechner/pi-coding-agent";
|
|
325
|
+
|
|
326
|
+
// src/server/harness/pi-coding-agent/nativeSessionRename.ts
|
|
327
|
+
import { open as open2, stat as fsStat, utimes } from "node:fs/promises";
|
|
328
|
+
import {
|
|
329
|
+
CURRENT_SESSION_VERSION,
|
|
330
|
+
SessionManager
|
|
331
|
+
} from "@mariozechner/pi-coding-agent";
|
|
332
|
+
|
|
333
|
+
// src/server/harness/pi-coding-agent/sessionJsonlPrefix.ts
|
|
334
|
+
import { open } from "node:fs/promises";
|
|
335
|
+
var SUMMARY_PREFIX_BYTES = 64 * 1024;
|
|
336
|
+
async function readJsonlPrefix(filepath, maxBytes = SUMMARY_PREFIX_BYTES) {
|
|
337
|
+
const handle = await open(filepath, "r");
|
|
338
|
+
try {
|
|
339
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
340
|
+
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
341
|
+
return completeJsonlPrefix(buffer.subarray(0, bytesRead).toString("utf-8"), bytesRead === maxBytes);
|
|
342
|
+
} finally {
|
|
343
|
+
await handle.close();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function parseJsonlPrefixEntries(content) {
|
|
347
|
+
const entries = [];
|
|
348
|
+
for (const line of content.split("\n")) {
|
|
349
|
+
if (!line.trim()) continue;
|
|
350
|
+
try {
|
|
351
|
+
entries.push(JSON.parse(line));
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return entries;
|
|
356
|
+
}
|
|
357
|
+
function completeJsonlPrefix(content, reachedLimit) {
|
|
358
|
+
if (!reachedLimit) return content;
|
|
359
|
+
const lastNewline = content.lastIndexOf("\n");
|
|
360
|
+
return lastNewline >= 0 ? content.slice(0, lastNewline + 1) : content;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/server/harness/pi-coding-agent/nativeSessionRename.ts
|
|
364
|
+
var NATIVE_RENAME_MAX_APPEND_BYTES = 64 * 1024;
|
|
365
|
+
var NATIVE_RENAME_MAX_ATTEMPTS = 3;
|
|
366
|
+
async function restoreVerifiedNativeRenameMtime(filepath, before, verifiedSize) {
|
|
367
|
+
try {
|
|
368
|
+
const current = await fsStat(filepath);
|
|
369
|
+
if (current.dev !== before.dev || current.ino !== before.ino || current.size !== verifiedSize) return;
|
|
370
|
+
const atimeSeconds = fileTimeSeconds(before.atimeMs);
|
|
371
|
+
const mtimeSeconds = fileTimeSeconds(before.mtimeMs);
|
|
372
|
+
if (atimeSeconds === void 0 || mtimeSeconds === void 0) return;
|
|
373
|
+
await utimes(filepath, atimeSeconds, mtimeSeconds);
|
|
374
|
+
const restored = await fsStat(filepath);
|
|
375
|
+
if (restored.size !== verifiedSize) {
|
|
376
|
+
const restoredAtimeSeconds = fileTimeSeconds(restored.atimeMs);
|
|
377
|
+
if (restoredAtimeSeconds !== void 0) await utimes(filepath, restoredAtimeSeconds, Date.now() / 1e3);
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
async function verifiedNativeRenameAppend(filepath, before, append, title) {
|
|
383
|
+
const after = await fsStat(filepath);
|
|
384
|
+
if (after.dev !== before.dev || after.ino !== before.ino) return null;
|
|
385
|
+
const appendedBytes = after.size - before.size;
|
|
386
|
+
if (!Number.isSafeInteger(before.size) || !Number.isSafeInteger(after.size) || appendedBytes <= 0 || appendedBytes > NATIVE_RENAME_MAX_APPEND_BYTES) return null;
|
|
387
|
+
const handle = await open2(filepath, "r");
|
|
388
|
+
try {
|
|
389
|
+
const record = Buffer.allocUnsafe(appendedBytes);
|
|
390
|
+
const { bytesRead } = await handle.read(record, 0, record.length, before.size);
|
|
391
|
+
if (bytesRead !== record.length || record.at(-1) !== 10 || record.indexOf(10) !== record.length - 1) return null;
|
|
392
|
+
const parsed = JSON.parse(record.toString("utf-8"));
|
|
393
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
394
|
+
const entry = parsed;
|
|
395
|
+
return entry.type === "session_info" && entry.id === append.id && entry.parentId === append.parentId && entry.name === title ? after.size : null;
|
|
396
|
+
} catch {
|
|
397
|
+
return null;
|
|
398
|
+
} finally {
|
|
399
|
+
await handle.close();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function latestConcurrentEntryId(manager, staleRenameIds) {
|
|
403
|
+
for (const entry of manager.getEntries().reverse()) {
|
|
404
|
+
if (!staleRenameIds.has(entry.id)) return entry.id;
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
function nativeRenameLockedError(message = "native session changed while renaming; retry") {
|
|
409
|
+
return Object.assign(new Error(message), {
|
|
410
|
+
code: ErrorCode.enum.SESSION_LOCKED,
|
|
411
|
+
statusCode: 409,
|
|
412
|
+
retryable: true
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
async function appendVerifiedNativeRename(filepath, sessionDir, cwd, title) {
|
|
416
|
+
const staleRenameIds = /* @__PURE__ */ new Set();
|
|
417
|
+
for (let attempt = 0; attempt < NATIVE_RENAME_MAX_ATTEMPTS; attempt += 1) {
|
|
418
|
+
try {
|
|
419
|
+
const before = await fsStat(filepath);
|
|
420
|
+
const header = parseJsonlPrefixEntries(await readJsonlPrefix(filepath)).find(
|
|
421
|
+
(entry) => entry.type === "session"
|
|
422
|
+
);
|
|
423
|
+
if (header?.version !== CURRENT_SESSION_VERSION) {
|
|
424
|
+
throw nativeRenameLockedError("This native session was created by an unsupported Pi version and cannot be renamed.");
|
|
425
|
+
}
|
|
426
|
+
const manager = SessionManager.open(filepath, sessionDir, cwd);
|
|
427
|
+
if (staleRenameIds.size > 0) {
|
|
428
|
+
const concurrentLeaf = latestConcurrentEntryId(manager, staleRenameIds);
|
|
429
|
+
if (!concurrentLeaf) break;
|
|
430
|
+
manager.branch(concurrentLeaf);
|
|
431
|
+
}
|
|
432
|
+
const append = {
|
|
433
|
+
parentId: manager.getLeafId(),
|
|
434
|
+
id: manager.appendSessionInfo(title)
|
|
435
|
+
};
|
|
436
|
+
staleRenameIds.add(append.id);
|
|
437
|
+
const verifiedSize = await verifiedNativeRenameAppend(filepath, before, append, title);
|
|
438
|
+
if (verifiedSize !== null) {
|
|
439
|
+
await restoreVerifiedNativeRenameMtime(filepath, before, verifiedSize);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (error.code === ErrorCode.enum.SESSION_LOCKED) throw error;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
throw nativeRenameLockedError();
|
|
447
|
+
}
|
|
448
|
+
function fileTimeSeconds(milliseconds) {
|
|
449
|
+
const seconds = milliseconds / 1e3;
|
|
450
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/server/harness/pi-coding-agent/nativeSessionTranscript.ts
|
|
454
|
+
import { createReadStream } from "node:fs";
|
|
455
|
+
import { open as open3 } from "node:fs/promises";
|
|
456
|
+
import { createInterface } from "node:readline";
|
|
457
|
+
|
|
458
|
+
// src/server/harness/pi-coding-agent/piSessionMessages.ts
|
|
459
|
+
function textFromPiContent(content) {
|
|
460
|
+
if (typeof content === "string") return content;
|
|
461
|
+
if (!Array.isArray(content)) return "";
|
|
462
|
+
return content.map((part) => {
|
|
463
|
+
const item = part;
|
|
464
|
+
return item?.type === "text" && typeof item.text === "string" ? item.text : "";
|
|
465
|
+
}).join("");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/server/harness/pi-coding-agent/nativeSessionTranscript.ts
|
|
469
|
+
var NATIVE_TAIL_CHUNK_BYTES = 64 * 1024;
|
|
470
|
+
var NATIVE_TAIL_MAX_RECORD_BYTES = 256 * 1024;
|
|
471
|
+
var NATIVE_TAIL_MAX_RECORD_FRAGMENTS = 4;
|
|
472
|
+
async function latestNativeMessageTimestamp(filepath, size) {
|
|
473
|
+
const handle = await open3(filepath, "r");
|
|
474
|
+
let end = size;
|
|
475
|
+
let lineFragments = [];
|
|
476
|
+
try {
|
|
477
|
+
while (end > 0) {
|
|
478
|
+
const start = Math.max(0, end - NATIVE_TAIL_CHUNK_BYTES);
|
|
479
|
+
const chunk = Buffer.alloc(end - start);
|
|
480
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, start);
|
|
481
|
+
let lineEnd = bytesRead;
|
|
482
|
+
while (lineEnd > 0) {
|
|
483
|
+
const newline = chunk.lastIndexOf(10, lineEnd - 1);
|
|
484
|
+
if (newline < 0) break;
|
|
485
|
+
const timestamp = nativeMessageTimestampFromBoundedPrefix(
|
|
486
|
+
nativeTailRecordPrefix(chunk.subarray(newline + 1, lineEnd), lineFragments)
|
|
487
|
+
);
|
|
488
|
+
lineFragments = [];
|
|
489
|
+
if (timestamp !== void 0) return timestamp;
|
|
490
|
+
lineEnd = newline;
|
|
491
|
+
}
|
|
492
|
+
if (lineEnd > 0) {
|
|
493
|
+
lineFragments = retainNativeTailFragment(chunk.subarray(0, lineEnd), lineFragments);
|
|
494
|
+
}
|
|
495
|
+
end = start;
|
|
496
|
+
}
|
|
497
|
+
return nativeMessageTimestampFromBoundedPrefix(
|
|
498
|
+
nativeTailRecordPrefix(lineFragments.at(-1) ?? Buffer.alloc(0), lineFragments.slice(0, -1))
|
|
499
|
+
);
|
|
500
|
+
} finally {
|
|
501
|
+
await handle.close();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function retainNativeTailFragment(fragment, fragments) {
|
|
505
|
+
const next = [...fragments, fragment.subarray(0, NATIVE_TAIL_MAX_RECORD_BYTES)];
|
|
506
|
+
while (next.length > NATIVE_TAIL_MAX_RECORD_FRAGMENTS || nativeTailFragmentBytes(next) > NATIVE_TAIL_MAX_RECORD_BYTES) {
|
|
507
|
+
next.shift();
|
|
508
|
+
}
|
|
509
|
+
return next;
|
|
510
|
+
}
|
|
511
|
+
function nativeTailFragmentBytes(fragments) {
|
|
512
|
+
return fragments.reduce((total, fragment) => total + fragment.length, 0);
|
|
513
|
+
}
|
|
514
|
+
function nativeTailRecordPrefix(recordStart, followingFragments) {
|
|
515
|
+
const total = Math.min(
|
|
516
|
+
NATIVE_TAIL_MAX_RECORD_BYTES,
|
|
517
|
+
recordStart.length + nativeTailFragmentBytes(followingFragments)
|
|
518
|
+
);
|
|
519
|
+
const prefix = Buffer.allocUnsafe(total);
|
|
520
|
+
let offset = 0;
|
|
521
|
+
for (const fragment of [recordStart, ...followingFragments.slice().reverse()]) {
|
|
522
|
+
const length = Math.min(fragment.length, total - offset);
|
|
523
|
+
if (length <= 0) break;
|
|
524
|
+
fragment.copy(prefix, offset, 0, length);
|
|
525
|
+
offset += length;
|
|
526
|
+
}
|
|
527
|
+
return prefix;
|
|
528
|
+
}
|
|
529
|
+
function nativeMessageTimestampFromBoundedPrefix(prefix) {
|
|
530
|
+
if (prefix.length === 0) return void 0;
|
|
531
|
+
const line = prefix.subarray(0, NATIVE_TAIL_MAX_RECORD_BYTES).toString("utf-8");
|
|
532
|
+
if (!/^\s*\{\s*"type"\s*:\s*"message"(?:\s*,|\s*})/.test(line)) return void 0;
|
|
533
|
+
const timestampMatch = /"timestamp"\s*:\s*"([^"\\]*(?:\\.[^"\\]*)*)"/.exec(line);
|
|
534
|
+
if (!timestampMatch) return void 0;
|
|
535
|
+
const timestamp = Date.parse(timestampMatch[1]);
|
|
536
|
+
return Number.isFinite(timestamp) ? timestamp : void 0;
|
|
537
|
+
}
|
|
538
|
+
async function summarizeNativeTranscript(filepath) {
|
|
539
|
+
let title;
|
|
540
|
+
let firstUserTitle;
|
|
541
|
+
let turnCount = 0;
|
|
542
|
+
let hasAssistantReply2 = false;
|
|
543
|
+
let latestMessageAtMs;
|
|
544
|
+
const input = createReadStream(filepath, { encoding: "utf-8" });
|
|
545
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
546
|
+
for await (const line of lines) {
|
|
547
|
+
if (!line.trim()) continue;
|
|
548
|
+
try {
|
|
549
|
+
const entry = JSON.parse(line);
|
|
550
|
+
if (entry.type === "session_info" && typeof entry.name === "string") {
|
|
551
|
+
title = entry.name;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (entry.type !== "message") continue;
|
|
555
|
+
const timestamp = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : Number.NaN;
|
|
556
|
+
if (!Number.isNaN(timestamp)) latestMessageAtMs = timestamp;
|
|
557
|
+
if (entry.message?.role === "user") {
|
|
558
|
+
turnCount += 1;
|
|
559
|
+
firstUserTitle ??= textFromPiContent(entry.message.content).slice(0, 80) || void 0;
|
|
560
|
+
} else if (entry.message?.role === "assistant") {
|
|
561
|
+
hasAssistantReply2 = true;
|
|
562
|
+
}
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return { title, firstUserTitle, turnCount, hasAssistantReply: hasAssistantReply2, latestMessageAtMs };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/server/harness/pi-coding-agent/sessions.ts
|
|
327
570
|
function sessionBaseDir(explicitRoot) {
|
|
328
571
|
const explicit = explicitRoot?.trim();
|
|
329
572
|
if (explicit) return resolve(explicit);
|
|
@@ -335,10 +578,9 @@ function defaultSessionDir(cwd, explicitRoot) {
|
|
|
335
578
|
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
336
579
|
return join(sessionBaseDir(explicitRoot), safePath);
|
|
337
580
|
}
|
|
338
|
-
var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
339
581
|
var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
|
|
340
582
|
var SESSION_ROOT_ENV = "BORING_AGENT_SESSION_ROOT";
|
|
341
|
-
var
|
|
583
|
+
var SUMMARY_PREFIX_BYTES2 = 64 * 1024;
|
|
342
584
|
var DEFAULT_LEGACY_WORKSPACE_ID = "default";
|
|
343
585
|
var TRUSTED_LOCAL_USER_ID = "local";
|
|
344
586
|
function sessionDirForNamespace(namespace, explicitRoot) {
|
|
@@ -352,7 +594,8 @@ function normalizeListOptions(options) {
|
|
|
352
594
|
return {
|
|
353
595
|
limit: options?.limit === void 0 ? void 0 : Math.max(0, options.limit),
|
|
354
596
|
offset: Math.max(0, options?.offset ?? 0),
|
|
355
|
-
includeId: options?.includeId
|
|
597
|
+
includeId: options?.includeId,
|
|
598
|
+
includeEmpty: options?.includeEmpty === true
|
|
356
599
|
};
|
|
357
600
|
}
|
|
358
601
|
var PiSessionStore = class {
|
|
@@ -360,6 +603,7 @@ var PiSessionStore = class {
|
|
|
360
603
|
sessionDir;
|
|
361
604
|
allowLegacyUnscopedAccess;
|
|
362
605
|
pathDerivedLegacyAccess;
|
|
606
|
+
trustedNativeRuntimeScopeIdentity;
|
|
363
607
|
prefixCache = /* @__PURE__ */ new Map();
|
|
364
608
|
listInFlight = /* @__PURE__ */ new Map();
|
|
365
609
|
writerTails = /* @__PURE__ */ new Map();
|
|
@@ -369,10 +613,12 @@ var PiSessionStore = class {
|
|
|
369
613
|
this.sessionDir = options;
|
|
370
614
|
this.allowLegacyUnscopedAccess = true;
|
|
371
615
|
this.pathDerivedLegacyAccess = false;
|
|
616
|
+
this.trustedNativeRuntimeScopeIdentity = void 0;
|
|
372
617
|
return;
|
|
373
618
|
}
|
|
374
619
|
this.allowLegacyUnscopedAccess = true;
|
|
375
620
|
this.pathDerivedLegacyAccess = options?.sessionDir === void 0 && options?.sessionNamespace === void 0;
|
|
621
|
+
this.trustedNativeRuntimeScopeIdentity = options?.trustedNativeRuntimeScopeIdentity?.trim() || void 0;
|
|
376
622
|
this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace, options.sessionRoot) : defaultSessionDir(options?.storageCwd ?? cwd, options?.sessionRoot));
|
|
377
623
|
}
|
|
378
624
|
getSessionDir() {
|
|
@@ -381,9 +627,10 @@ var PiSessionStore = class {
|
|
|
381
627
|
/** Reads the Host execution pin from authoritative session metadata. */
|
|
382
628
|
async readRuntimeScopeIdentity(ctx, sessionId) {
|
|
383
629
|
const filepath = await this.resolveSessionFile(sessionId, ctx);
|
|
384
|
-
const entries =
|
|
630
|
+
const entries = parseJsonlPrefixEntries2(await readJsonlPrefix2(filepath));
|
|
385
631
|
const header = entries.find((entry) => entry.type === "session");
|
|
386
|
-
|
|
632
|
+
const directNative = isTimestampNamedPiSessionFile(filepath, header?.id ?? sessionId);
|
|
633
|
+
if (!this.headerBelongsToCtx(header, ctx, directNative)) throw new Error(`Session not found: ${sessionId}`);
|
|
387
634
|
return readHeaderRuntimeScopeIdentity(header);
|
|
388
635
|
}
|
|
389
636
|
async list(ctx, options) {
|
|
@@ -393,7 +640,8 @@ var PiSessionStore = class {
|
|
|
393
640
|
ctx.userId ?? null,
|
|
394
641
|
normalizedOptions.limit ?? null,
|
|
395
642
|
normalizedOptions.offset,
|
|
396
|
-
normalizedOptions.includeId ?? null
|
|
643
|
+
normalizedOptions.includeId ?? null,
|
|
644
|
+
normalizedOptions.includeEmpty
|
|
397
645
|
]);
|
|
398
646
|
const inFlight = this.listInFlight.get(inFlightKey);
|
|
399
647
|
if (inFlight) return inFlight;
|
|
@@ -411,7 +659,7 @@ var PiSessionStore = class {
|
|
|
411
659
|
const filepaths = jsonlFiles.map((f) => join(this.sessionDir, f));
|
|
412
660
|
const fileStats = await Promise.all(filepaths.map(async (filepath) => {
|
|
413
661
|
try {
|
|
414
|
-
return { filepath, stat: await
|
|
662
|
+
return { filepath, stat: await fsStat2(filepath) };
|
|
415
663
|
} catch {
|
|
416
664
|
return null;
|
|
417
665
|
}
|
|
@@ -424,8 +672,14 @@ var PiSessionStore = class {
|
|
|
424
672
|
})));
|
|
425
673
|
visibleFiles.sort((a, b) => b.sortMtimeMs - a.sortMtimeMs);
|
|
426
674
|
const { offset, limit } = options;
|
|
427
|
-
const pageSummaries = await this.summarizeVisiblePage(visibleFiles, { ctx, offset, limit });
|
|
428
675
|
const includeId = options.includeId;
|
|
676
|
+
const pageSummaries = await this.summarizeVisiblePage(visibleFiles, {
|
|
677
|
+
ctx,
|
|
678
|
+
offset,
|
|
679
|
+
limit,
|
|
680
|
+
includeId,
|
|
681
|
+
includeEmpty: options.includeEmpty
|
|
682
|
+
});
|
|
429
683
|
if (!includeId || pageSummaries.some((summary) => summary.id === includeId)) return pageSummaries;
|
|
430
684
|
const includeSummary = await this.summarizeIncludedSession(ctx, includeId, referencedPiFiles);
|
|
431
685
|
return includeSummary ? [...pageSummaries, includeSummary] : pageSummaries;
|
|
@@ -436,7 +690,7 @@ var PiSessionStore = class {
|
|
|
436
690
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
437
691
|
const header = {
|
|
438
692
|
type: "session",
|
|
439
|
-
version:
|
|
693
|
+
version: CURRENT_SESSION_VERSION2,
|
|
440
694
|
id,
|
|
441
695
|
timestamp: now,
|
|
442
696
|
cwd: this.cwd,
|
|
@@ -453,27 +707,34 @@ var PiSessionStore = class {
|
|
|
453
707
|
};
|
|
454
708
|
lines.push(JSON.stringify(infoEntry));
|
|
455
709
|
}
|
|
456
|
-
const filepath = join(this.sessionDir, `${id}
|
|
710
|
+
const filepath = join(this.sessionDir, `${nativeSessionFilename(id, now)}`);
|
|
457
711
|
await writeFile(filepath, lines.join("\n") + "\n", "utf-8");
|
|
458
712
|
return {
|
|
459
713
|
id,
|
|
460
714
|
title: init?.title ?? "New session",
|
|
461
715
|
createdAt: now,
|
|
462
716
|
updatedAt: now,
|
|
463
|
-
turnCount: 0
|
|
717
|
+
turnCount: 0,
|
|
718
|
+
nativeSessionId: id,
|
|
719
|
+
hasAssistantReply: false
|
|
464
720
|
};
|
|
465
721
|
}
|
|
466
722
|
async load(ctx, sessionId) {
|
|
467
723
|
const resolved = await this.resolveSessionTranscript(ctx, sessionId);
|
|
468
|
-
const
|
|
724
|
+
const nativeSummary = resolved.directNative ? await summarizeNativeTranscript(resolved.filepath) : null;
|
|
725
|
+
const title = newestDurableTitle(resolved.sessionEntries, resolved.linkedEntries) ?? nativeSummary?.title ?? nativeSummary?.firstUserTitle ?? "New session";
|
|
469
726
|
const turnCount = countUserTurns(resolved.transcriptEntries);
|
|
470
|
-
const updatedAtMs = Math.max(resolved.fileStat.mtime.getTime(), resolved.linkedMtimeMs ?? 0);
|
|
727
|
+
const updatedAtMs = nativeSummary?.latestMessageAtMs ?? Math.max(resolved.fileStat.mtime.getTime(), resolved.linkedMtimeMs ?? 0);
|
|
471
728
|
return {
|
|
472
729
|
id: resolved.resolvedSessionId,
|
|
473
730
|
title,
|
|
474
731
|
createdAt: resolved.header?.timestamp ?? resolved.fileStat.birthtime.toISOString(),
|
|
475
732
|
updatedAt: new Date(updatedAtMs).toISOString(),
|
|
476
|
-
turnCount
|
|
733
|
+
turnCount,
|
|
734
|
+
...resolved.directNative ? {
|
|
735
|
+
nativeSessionId: resolved.resolvedSessionId,
|
|
736
|
+
hasAssistantReply: hasAssistantReply(resolved.transcriptEntries)
|
|
737
|
+
} : {}
|
|
477
738
|
};
|
|
478
739
|
}
|
|
479
740
|
/**
|
|
@@ -508,7 +769,14 @@ var PiSessionStore = class {
|
|
|
508
769
|
throw new Error(`Session not found: ${sessionId}`);
|
|
509
770
|
}
|
|
510
771
|
const fileEntries = safeParseEntries(content);
|
|
511
|
-
|
|
772
|
+
const header = fileEntries.find(
|
|
773
|
+
(e) => e.type === "session"
|
|
774
|
+
);
|
|
775
|
+
const timestampNamedNative = isTimestampNamedPiSessionFile(
|
|
776
|
+
filepath,
|
|
777
|
+
header?.id ?? sessionId
|
|
778
|
+
);
|
|
779
|
+
if (!timestampNamedNative && fileEntries.some((e) => e.type === "ui_snapshot")) {
|
|
512
780
|
const compacted = fileEntries.filter((e) => e.type !== "ui_snapshot").map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
513
781
|
const tmp = `${filepath}.compact-${randomUUID()}`;
|
|
514
782
|
try {
|
|
@@ -519,15 +787,15 @@ var PiSessionStore = class {
|
|
|
519
787
|
});
|
|
520
788
|
}
|
|
521
789
|
}
|
|
522
|
-
|
|
523
|
-
(
|
|
524
|
-
|
|
525
|
-
if (!this.headerBelongsToCtx(header, ctx)) throw new Error(`Session not found: ${sessionId}`);
|
|
790
|
+
if (!this.headerBelongsToCtx(header, ctx, timestampNamedNative)) {
|
|
791
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
792
|
+
}
|
|
526
793
|
const sessionEntries = fileEntries.filter(
|
|
527
794
|
(e) => e.type !== "session" && e.type !== "ui_snapshot"
|
|
528
795
|
);
|
|
529
|
-
const fileStat = await
|
|
796
|
+
const fileStat = await fsStat2(filepath);
|
|
530
797
|
const linkedPiFile = extractPiSessionFilePath(fileEntries);
|
|
798
|
+
const directNative = timestampNamedNative && !linkedPiFile;
|
|
531
799
|
const linked = linkedPiFile && resolve(linkedPiFile) !== resolve(filepath) ? await this.readLinkedPiSession(linkedPiFile) : null;
|
|
532
800
|
const linkedEntries = linked?.entries.filter(
|
|
533
801
|
(e) => e.type !== "session"
|
|
@@ -542,7 +810,8 @@ var PiSessionStore = class {
|
|
|
542
810
|
fileStat,
|
|
543
811
|
linkedMtimeMs: linked?.mtime.getTime(),
|
|
544
812
|
filepath,
|
|
545
|
-
...linkedPiFile && linked ? { linkedFilepath: linkedPiFile } : {}
|
|
813
|
+
...linkedPiFile && linked ? { linkedFilepath: linkedPiFile } : {},
|
|
814
|
+
directNative
|
|
546
815
|
};
|
|
547
816
|
}
|
|
548
817
|
/**
|
|
@@ -550,10 +819,23 @@ var PiSessionStore = class {
|
|
|
550
819
|
* appended through the same JSONL path under one process-local writer lock.
|
|
551
820
|
*/
|
|
552
821
|
async rename(ctx, sessionId, title) {
|
|
553
|
-
const trimmed = title.trim();
|
|
822
|
+
const trimmed = title.replace(/[\r\n]+/g, " ").trim();
|
|
554
823
|
if (!trimmed) throw new Error("Session title must not be empty");
|
|
555
824
|
return await this.withWriter(sessionId, async () => {
|
|
556
825
|
const resolved = await this.resolveSessionTranscript(ctx, sessionId);
|
|
826
|
+
if (resolved.directNative) {
|
|
827
|
+
try {
|
|
828
|
+
await appendVerifiedNativeRename(
|
|
829
|
+
resolved.filepath,
|
|
830
|
+
this.sessionDir,
|
|
831
|
+
this.cwd,
|
|
832
|
+
trimmed
|
|
833
|
+
);
|
|
834
|
+
} finally {
|
|
835
|
+
this.prefixCache.delete(resolved.filepath);
|
|
836
|
+
}
|
|
837
|
+
return await this.load(ctx, sessionId);
|
|
838
|
+
}
|
|
557
839
|
const entry = {
|
|
558
840
|
type: "session_info",
|
|
559
841
|
id: randomUUID(),
|
|
@@ -591,7 +873,7 @@ var PiSessionStore = class {
|
|
|
591
873
|
// I/O hop is introduced before createAgentSession (which would break test
|
|
592
874
|
// timing when fake timers are in use). The file is tiny (metadata only).
|
|
593
875
|
loadPiSessionFileSync(ctx, sessionId) {
|
|
594
|
-
if (!
|
|
876
|
+
if (!SAFE_NATIVE_SESSION_ID.test(sessionId)) return null;
|
|
595
877
|
try {
|
|
596
878
|
const direct = join(this.sessionDir, `${sessionId}.jsonl`);
|
|
597
879
|
let filepath = direct;
|
|
@@ -608,13 +890,17 @@ var PiSessionStore = class {
|
|
|
608
890
|
}
|
|
609
891
|
const entries = safeParseEntries(content);
|
|
610
892
|
const header = entries.find((entry) => entry.type === "session");
|
|
611
|
-
|
|
893
|
+
const directNative = isTimestampNamedPiSessionFile(filepath, header?.id ?? sessionId);
|
|
612
894
|
const linkedPiFile = extractPiSessionFilePath(entries);
|
|
613
|
-
if (
|
|
614
|
-
|
|
895
|
+
if (!directNative) {
|
|
896
|
+
if (!this.headerBelongsToCtx(header, ctx)) return null;
|
|
897
|
+
return linkedPiFile;
|
|
898
|
+
}
|
|
899
|
+
if (this.nativeFileBelongsToCtx(header, ctx)) return filepath;
|
|
900
|
+
if (readHeaderSessionCtx(header) !== null) return null;
|
|
615
901
|
const existingWrapper = this.findWrapperReferencingNativeSessionSync(filepath);
|
|
616
902
|
if (existingWrapper) {
|
|
617
|
-
const existingEntries =
|
|
903
|
+
const existingEntries = parseJsonlPrefixEntries2(readJsonlPrefixSync(existingWrapper));
|
|
618
904
|
if (extractSessionHeaderId(existingEntries) !== sessionId) return null;
|
|
619
905
|
const wrapperHeader = existingEntries.find((entry) => entry.type === "session");
|
|
620
906
|
if (!this.headerBelongsToCtx(wrapperHeader, ctx)) return null;
|
|
@@ -627,7 +913,7 @@ var PiSessionStore = class {
|
|
|
627
913
|
}
|
|
628
914
|
}
|
|
629
915
|
async loadPiSessionFile(ctx, sessionId) {
|
|
630
|
-
if (!
|
|
916
|
+
if (!SAFE_NATIVE_SESSION_ID.test(sessionId)) return null;
|
|
631
917
|
try {
|
|
632
918
|
const direct = join(this.sessionDir, `${sessionId}.jsonl`);
|
|
633
919
|
let filepath = direct;
|
|
@@ -645,20 +931,25 @@ var PiSessionStore = class {
|
|
|
645
931
|
}
|
|
646
932
|
const entries = safeParseEntries(content);
|
|
647
933
|
const header = entries.find((entry) => entry.type === "session");
|
|
648
|
-
|
|
934
|
+
const directNative = isTimestampNamedPiSessionFile(filepath, header?.id ?? sessionId);
|
|
649
935
|
const linkedPiFile = extractPiSessionFilePath(entries);
|
|
650
|
-
if (
|
|
651
|
-
|
|
936
|
+
if (!directNative) {
|
|
937
|
+
if (!this.headerBelongsToCtx(header, ctx)) return null;
|
|
938
|
+
return linkedPiFile;
|
|
939
|
+
}
|
|
940
|
+
if (this.nativeFileBelongsToCtx(header, ctx)) return filepath;
|
|
941
|
+
if (readHeaderSessionCtx(header) !== null) return null;
|
|
652
942
|
const existingWrapper = await this.findWrapperReferencingNativeSession(filepath);
|
|
653
943
|
if (existingWrapper) {
|
|
654
944
|
const wrapperSessionId = await this.readSessionFileId(existingWrapper);
|
|
655
945
|
if (wrapperSessionId !== sessionId) return null;
|
|
656
|
-
const wrapperEntries =
|
|
946
|
+
const wrapperEntries = parseJsonlPrefixEntries2(await readJsonlPrefix2(existingWrapper));
|
|
657
947
|
const wrapperHeader = wrapperEntries.find((entry) => entry.type === "session");
|
|
658
948
|
if (!this.headerBelongsToCtx(wrapperHeader, ctx)) return null;
|
|
659
949
|
return extractPiSessionFilePath(wrapperEntries);
|
|
660
950
|
}
|
|
661
|
-
|
|
951
|
+
await this.ensureWrapperForNativeSession(sessionId, filepath, ctx);
|
|
952
|
+
return filepath;
|
|
662
953
|
} catch {
|
|
663
954
|
return null;
|
|
664
955
|
}
|
|
@@ -688,12 +979,12 @@ var PiSessionStore = class {
|
|
|
688
979
|
}
|
|
689
980
|
}
|
|
690
981
|
async resolveSessionFile(sessionId, ctx) {
|
|
691
|
-
if (!
|
|
982
|
+
if (!SAFE_NATIVE_SESSION_ID.test(sessionId)) {
|
|
692
983
|
throw new Error(`Session not found: ${sessionId}`);
|
|
693
984
|
}
|
|
694
985
|
const direct = join(this.sessionDir, `${sessionId}.jsonl`);
|
|
695
986
|
try {
|
|
696
|
-
await
|
|
987
|
+
await fsStat2(direct);
|
|
697
988
|
if (ctx) await this.assertFileBelongsToCtx(direct, ctx, sessionId);
|
|
698
989
|
return direct;
|
|
699
990
|
} catch {
|
|
@@ -708,26 +999,24 @@ var PiSessionStore = class {
|
|
|
708
999
|
if (ctx) await this.assertFileBelongsToCtx(matchedPath, ctx, sessionId);
|
|
709
1000
|
return matchedPath;
|
|
710
1001
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (wrapperSessionId === sessionId) {
|
|
715
|
-
if (ctx) await this.assertFileBelongsToCtx(existingWrapper, ctx, sessionId);
|
|
716
|
-
return existingWrapper;
|
|
717
|
-
}
|
|
718
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
1002
|
+
if (ctx && (this.pathDerivedLegacyAccess || this.hasTrustedNativeRuntimeCapability(ctx) || await this.nativeFilePin(matchedPath) !== null)) {
|
|
1003
|
+
await this.assertFileBelongsToCtx(matchedPath, ctx, sessionId);
|
|
1004
|
+
return matchedPath;
|
|
719
1005
|
}
|
|
720
|
-
if (ctx
|
|
721
|
-
|
|
1006
|
+
if (!ctx && this.pathDerivedLegacyAccess) return matchedPath;
|
|
1007
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
722
1008
|
}
|
|
723
1009
|
async assertFileBelongsToCtx(filepath, ctx, sessionId) {
|
|
724
|
-
const entries =
|
|
1010
|
+
const entries = parseJsonlPrefixEntries2(await readJsonlPrefix2(filepath));
|
|
725
1011
|
const header = entries.find((entry) => entry.type === "session");
|
|
726
|
-
|
|
1012
|
+
const directNative = !extractPiSessionFilePath(entries) && isTimestampNamedPiSessionFile(filepath, header?.id ?? sessionId);
|
|
1013
|
+
if (!this.headerBelongsToCtx(header, ctx, directNative)) {
|
|
1014
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
1015
|
+
}
|
|
727
1016
|
}
|
|
728
1017
|
async readSessionFileId(filepath) {
|
|
729
1018
|
try {
|
|
730
|
-
const entries =
|
|
1019
|
+
const entries = parseJsonlPrefixEntries2(await readJsonlPrefix2(filepath));
|
|
731
1020
|
return extractSessionHeaderId(entries);
|
|
732
1021
|
} catch {
|
|
733
1022
|
return null;
|
|
@@ -759,21 +1048,25 @@ var PiSessionStore = class {
|
|
|
759
1048
|
try {
|
|
760
1049
|
const linkedPiFile = (await this.readPrefixCache(filepath, stat)).referencedPiFile;
|
|
761
1050
|
if (linkedPiFile && resolve(linkedPiFile) !== resolve(filepath)) {
|
|
762
|
-
const linkedStat = await
|
|
1051
|
+
const linkedStat = await fsStat2(linkedPiFile);
|
|
763
1052
|
sortMtimeMs = Math.max(sortMtimeMs, linkedStat.mtime.getTime());
|
|
764
1053
|
}
|
|
1054
|
+
const header = parseJsonlPrefixEntries2(await readJsonlPrefix2(filepath)).find((entry) => entry.type === "session");
|
|
1055
|
+
if (header && isTimestampNamedPiSessionFile(filepath, header.id)) {
|
|
1056
|
+
sortMtimeMs = await latestNativeMessageTimestamp(filepath, Number(stat.size)) ?? sortMtimeMs;
|
|
1057
|
+
}
|
|
765
1058
|
} catch {
|
|
766
1059
|
}
|
|
767
1060
|
return sortMtimeMs;
|
|
768
1061
|
}
|
|
769
1062
|
async summarizeFile(ctx, filepath, existingStat) {
|
|
770
1063
|
try {
|
|
771
|
-
const fileStat = existingStat ?? await
|
|
1064
|
+
const fileStat = existingStat ?? await fsStat2(filepath);
|
|
772
1065
|
const cached = this.cachedPrefix(filepath, fileStat);
|
|
773
1066
|
if (cached && "summary" in cached && cached.sessionCtx !== void 0 && this.storedCtxBelongsToCtx(cached.sessionCtx, ctx) && await this.cachedSummaryIsFresh(filepath, cached)) {
|
|
774
1067
|
return cached.summary ?? null;
|
|
775
1068
|
}
|
|
776
|
-
const content = await
|
|
1069
|
+
const content = await readJsonlPrefix2(filepath);
|
|
777
1070
|
const firstNewline = content.indexOf("\n");
|
|
778
1071
|
if (firstNewline === -1) return null;
|
|
779
1072
|
const header = JSON.parse(
|
|
@@ -781,8 +1074,9 @@ var PiSessionStore = class {
|
|
|
781
1074
|
);
|
|
782
1075
|
if (header.type !== "session") return null;
|
|
783
1076
|
const sessionCtx = readHeaderSessionCtx(header);
|
|
784
|
-
|
|
785
|
-
|
|
1077
|
+
const directNative = isTimestampNamedPiSessionFile(filepath, header.id);
|
|
1078
|
+
if (directNative ? !this.nativeFileBelongsToCtx(header, ctx) : !this.storedCtxBelongsToCtx(sessionCtx, ctx)) return null;
|
|
1079
|
+
const entries = parseJsonlPrefixEntries2(content);
|
|
786
1080
|
const sessionEntries = entries.filter(
|
|
787
1081
|
(e) => e.type !== "session"
|
|
788
1082
|
);
|
|
@@ -791,17 +1085,22 @@ var PiSessionStore = class {
|
|
|
791
1085
|
const linkedEntries = linked?.entries.filter(
|
|
792
1086
|
(e) => e.type !== "session"
|
|
793
1087
|
) ?? [];
|
|
794
|
-
const
|
|
795
|
-
const
|
|
1088
|
+
const nativeSummary = directNative ? await summarizeNativeTranscript(filepath) : null;
|
|
1089
|
+
const title = newestDurableTitle(sessionEntries, linkedEntries) ?? nativeSummary?.title ?? nativeSummary?.firstUserTitle ?? firstUserMessage(linkedEntries) ?? firstUserMessage(sessionEntries) ?? "New session";
|
|
1090
|
+
const turnCount = nativeSummary?.turnCount ?? [...sessionEntries, ...linkedEntries].filter(
|
|
796
1091
|
(e) => e.type === "message" && e.message?.role === "user"
|
|
797
1092
|
).length;
|
|
798
|
-
const updatedAtMs = Math.max(fileStat.mtime.getTime(), linked?.mtime.getTime() ?? 0);
|
|
1093
|
+
const updatedAtMs = nativeSummary?.latestMessageAtMs ?? Math.max(fileStat.mtime.getTime(), linked?.mtime.getTime() ?? 0);
|
|
799
1094
|
const summary = {
|
|
800
1095
|
id: header.id,
|
|
801
1096
|
title,
|
|
802
1097
|
createdAt: header.timestamp,
|
|
803
1098
|
updatedAt: new Date(updatedAtMs).toISOString(),
|
|
804
|
-
turnCount
|
|
1099
|
+
turnCount,
|
|
1100
|
+
...nativeSummary ? {
|
|
1101
|
+
nativeSessionId: header.id,
|
|
1102
|
+
hasAssistantReply: nativeSummary.hasAssistantReply
|
|
1103
|
+
} : {}
|
|
805
1104
|
};
|
|
806
1105
|
this.prefixCache.set(filepath, {
|
|
807
1106
|
mtimeMs: fileStat.mtime.getTime(),
|
|
@@ -809,7 +1108,7 @@ var PiSessionStore = class {
|
|
|
809
1108
|
referencedPiFile: linkedPiFile,
|
|
810
1109
|
sessionCtx,
|
|
811
1110
|
...linked ? { linkedMtimeMs: linked.mtime.getTime(), linkedSize: linked.size } : {},
|
|
812
|
-
summary
|
|
1111
|
+
...!nativeSummary ? { summary } : {}
|
|
813
1112
|
});
|
|
814
1113
|
return summary;
|
|
815
1114
|
} catch {
|
|
@@ -826,7 +1125,7 @@ var PiSessionStore = class {
|
|
|
826
1125
|
const linkedPiFile = cached.referencedPiFile;
|
|
827
1126
|
if (!linkedPiFile || resolve(linkedPiFile) === resolve(filepath)) return true;
|
|
828
1127
|
try {
|
|
829
|
-
const linkedStat = await
|
|
1128
|
+
const linkedStat = await fsStat2(linkedPiFile);
|
|
830
1129
|
return cached.linkedMtimeMs === linkedStat.mtime.getTime() && cached.linkedSize === Number(linkedStat.size);
|
|
831
1130
|
} catch {
|
|
832
1131
|
return cached.linkedMtimeMs === void 0 && cached.linkedSize === void 0;
|
|
@@ -835,8 +1134,8 @@ var PiSessionStore = class {
|
|
|
835
1134
|
async readPrefixCache(filepath, fileStat) {
|
|
836
1135
|
const cached = this.cachedPrefix(filepath, fileStat);
|
|
837
1136
|
if (cached) return cached;
|
|
838
|
-
const content = await
|
|
839
|
-
const entries =
|
|
1137
|
+
const content = await readJsonlPrefix2(filepath);
|
|
1138
|
+
const entries = parseJsonlPrefixEntries2(content);
|
|
840
1139
|
const entry = {
|
|
841
1140
|
mtimeMs: fileStat.mtime.getTime(),
|
|
842
1141
|
size: Number(fileStat.size),
|
|
@@ -860,6 +1159,9 @@ var PiSessionStore = class {
|
|
|
860
1159
|
);
|
|
861
1160
|
for (const summary of summaries) {
|
|
862
1161
|
if (!summary) continue;
|
|
1162
|
+
if (summary.turnCount === 0 && !options.includeEmpty && summary.id !== options.includeId) {
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
863
1165
|
if (validSeen < options.offset) {
|
|
864
1166
|
validSeen += 1;
|
|
865
1167
|
continue;
|
|
@@ -888,7 +1190,7 @@ var PiSessionStore = class {
|
|
|
888
1190
|
const filepath = join(this.sessionDir, file);
|
|
889
1191
|
if (resolve(filepath) === resolvedNativePath) continue;
|
|
890
1192
|
try {
|
|
891
|
-
const linkedPiFile = extractPiSessionFilePath(
|
|
1193
|
+
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries2(readJsonlPrefixSync(filepath)));
|
|
892
1194
|
if (linkedPiFile && resolve(linkedPiFile) === resolvedNativePath) return filepath;
|
|
893
1195
|
} catch {
|
|
894
1196
|
}
|
|
@@ -906,7 +1208,7 @@ var PiSessionStore = class {
|
|
|
906
1208
|
const filepath = join(this.sessionDir, file);
|
|
907
1209
|
if (resolve(filepath) === resolvedNativePath) continue;
|
|
908
1210
|
try {
|
|
909
|
-
const linkedPiFile = extractPiSessionFilePath(
|
|
1211
|
+
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries2(await readJsonlPrefix2(filepath)));
|
|
910
1212
|
if (linkedPiFile && resolve(linkedPiFile) === resolvedNativePath) return filepath;
|
|
911
1213
|
} catch {
|
|
912
1214
|
}
|
|
@@ -937,11 +1239,11 @@ var PiSessionStore = class {
|
|
|
937
1239
|
const wrapperPath = join(this.sessionDir, `${sessionId}.jsonl`);
|
|
938
1240
|
if (resolve(wrapperPath) === resolve(nativePath)) return wrapperPath;
|
|
939
1241
|
try {
|
|
940
|
-
await
|
|
1242
|
+
await fsStat2(wrapperPath);
|
|
941
1243
|
return wrapperPath;
|
|
942
1244
|
} catch {
|
|
943
1245
|
}
|
|
944
|
-
const entries =
|
|
1246
|
+
const entries = parseJsonlPrefixEntries2(await readJsonlPrefix2(nativePath));
|
|
945
1247
|
try {
|
|
946
1248
|
await writeFile(
|
|
947
1249
|
wrapperPath,
|
|
@@ -957,7 +1259,7 @@ var PiSessionStore = class {
|
|
|
957
1259
|
async readLinkedPiSession(filepath) {
|
|
958
1260
|
try {
|
|
959
1261
|
const [fileStat, content] = await Promise.all([
|
|
960
|
-
|
|
1262
|
+
fsStat2(filepath),
|
|
961
1263
|
readFile(filepath, "utf-8")
|
|
962
1264
|
]);
|
|
963
1265
|
return { entries: safeParseEntries(content), mtime: fileStat.mtime, size: Number(fileStat.size) };
|
|
@@ -968,17 +1270,45 @@ var PiSessionStore = class {
|
|
|
968
1270
|
async readLinkedPiSessionSummary(filepath) {
|
|
969
1271
|
try {
|
|
970
1272
|
const [fileStat, content] = await Promise.all([
|
|
971
|
-
|
|
972
|
-
|
|
1273
|
+
fsStat2(filepath),
|
|
1274
|
+
readJsonlPrefix2(filepath)
|
|
973
1275
|
]);
|
|
974
|
-
return { entries:
|
|
1276
|
+
return { entries: parseJsonlPrefixEntries2(content), mtime: fileStat.mtime, size: Number(fileStat.size) };
|
|
975
1277
|
} catch {
|
|
976
1278
|
return null;
|
|
977
1279
|
}
|
|
978
1280
|
}
|
|
979
|
-
headerBelongsToCtx(header, ctx) {
|
|
1281
|
+
headerBelongsToCtx(header, ctx, directNative = false) {
|
|
1282
|
+
if (directNative) return this.nativeFileBelongsToCtx(header, ctx);
|
|
980
1283
|
return header ? this.storedCtxBelongsToCtx(readHeaderSessionCtx(header), ctx) : isEmptySessionCtx(ctx);
|
|
981
1284
|
}
|
|
1285
|
+
/**
|
|
1286
|
+
* A bare Pi transcript carries no tenancy, so only a path-derived trusted
|
|
1287
|
+
* local store may reach it. Boring-created native transcripts carry an exact
|
|
1288
|
+
* persisted tenancy/runtime pin and remain reachable by hosted/namespaced
|
|
1289
|
+
* stores without minting a compatibility wrapper.
|
|
1290
|
+
*/
|
|
1291
|
+
nativeFileBelongsToCtx(header, ctx) {
|
|
1292
|
+
const pinned = readHeaderSessionCtx(header);
|
|
1293
|
+
if (pinned === null && this.hasTrustedNativeRuntimeCapability(ctx)) return true;
|
|
1294
|
+
if (this.pathDerivedLegacyAccess) return this.storedCtxBelongsToCtx(pinned, ctx);
|
|
1295
|
+
return pinned !== null && sameSessionCtx(pinned, ctx);
|
|
1296
|
+
}
|
|
1297
|
+
hasTrustedNativeRuntimeCapability(ctx) {
|
|
1298
|
+
const runtimeScopeIdentity = ctx.runtimeScopeIdentity?.trim();
|
|
1299
|
+
return Boolean(
|
|
1300
|
+
runtimeScopeIdentity && this.trustedNativeRuntimeScopeIdentity && runtimeScopeIdentity === this.trustedNativeRuntimeScopeIdentity
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
/** The Boring tenancy pin on a native transcript, or null when unpinned. */
|
|
1304
|
+
async nativeFilePin(filepath) {
|
|
1305
|
+
try {
|
|
1306
|
+
const entries = parseJsonlPrefixEntries2(await readJsonlPrefix2(filepath));
|
|
1307
|
+
return readHeaderSessionCtx(entries.find((entry) => entry.type === "session"));
|
|
1308
|
+
} catch {
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
982
1312
|
storedCtxBelongsToCtx(storedCtx, ctx) {
|
|
983
1313
|
const trustedLocalCtx = !ctx.userId || ctx.userId === TRUSTED_LOCAL_USER_ID;
|
|
984
1314
|
if (storedCtx === null) {
|
|
@@ -992,8 +1322,8 @@ var PiSessionStore = class {
|
|
|
992
1322
|
);
|
|
993
1323
|
}
|
|
994
1324
|
};
|
|
995
|
-
async function
|
|
996
|
-
const handle = await
|
|
1325
|
+
async function readJsonlPrefix2(filepath, maxBytes = SUMMARY_PREFIX_BYTES2) {
|
|
1326
|
+
const handle = await open4(filepath, "r");
|
|
997
1327
|
try {
|
|
998
1328
|
const buffer = Buffer.alloc(maxBytes);
|
|
999
1329
|
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
@@ -1007,7 +1337,7 @@ async function readJsonlPrefix(filepath, maxBytes = SUMMARY_PREFIX_BYTES) {
|
|
|
1007
1337
|
await handle.close();
|
|
1008
1338
|
}
|
|
1009
1339
|
}
|
|
1010
|
-
function readJsonlPrefixSync(filepath, maxBytes =
|
|
1340
|
+
function readJsonlPrefixSync(filepath, maxBytes = SUMMARY_PREFIX_BYTES2) {
|
|
1011
1341
|
const fd = openSync(filepath, "r");
|
|
1012
1342
|
try {
|
|
1013
1343
|
const buffer = Buffer.alloc(maxBytes);
|
|
@@ -1067,7 +1397,7 @@ function buildNativePiSessionWrapper(sessionId, cwd, piFilePath, entries, ctx) {
|
|
|
1067
1397
|
const timestamp = nativeHeader?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1068
1398
|
const header = {
|
|
1069
1399
|
type: "session",
|
|
1070
|
-
version:
|
|
1400
|
+
version: CURRENT_SESSION_VERSION2,
|
|
1071
1401
|
id: sessionId,
|
|
1072
1402
|
timestamp,
|
|
1073
1403
|
cwd: nativeHeader?.cwd ?? cwd,
|
|
@@ -1086,14 +1416,23 @@ function extractSessionHeaderId(entries) {
|
|
|
1086
1416
|
const header = entries.find((entry) => entry.type === "session");
|
|
1087
1417
|
return header?.id ?? null;
|
|
1088
1418
|
}
|
|
1419
|
+
function nativeSessionFilename(sessionId, isoTimestamp) {
|
|
1420
|
+
return `${isoTimestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`;
|
|
1421
|
+
}
|
|
1089
1422
|
function isTimestampNamedPiSessionFile(filepath, sessionId) {
|
|
1090
|
-
|
|
1423
|
+
const filename = basename(filepath);
|
|
1424
|
+
return /^\d{4}-\d{2}-\d{2}/.test(filename) && filename.endsWith(`_${sessionId}.jsonl`);
|
|
1091
1425
|
}
|
|
1092
1426
|
function countUserTurns(entries) {
|
|
1093
1427
|
return entries.filter(
|
|
1094
1428
|
(e) => e.type === "message" && e.message?.role === "user"
|
|
1095
1429
|
).length;
|
|
1096
1430
|
}
|
|
1431
|
+
function hasAssistantReply(entries) {
|
|
1432
|
+
return entries.some(
|
|
1433
|
+
(entry) => entry.type === "message" && entry.message.role === "assistant"
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1097
1436
|
function newestDurableTitle(wrapperEntries, nativeEntries) {
|
|
1098
1437
|
const candidates = [
|
|
1099
1438
|
...wrapperEntries.flatMap((entry, offset) => entry.type === "session_info" ? [{ entry, native: 0, offset }] : []),
|
|
@@ -1110,7 +1449,7 @@ function firstUserMessage(entries) {
|
|
|
1110
1449
|
if (e.type !== "message") continue;
|
|
1111
1450
|
const msg = e.message;
|
|
1112
1451
|
if (msg?.role !== "user") continue;
|
|
1113
|
-
const text =
|
|
1452
|
+
const text = textFromPiContent2(msg.content);
|
|
1114
1453
|
if (text) return text.slice(0, 80);
|
|
1115
1454
|
}
|
|
1116
1455
|
}
|
|
@@ -1129,7 +1468,7 @@ function safeParseEntries(content) {
|
|
|
1129
1468
|
return results;
|
|
1130
1469
|
}
|
|
1131
1470
|
}
|
|
1132
|
-
function
|
|
1471
|
+
function parseJsonlPrefixEntries2(content) {
|
|
1133
1472
|
const entries = [];
|
|
1134
1473
|
for (const line of content.split("\n")) {
|
|
1135
1474
|
if (!line.trim()) continue;
|
|
@@ -1140,7 +1479,7 @@ function parseJsonlPrefixEntries(content) {
|
|
|
1140
1479
|
}
|
|
1141
1480
|
return entries;
|
|
1142
1481
|
}
|
|
1143
|
-
function
|
|
1482
|
+
function textFromPiContent2(content) {
|
|
1144
1483
|
if (typeof content === "string") return content;
|
|
1145
1484
|
if (!Array.isArray(content)) return "";
|
|
1146
1485
|
return content.map((part) => {
|
|
@@ -1421,6 +1760,43 @@ function mergePiPackageSources(base = [], additional = []) {
|
|
|
1421
1760
|
return compactPiPackages([...base, ...additional]);
|
|
1422
1761
|
}
|
|
1423
1762
|
|
|
1763
|
+
// src/server/harness/pi-coding-agent/resourceSettingsManager.ts
|
|
1764
|
+
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1765
|
+
import { join as join2 } from "node:path";
|
|
1766
|
+
import { SettingsManager as SettingsManager2 } from "@mariozechner/pi-coding-agent";
|
|
1767
|
+
function readSettingsFileIfPresent(path) {
|
|
1768
|
+
return existsSync(path) ? readFileSync2(path, "utf-8") : void 0;
|
|
1769
|
+
}
|
|
1770
|
+
function mergeInjectedProjectPackages(settingsJson, piPackages, includePackage) {
|
|
1771
|
+
const settings = settingsJson ? JSON.parse(settingsJson) : {};
|
|
1772
|
+
const configuredPackages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
1773
|
+
return JSON.stringify({
|
|
1774
|
+
...settings,
|
|
1775
|
+
packages: mergePiPackageSources(configuredPackages, piPackages).filter((source) => includePackage?.(source) ?? true)
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
function createResourceSettingsManager(cwd, agentDir, piPackages, options = {}) {
|
|
1779
|
+
if (piPackages.length === 0 && !options.includePackage) return SettingsManager2.create(cwd, agentDir);
|
|
1780
|
+
const globalSettingsPath = join2(agentDir, "settings.json");
|
|
1781
|
+
const projectSettingsPath = join2(cwd, ".pi", "settings.json");
|
|
1782
|
+
let globalSettingsOverrideJson;
|
|
1783
|
+
let projectSettingsOverrideJson;
|
|
1784
|
+
const storage = {
|
|
1785
|
+
withLock(scope, fn) {
|
|
1786
|
+
if (scope === "global") {
|
|
1787
|
+
const current2 = globalSettingsOverrideJson ?? mergeInjectedProjectPackages(readSettingsFileIfPresent(globalSettingsPath), [], options.includePackage);
|
|
1788
|
+
const next2 = fn(current2);
|
|
1789
|
+
if (next2 !== void 0) globalSettingsOverrideJson = next2;
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
const current = projectSettingsOverrideJson ?? mergeInjectedProjectPackages(readSettingsFileIfPresent(projectSettingsPath), piPackages, options.includePackage);
|
|
1793
|
+
const next = fn(current);
|
|
1794
|
+
if (next !== void 0) projectSettingsOverrideJson = next;
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
return SettingsManager2.fromStorage(storage);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1424
1800
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
1425
1801
|
var WORKSPACE_PATHS_GUIDELINE = [
|
|
1426
1802
|
"## Workspace paths",
|
|
@@ -1520,52 +1896,20 @@ function resolveDefaultModel(modelRegistry, override, strict) {
|
|
|
1520
1896
|
return void 0;
|
|
1521
1897
|
}
|
|
1522
1898
|
function sessionCtxForInput(input, ctx) {
|
|
1523
|
-
return normalizeSessionCtx2(input.ctx ?? { workspaceId: ctx.workspaceId }) ?? {};
|
|
1899
|
+
return normalizeSessionCtx2(input.ctx ?? ctx.sessionCtx ?? { workspaceId: ctx.workspaceId }) ?? {};
|
|
1524
1900
|
}
|
|
1525
1901
|
function sessionCtxFromRunContext(ctx) {
|
|
1526
|
-
return normalizeSessionCtx2({ workspaceId: ctx.workspaceId
|
|
1902
|
+
return normalizeSessionCtx2(ctx.sessionCtx ?? { workspaceId: ctx.workspaceId }) ?? {};
|
|
1527
1903
|
}
|
|
1528
1904
|
function normalizeSessionCtx2(ctx) {
|
|
1529
|
-
if (!ctx?.workspaceId && !ctx?.
|
|
1905
|
+
if (!ctx?.workspaceId && !ctx?.runtimeScopeIdentity) return void 0;
|
|
1530
1906
|
return {
|
|
1531
1907
|
...ctx.workspaceId ? { workspaceId: ctx.workspaceId } : {},
|
|
1532
|
-
...ctx.
|
|
1908
|
+
...ctx.runtimeScopeIdentity ? { runtimeScopeIdentity: ctx.runtimeScopeIdentity } : {}
|
|
1533
1909
|
};
|
|
1534
1910
|
}
|
|
1535
1911
|
function sessionCacheKey(sessionId, ctx) {
|
|
1536
|
-
return JSON.stringify([sessionId, ctx.workspaceId ?? "", ctx.
|
|
1537
|
-
}
|
|
1538
|
-
function readSettingsFileIfPresent(path) {
|
|
1539
|
-
return existsSync(path) ? readFileSync2(path, "utf-8") : void 0;
|
|
1540
|
-
}
|
|
1541
|
-
function mergeInjectedProjectPackages(settingsJson, piPackages) {
|
|
1542
|
-
const settings = settingsJson ? JSON.parse(settingsJson) : {};
|
|
1543
|
-
const configuredPackages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
1544
|
-
return JSON.stringify({
|
|
1545
|
-
...settings,
|
|
1546
|
-
packages: mergePiPackageSources(configuredPackages, piPackages)
|
|
1547
|
-
});
|
|
1548
|
-
}
|
|
1549
|
-
function createResourceSettingsManager(cwd, agentDir, piPackages) {
|
|
1550
|
-
if (piPackages.length === 0) return SettingsManager2.create(cwd, agentDir);
|
|
1551
|
-
const globalSettingsPath = join2(agentDir, "settings.json");
|
|
1552
|
-
const projectSettingsPath = join2(cwd, ".pi", "settings.json");
|
|
1553
|
-
let globalSettingsOverrideJson;
|
|
1554
|
-
let projectSettingsOverrideJson;
|
|
1555
|
-
const storage = {
|
|
1556
|
-
withLock(scope, fn) {
|
|
1557
|
-
if (scope === "global") {
|
|
1558
|
-
const current2 = globalSettingsOverrideJson ?? readSettingsFileIfPresent(globalSettingsPath);
|
|
1559
|
-
const next2 = fn(current2);
|
|
1560
|
-
if (next2 !== void 0) globalSettingsOverrideJson = next2;
|
|
1561
|
-
return;
|
|
1562
|
-
}
|
|
1563
|
-
const current = projectSettingsOverrideJson ?? mergeInjectedProjectPackages(readSettingsFileIfPresent(projectSettingsPath), piPackages);
|
|
1564
|
-
const next = fn(current);
|
|
1565
|
-
if (next !== void 0) projectSettingsOverrideJson = next;
|
|
1566
|
-
}
|
|
1567
|
-
};
|
|
1568
|
-
return SettingsManager2.fromStorage(storage);
|
|
1912
|
+
return JSON.stringify([sessionId, ctx.workspaceId ?? "", ctx.runtimeScopeIdentity ?? ""]);
|
|
1569
1913
|
}
|
|
1570
1914
|
async function applyRequestedSessionOptions(handle, input, options = {}) {
|
|
1571
1915
|
const requestedModel = resolveRequestedModel(handle.modelRegistry, input, { strict: options.strictModelResolution });
|
|
@@ -1662,6 +2006,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
1662
2006
|
};
|
|
1663
2007
|
refreshEffectiveResources();
|
|
1664
2008
|
const piSessionCreations = /* @__PURE__ */ new Map();
|
|
2009
|
+
const sessionGenerations = /* @__PURE__ */ new Map();
|
|
2010
|
+
const generationOf = (sessionKey) => sessionGenerations.get(sessionKey) ?? 0;
|
|
1665
2011
|
async function getOrCreatePiSession(sessionId, input, ctx) {
|
|
1666
2012
|
const sessionCtx = sessionCtxForInput(input, ctx);
|
|
1667
2013
|
const sessionKey = sessionCacheKey(sessionId, sessionCtx);
|
|
@@ -1676,8 +2022,18 @@ function createPiCodingAgentHarness(opts) {
|
|
|
1676
2022
|
await applyRequestedSessionOptions(handle, input, { strictModelResolution: pi.strictModelResolution });
|
|
1677
2023
|
return handle;
|
|
1678
2024
|
}
|
|
1679
|
-
const
|
|
2025
|
+
const generation = generationOf(sessionKey);
|
|
2026
|
+
const creation = createPiSession(sessionId, sessionCtx, input, ctx).then((handle) => {
|
|
2027
|
+
if (generationOf(sessionKey) === generation) return handle;
|
|
2028
|
+
disposeHandleAt(sessionKey, handle);
|
|
2029
|
+
throw Object.assign(new Error("session not found"), {
|
|
2030
|
+
code: ErrorCode.enum.SESSION_NOT_FOUND,
|
|
2031
|
+
statusCode: 404
|
|
2032
|
+
});
|
|
2033
|
+
});
|
|
1680
2034
|
piSessionCreations.set(sessionKey, creation);
|
|
2035
|
+
creation.catch(() => {
|
|
2036
|
+
});
|
|
1681
2037
|
try {
|
|
1682
2038
|
return await creation;
|
|
1683
2039
|
} finally {
|
|
@@ -1704,24 +2060,25 @@ function createPiCodingAgentHarness(opts) {
|
|
|
1704
2060
|
const authStorage = AuthStorage.create();
|
|
1705
2061
|
const modelRegistry = ModelRegistry.create(authStorage);
|
|
1706
2062
|
registerConfiguredModelProviders(modelRegistry);
|
|
1707
|
-
const savedPiFile = sessionStore.loadPiSessionFileSync(sessionCtx, sessionId);
|
|
1708
|
-
let sessionManager;
|
|
1709
|
-
let isNewPiSession = false;
|
|
1710
|
-
const runtimeCwd = opts.runtimeCwd ?? ctx.workdir;
|
|
1711
|
-
const nativeSessionDir = sessionStore.getSessionDir();
|
|
1712
|
-
if (savedPiFile) {
|
|
1713
|
-
try {
|
|
1714
|
-
sessionManager = SessionManager.open(savedPiFile, void 0, runtimeCwd);
|
|
1715
|
-
} catch {
|
|
1716
|
-
sessionManager = SessionManager.create(runtimeCwd, nativeSessionDir);
|
|
1717
|
-
isNewPiSession = true;
|
|
1718
|
-
}
|
|
1719
|
-
} else {
|
|
1720
|
-
sessionManager = SessionManager.create(runtimeCwd, nativeSessionDir);
|
|
1721
|
-
isNewPiSession = true;
|
|
1722
|
-
}
|
|
1723
2063
|
const resolvedModel = resolveRequestedModel(modelRegistry, input, { strict: pi.strictModelResolution });
|
|
1724
2064
|
const model = resolvedModel ?? resolveDefaultModel(modelRegistry, pi.defaultModel, pi.strictModelResolution);
|
|
2065
|
+
const savedPiFile = await sessionStore.loadPiSessionFile(sessionCtx, sessionId);
|
|
2066
|
+
const runtimeCwd = opts.runtimeCwd ?? ctx.workdir;
|
|
2067
|
+
if (!savedPiFile) {
|
|
2068
|
+
throw Object.assign(
|
|
2069
|
+
new Error(`Session not found: ${sessionId}`),
|
|
2070
|
+
{ code: ErrorCode.enum.SESSION_NOT_FOUND, statusCode: 404 }
|
|
2071
|
+
);
|
|
2072
|
+
}
|
|
2073
|
+
let sessionManager;
|
|
2074
|
+
try {
|
|
2075
|
+
sessionManager = SessionManager2.open(savedPiFile, void 0, runtimeCwd);
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
throw Object.assign(
|
|
2078
|
+
new Error(`failed to open pi session transcript for '${sessionId}'`),
|
|
2079
|
+
{ code: ErrorCode.enum.SESSION_TRANSCRIPT_UNREADABLE, statusCode: 500, cause: error }
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
1725
2082
|
refreshEffectiveResources();
|
|
1726
2083
|
const composedSystemPromptAppend = composeSystemPromptAppend(opts.systemPromptAppend);
|
|
1727
2084
|
const dynamicPromptExtension = opts.systemPromptDynamic ? buildDynamicPromptExtension(opts.systemPromptDynamic) : void 0;
|
|
@@ -1746,6 +2103,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
1746
2103
|
...extensionFactories.length ? { extensionFactories } : {},
|
|
1747
2104
|
...pi.noContextFiles ? { noContextFiles: true } : {},
|
|
1748
2105
|
...pi.noSkills ? { noSkills: true } : {},
|
|
2106
|
+
...pi.noExtensions ? { noExtensions: true } : {},
|
|
1749
2107
|
...effectiveSkillPaths.length ? { additionalSkillPaths: effectiveSkillPaths } : {},
|
|
1750
2108
|
// skillsOverride REPLACES Pi's resolved skill set, which includes
|
|
1751
2109
|
// skills contributed by host-declared pi packages (e.g.
|
|
@@ -1771,7 +2129,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
1771
2129
|
// adapted tool catalog active. Do NOT pass an explicit empty tool-name
|
|
1772
2130
|
// allowlist: in the current Pi SDK that disables custom tools too.
|
|
1773
2131
|
noTools: "builtin",
|
|
1774
|
-
customTools: adaptToolsForPi(opts.tools,
|
|
2132
|
+
customTools: adaptToolsForPi(opts.tools, sessionId, opts.telemetry, () => runContextStorage.getStore()),
|
|
1775
2133
|
model,
|
|
1776
2134
|
thinkingLevel: input.thinkingLevel ?? "off",
|
|
1777
2135
|
sessionManager,
|
|
@@ -1779,13 +2137,6 @@ function createPiCodingAgentHarness(opts) {
|
|
|
1779
2137
|
modelRegistry,
|
|
1780
2138
|
...resourceLoader ? { resourceLoader } : {}
|
|
1781
2139
|
});
|
|
1782
|
-
if (isNewPiSession) {
|
|
1783
|
-
const piFile = sessionManager.getSessionFile();
|
|
1784
|
-
if (piFile) {
|
|
1785
|
-
sessionStore.savePiSessionFile(sessionCtx, sessionId, piFile).catch(() => {
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
2140
|
const restoreFollowUpContextWrapper = rememberQueuedFollowUpRunContexts(piSession, runContextState, () => runContextStorage.getStore());
|
|
1790
2141
|
const unsubscribePiRunContextListener = piSession.subscribe((event) => updateRunContextStateFromPiEvent(runContextState, event, (ctx2) => runContextStorage.enterWith(ctx2)));
|
|
1791
2142
|
const unsubscribeRunContextListener = () => {
|
|
@@ -1812,34 +2163,36 @@ function createPiCodingAgentHarness(opts) {
|
|
|
1812
2163
|
await Promise.all(handles.map((handle) => handle.piSession.reload()));
|
|
1813
2164
|
return true;
|
|
1814
2165
|
}
|
|
2166
|
+
function disposeHandleAt(key, handle) {
|
|
2167
|
+
handle.unsubscribeRunContextListener();
|
|
2168
|
+
handle.piSession.dispose();
|
|
2169
|
+
if (piSessions.get(key) === handle) piSessions.delete(key);
|
|
2170
|
+
}
|
|
1815
2171
|
function disposePiSession(sessionId, ctx) {
|
|
1816
2172
|
if (ctx) {
|
|
1817
2173
|
const key = sessionCacheKey(sessionId, ctx);
|
|
1818
2174
|
const handle = piSessions.get(key);
|
|
1819
2175
|
if (!handle) return;
|
|
1820
|
-
handle
|
|
1821
|
-
handle.piSession.dispose();
|
|
1822
|
-
piSessions.delete(key);
|
|
2176
|
+
disposeHandleAt(key, handle);
|
|
1823
2177
|
return;
|
|
1824
2178
|
}
|
|
1825
2179
|
for (const [key, handle] of piSessions) {
|
|
1826
2180
|
if (handle.sessionId !== sessionId) continue;
|
|
1827
|
-
handle
|
|
1828
|
-
handle.piSession.dispose();
|
|
1829
|
-
piSessions.delete(key);
|
|
2181
|
+
disposeHandleAt(key, handle);
|
|
1830
2182
|
}
|
|
1831
2183
|
}
|
|
1832
2184
|
function piSessionHandlesFor(sessionId) {
|
|
1833
2185
|
return [...piSessions.values()].filter((handle) => handle.sessionId === sessionId);
|
|
1834
2186
|
}
|
|
1835
2187
|
async function getOrCreatePiSessionForCommand(sessionId, ctx) {
|
|
1836
|
-
const sessionCtx = sessionCtxFromRunContext(ctx);
|
|
1837
|
-
const existing = piSessions.get(sessionCacheKey(sessionId, sessionCtx));
|
|
1838
|
-
if (existing) return existing;
|
|
2188
|
+
const sessionCtx = normalizeSessionCtx2(ctx.sessionCtx) ?? sessionCtxFromRunContext(ctx);
|
|
1839
2189
|
return getOrCreatePiSession(sessionId, { sessionId, content: "", ctx: sessionCtx }, ctx);
|
|
1840
2190
|
}
|
|
1841
2191
|
const originalDelete = sessionStore.delete.bind(sessionStore);
|
|
1842
2192
|
sessionStore.delete = async (ctx, sessionId) => {
|
|
2193
|
+
const key = sessionCacheKey(sessionId, ctx);
|
|
2194
|
+
sessionGenerations.set(key, generationOf(key) + 1);
|
|
2195
|
+
await Promise.allSettled([piSessionCreations.get(key)]);
|
|
1843
2196
|
await originalDelete(ctx, sessionId);
|
|
1844
2197
|
disposePiSession(sessionId, ctx);
|
|
1845
2198
|
};
|
|
@@ -1932,8 +2285,8 @@ export {
|
|
|
1932
2285
|
piPackageSourceKey,
|
|
1933
2286
|
compactPiPackages,
|
|
1934
2287
|
mergePiPackageSources,
|
|
1935
|
-
withPiHarnessDefaults,
|
|
1936
2288
|
createResourceSettingsManager,
|
|
2289
|
+
withPiHarnessDefaults,
|
|
1937
2290
|
deriveSourcePlugin,
|
|
1938
2291
|
createPiCodingAgentHarness
|
|
1939
2292
|
};
|