@hachej/boring-agent 0.1.41 → 0.1.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -252
- package/dist/{DebugDrawer-2PUDZ2RL.js → DebugDrawer-RYEBQ6CO.js} +1 -1
- package/dist/{agentPluginEvents-CKrZLW3g.d.ts → agentPluginEvents-DP-vLNCs.d.ts} +36 -3
- package/dist/{session-BRovhe0D.d.ts → chatSubmitPayload-gC61O4Ni.d.ts} +20 -1
- package/dist/{chunk-IUJ22EFB.js → chunk-BOIV2I3N.js} +2 -10
- package/dist/{chunk-VQ7VSSSX.js → chunk-YUDMSYAO.js} +6 -1
- package/dist/front/index.d.ts +22 -5
- package/dist/front/index.js +155 -103
- package/dist/front/styles.css +0 -6
- package/dist/piChatEvent-DR9a-FVz.d.ts +306 -0
- package/dist/server/index.d.ts +363 -94
- package/dist/server/index.js +1611 -414
- package/dist/shared/index.d.ts +29 -23
- package/dist/shared/index.js +1 -1
- package/docs/ACCESSIBILITY.md +11 -13
- package/docs/API.md +86 -59
- package/docs/CSP.md +10 -9
- package/docs/ERROR_CODES.md +5 -1
- package/docs/KNOWN_LIMITATIONS.md +3 -3
- package/docs/MIGRATION.md +16 -16
- package/docs/PERFORMANCE.md +1 -1
- package/docs/PLUGINS.md +42 -12
- package/docs/README.md +89 -12
- package/docs/RISKS-MULTI-TAB.md +1 -1
- package/docs/STYLING.md +6 -4
- package/docs/UI-SHADCN.md +7 -7
- package/docs/runtime-provisioning.md +7 -3
- package/docs/runtime.md +68 -13
- package/docs/tools.md +37 -14
- package/package.json +2 -2
- package/dist/piChatEvent-Ck1BAE_m.d.ts +0 -323
- /package/docs/plans/{AGENT_EVAL_FRAMEWORK.md → archive/AGENT_EVAL_FRAMEWORK.md} +0 -0
- /package/docs/plans/{agent-package-spec.md → archive/agent-package-spec.md} +0 -0
- /package/docs/plans/{harness-followup-capabilities.md → archive/harness-followup-capabilities.md} +0 -0
- /package/docs/plans/{harness-tool-ui-capabilities.md → archive/harness-tool-ui-capabilities.md} +0 -0
- /package/docs/plans/{pi-followup-history-projection.md → archive/pi-followup-history-projection.md} +0 -0
- /package/docs/plans/{pi-tools-migration.md → archive/pi-tools-migration.md} +0 -0
- /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-codex-review.md +0 -0
- /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-gpt-review.md +0 -0
- /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-opus-review.md +0 -0
- /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-xai-review.md +0 -0
- /package/docs/plans/{vercel-base-snapshot-template-plan.md → archive/vercel-base-snapshot-template-plan.md} +0 -0
- /package/docs/plans/{vercel-persistent-sandbox-adapter.md → archive/vercel-persistent-sandbox-adapter.md} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
StopPayloadSchema,
|
|
14
14
|
extractToolUiMetadata,
|
|
15
15
|
sanitizeToolUiMetadata2 as sanitizeToolUiMetadata
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-YUDMSYAO.js";
|
|
17
17
|
|
|
18
18
|
// src/server/sandbox/direct/createDirectSandbox.ts
|
|
19
19
|
import { spawn } from "child_process";
|
|
@@ -257,7 +257,7 @@ function createDirectSandbox(opts = {}) {
|
|
|
257
257
|
import { access, stat as stat3 } from "fs/promises";
|
|
258
258
|
import { constants } from "fs";
|
|
259
259
|
import { spawn as spawn2 } from "child_process";
|
|
260
|
-
import { dirname as dirname4, isAbsolute as isAbsolute3, join as
|
|
260
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join3, posix, relative as relative3, resolve as resolve4, sep as sep2 } from "path";
|
|
261
261
|
|
|
262
262
|
// src/server/sandbox/bwrap/buildBwrapArgs.ts
|
|
263
263
|
import { isAbsolute } from "path";
|
|
@@ -305,10 +305,13 @@ function validateWorkspaceRoot(workspaceRoot) {
|
|
|
305
305
|
}
|
|
306
306
|
function buildBwrapArgs(workspaceRoot, options) {
|
|
307
307
|
validateWorkspaceRoot(workspaceRoot);
|
|
308
|
+
const network = options?.network ?? "shared";
|
|
308
309
|
const args = [
|
|
309
310
|
"--unshare-all",
|
|
310
|
-
"--share-net",
|
|
311
|
+
...network === "shared" ? ["--share-net"] : [],
|
|
311
312
|
"--die-with-parent",
|
|
313
|
+
...options?.newSession === false ? [] : ["--new-session"],
|
|
314
|
+
...options?.dropAllCapabilities ? ["--cap-drop", "ALL"] : [],
|
|
312
315
|
"--tmpfs",
|
|
313
316
|
"/",
|
|
314
317
|
"--proc",
|
|
@@ -345,9 +348,8 @@ function buildBwrapArgs(workspaceRoot, options) {
|
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
// src/server/workspace/createNodeWorkspace.ts
|
|
348
|
-
import { lstat as lstat2, mkdir, readdir, readFile, rename, rm, stat as stat2, unlink, writeFile } from "fs/promises";
|
|
349
|
-
import { dirname as dirname3,
|
|
350
|
-
import chokidar from "chokidar";
|
|
351
|
+
import { lstat as lstat2, mkdir, readdir as readdir2, readFile, rename, rm, stat as stat2, unlink, writeFile } from "fs/promises";
|
|
352
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
351
353
|
|
|
352
354
|
// src/server/workspace/paths.ts
|
|
353
355
|
import { lstat, realpath, stat } from "fs/promises";
|
|
@@ -428,6 +430,11 @@ async function ensureWritableWorkspacePath(workspaceRoot, relPath) {
|
|
|
428
430
|
return absPath;
|
|
429
431
|
}
|
|
430
432
|
|
|
433
|
+
// src/server/workspace/nodeWatcher.ts
|
|
434
|
+
import { readdir } from "fs/promises";
|
|
435
|
+
import { join as join2, relative as relative2, sep } from "path";
|
|
436
|
+
import chokidar from "chokidar";
|
|
437
|
+
|
|
431
438
|
// src/server/workspace/ignore.ts
|
|
432
439
|
var DEFAULT_IGNORED_DIR_NAMES = [
|
|
433
440
|
"node_modules",
|
|
@@ -446,19 +453,136 @@ function isIgnoredDirName(name) {
|
|
|
446
453
|
return IGNORED_SET.has(name) || name.endsWith(".tsbuildinfo");
|
|
447
454
|
}
|
|
448
455
|
|
|
449
|
-
// src/server/
|
|
450
|
-
var
|
|
456
|
+
// src/server/logging.ts
|
|
457
|
+
var SENSITIVE_KEYS = new Set([
|
|
458
|
+
"apiKey",
|
|
459
|
+
"api_key",
|
|
460
|
+
"token",
|
|
461
|
+
"secret",
|
|
462
|
+
"password",
|
|
463
|
+
"authorization",
|
|
464
|
+
"cookie",
|
|
465
|
+
"oidcToken",
|
|
466
|
+
"accessToken",
|
|
467
|
+
"refreshToken",
|
|
468
|
+
"ANTHROPIC_API_KEY",
|
|
469
|
+
"OPENAI_API_KEY",
|
|
470
|
+
"VERCEL_OIDC_TOKEN",
|
|
471
|
+
"VERCEL_TEAM_ID"
|
|
472
|
+
].map((key) => key.toLowerCase()));
|
|
473
|
+
function isSensitiveKey(key) {
|
|
474
|
+
return SENSITIVE_KEYS.has(key.toLowerCase());
|
|
475
|
+
}
|
|
476
|
+
function redactValue(key, value, seen) {
|
|
477
|
+
if (key && isSensitiveKey(key) && value != null) return "***";
|
|
478
|
+
if (value == null || typeof value !== "object") return value;
|
|
479
|
+
if (value instanceof Date) return value;
|
|
480
|
+
if (seen.has(value)) return "[Circular]";
|
|
481
|
+
seen.add(value);
|
|
482
|
+
if (Array.isArray(value)) {
|
|
483
|
+
const out2 = value.map((item) => redactValue(void 0, item, seen));
|
|
484
|
+
seen.delete(value);
|
|
485
|
+
return out2;
|
|
486
|
+
}
|
|
487
|
+
const out = {};
|
|
488
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
489
|
+
out[childKey] = redactValue(childKey, childValue, seen);
|
|
490
|
+
}
|
|
491
|
+
seen.delete(value);
|
|
492
|
+
return out;
|
|
493
|
+
}
|
|
494
|
+
function redact(fields) {
|
|
495
|
+
const out = {};
|
|
496
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
497
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
498
|
+
out[k] = redactValue(k, v, seen);
|
|
499
|
+
}
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
var verbose = typeof process !== "undefined" && process.env?.BORING_AGENT_VERBOSE === "1";
|
|
503
|
+
function createLogger(prefix) {
|
|
504
|
+
function emit(level, msg, fields) {
|
|
505
|
+
const entry = {
|
|
506
|
+
level,
|
|
507
|
+
prefix,
|
|
508
|
+
msg,
|
|
509
|
+
...fields ? redact(fields) : {},
|
|
510
|
+
t: (/* @__PURE__ */ new Date()).toISOString()
|
|
511
|
+
};
|
|
512
|
+
if (level === "error") {
|
|
513
|
+
console.error(JSON.stringify(entry));
|
|
514
|
+
} else if (level === "warn") {
|
|
515
|
+
console.warn(JSON.stringify(entry));
|
|
516
|
+
} else {
|
|
517
|
+
console.log(JSON.stringify(entry));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
debug(msg, fields) {
|
|
522
|
+
if (verbose) emit("debug", msg, fields);
|
|
523
|
+
},
|
|
524
|
+
info(msg, fields) {
|
|
525
|
+
emit("info", msg, fields);
|
|
526
|
+
},
|
|
527
|
+
warn(msg, fields) {
|
|
528
|
+
emit("warn", msg, fields);
|
|
529
|
+
},
|
|
530
|
+
error(msg, fields) {
|
|
531
|
+
emit("error", msg, fields);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/server/workspace/nodeWatcher.ts
|
|
537
|
+
var log = createLogger("workspace-watch");
|
|
451
538
|
function shouldIgnoreWatchPath(root, path4) {
|
|
452
539
|
const relPath = relative2(root, path4);
|
|
453
540
|
const parts = relPath.split(sep);
|
|
454
541
|
return parts.some((part) => isIgnoredDirName(part));
|
|
455
542
|
}
|
|
543
|
+
function toPosixRel(root, absPath) {
|
|
544
|
+
return relative2(root, absPath).split(sep).join("/");
|
|
545
|
+
}
|
|
546
|
+
var RENAME_ECHO_IDLE_MS = 1e3;
|
|
547
|
+
var RENAME_ECHO_MAX_MS = 3e4;
|
|
548
|
+
var RENAME_ECHO_FROM_KINDS = /* @__PURE__ */ new Set(["unlink", "unlinkDir"]);
|
|
549
|
+
var RENAME_ECHO_TO_KINDS = /* @__PURE__ */ new Set(["add", "addDir"]);
|
|
550
|
+
var DEFAULT_MAX_WATCHED_ENTRIES = 5e4;
|
|
551
|
+
function maxWatchedEntries() {
|
|
552
|
+
const raw = getEnv("BORING_MAX_WATCHED_ENTRIES");
|
|
553
|
+
if (!raw) return DEFAULT_MAX_WATCHED_ENTRIES;
|
|
554
|
+
const n = Number.parseInt(raw, 10);
|
|
555
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_WATCHED_ENTRIES;
|
|
556
|
+
}
|
|
557
|
+
async function countWatchableEntries(root, cap) {
|
|
558
|
+
let count = 0;
|
|
559
|
+
const stack = [root];
|
|
560
|
+
while (stack.length > 0) {
|
|
561
|
+
const dir = stack.pop();
|
|
562
|
+
let entries;
|
|
563
|
+
try {
|
|
564
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
565
|
+
} catch {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
for (const entry of entries) {
|
|
569
|
+
count += 1;
|
|
570
|
+
if (count > cap) return count;
|
|
571
|
+
if (entry.isDirectory() && !isIgnoredDirName(entry.name)) {
|
|
572
|
+
stack.push(join2(dir, entry.name));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return count;
|
|
577
|
+
}
|
|
456
578
|
function createNodeWatcher(root) {
|
|
457
579
|
const listeners = /* @__PURE__ */ new Set();
|
|
580
|
+
const suppressions = [];
|
|
458
581
|
let fsw = null;
|
|
459
582
|
let closed = false;
|
|
460
|
-
|
|
461
|
-
|
|
583
|
+
let readiness = null;
|
|
584
|
+
const startFsw = () => {
|
|
585
|
+
if (fsw || closed) return;
|
|
462
586
|
fsw = chokidar.watch(root, {
|
|
463
587
|
ignored: (path4) => shouldIgnoreWatchPath(root, path4),
|
|
464
588
|
ignoreInitial: true,
|
|
@@ -468,23 +592,43 @@ function createNodeWatcher(root) {
|
|
|
468
592
|
// servers. Consumers already reconcile by mtime after invalidation.
|
|
469
593
|
// No native renames from chokidar — `unlinkDir`/`addDir`/
|
|
470
594
|
// `unlink`/`add` are the primitives we get. We surface them as
|
|
471
|
-
// separate `unlink` + `write`/`mkdir` events
|
|
472
|
-
//
|
|
595
|
+
// separate `unlink` + `write`/`mkdir` events. Renames the host
|
|
596
|
+
// performs itself arrive via `emitRename` instead, which also
|
|
597
|
+
// mutes this echo.
|
|
473
598
|
});
|
|
474
|
-
fsw.on("add", (p, s) => emit({ op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
475
|
-
fsw.on("change", (p, s) => emit({ op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
476
|
-
fsw.on("addDir", (p) => emit({ op: "mkdir", path: rel(p) }));
|
|
477
|
-
fsw.on("unlink", (p) => emit({ op: "unlink", path: rel(p) }));
|
|
478
|
-
fsw.on("unlinkDir", (p) => emit({ op: "unlink", path: rel(p) }));
|
|
479
|
-
|
|
599
|
+
fsw.on("add", (p, s) => emit("add", { op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
600
|
+
fsw.on("change", (p, s) => emit("change", { op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
601
|
+
fsw.on("addDir", (p) => emit("addDir", { op: "mkdir", path: rel(p) }));
|
|
602
|
+
fsw.on("unlink", (p) => emit("unlink", { op: "unlink", path: rel(p) }));
|
|
603
|
+
fsw.on("unlinkDir", (p) => emit("unlinkDir", { op: "unlink", path: rel(p) }));
|
|
604
|
+
let loggedError = false;
|
|
605
|
+
fsw.on("error", (err) => {
|
|
606
|
+
if (loggedError) return;
|
|
607
|
+
loggedError = true;
|
|
608
|
+
log.error("file watcher error \u2014 live file events may be incomplete", {
|
|
609
|
+
root,
|
|
610
|
+
error: err instanceof Error ? err.message : String(err)
|
|
611
|
+
});
|
|
480
612
|
});
|
|
481
|
-
return fsw;
|
|
482
613
|
};
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
614
|
+
const ensureFsw = () => {
|
|
615
|
+
if (readiness) return readiness;
|
|
616
|
+
readiness = (async () => {
|
|
617
|
+
const cap = maxWatchedEntries();
|
|
618
|
+
const count = await countWatchableEntries(root, cap);
|
|
619
|
+
if (closed) return { ok: false, reason: "closed" };
|
|
620
|
+
if (count > cap) {
|
|
621
|
+
const message = `workspace at ${root} has more than ${cap} entries \u2014 file watching disabled. Start boring-ui in a smaller subfolder for live file updates, or raise BORING_MAX_WATCHED_ENTRIES.`;
|
|
622
|
+
log.error(message, { root, cap });
|
|
623
|
+
return { ok: false, reason: "workspace_too_large", message };
|
|
624
|
+
}
|
|
625
|
+
startFsw();
|
|
626
|
+
return { ok: true };
|
|
627
|
+
})();
|
|
628
|
+
return readiness;
|
|
486
629
|
};
|
|
487
|
-
const
|
|
630
|
+
const rel = (abs) => toPosixRel(root, abs);
|
|
631
|
+
const fanout = (event) => {
|
|
488
632
|
if (closed) return;
|
|
489
633
|
if (event.path === "" || event.path.startsWith("..")) return;
|
|
490
634
|
for (const l of [...listeners]) {
|
|
@@ -494,26 +638,67 @@ function createNodeWatcher(root) {
|
|
|
494
638
|
}
|
|
495
639
|
}
|
|
496
640
|
};
|
|
641
|
+
const isSuppressedEcho = (kind, path4) => {
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
for (let i = suppressions.length - 1; i >= 0; i--) {
|
|
644
|
+
const s = suppressions[i];
|
|
645
|
+
if (now > s.idleDeadline || now > s.hardDeadline) {
|
|
646
|
+
suppressions.splice(i, 1);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (s.kinds.has(kind) && (path4 === s.prefix || path4.startsWith(`${s.prefix}/`))) {
|
|
650
|
+
s.idleDeadline = Math.min(now + RENAME_ECHO_IDLE_MS, s.hardDeadline);
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return false;
|
|
655
|
+
};
|
|
656
|
+
const emit = (kind, event) => {
|
|
657
|
+
if (isSuppressedEcho(kind, event.path)) return;
|
|
658
|
+
fanout(event);
|
|
659
|
+
};
|
|
497
660
|
return {
|
|
498
661
|
subscribe(listener) {
|
|
499
662
|
if (closed) return () => {
|
|
500
663
|
};
|
|
501
664
|
listeners.add(listener);
|
|
502
|
-
ensureFsw();
|
|
665
|
+
void ensureFsw();
|
|
503
666
|
return () => {
|
|
504
667
|
listeners.delete(listener);
|
|
505
668
|
};
|
|
506
669
|
},
|
|
670
|
+
whenReady() {
|
|
671
|
+
return ensureFsw();
|
|
672
|
+
},
|
|
673
|
+
emitRename(fromRel, toRel) {
|
|
674
|
+
if (closed) return;
|
|
675
|
+
if (fsw) {
|
|
676
|
+
const now = Date.now();
|
|
677
|
+
const window = {
|
|
678
|
+
idleDeadline: now + RENAME_ECHO_IDLE_MS,
|
|
679
|
+
hardDeadline: now + RENAME_ECHO_MAX_MS
|
|
680
|
+
};
|
|
681
|
+
suppressions.push(
|
|
682
|
+
{ prefix: fromRel, kinds: RENAME_ECHO_FROM_KINDS, ...window },
|
|
683
|
+
{ prefix: toRel, kinds: RENAME_ECHO_TO_KINDS, ...window }
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
fanout({ op: "rename", path: toRel, oldPath: fromRel });
|
|
687
|
+
},
|
|
507
688
|
close() {
|
|
508
689
|
if (closed) return;
|
|
509
690
|
closed = true;
|
|
510
691
|
listeners.clear();
|
|
692
|
+
suppressions.length = 0;
|
|
511
693
|
fsw?.close().catch(() => {
|
|
512
694
|
});
|
|
513
695
|
fsw = null;
|
|
514
696
|
}
|
|
515
697
|
};
|
|
516
698
|
}
|
|
699
|
+
|
|
700
|
+
// src/server/workspace/createNodeWorkspace.ts
|
|
701
|
+
var EPERM_CODE = "EPERM";
|
|
517
702
|
var nodeWorkspaceHostRoots = /* @__PURE__ */ new WeakMap();
|
|
518
703
|
function getNodeWorkspaceHostRoot(workspace) {
|
|
519
704
|
return nodeWorkspaceHostRoots.get(workspace);
|
|
@@ -594,7 +779,7 @@ function createNodeWorkspace(root, opts = {}) {
|
|
|
594
779
|
},
|
|
595
780
|
async readdir(relPath) {
|
|
596
781
|
const absPath = await ensureExistingWorkspacePath(root, relPath);
|
|
597
|
-
const entries = await
|
|
782
|
+
const entries = await readdir2(absPath, { withFileTypes: true });
|
|
598
783
|
return entries.map((entry) => ({
|
|
599
784
|
name: entry.name,
|
|
600
785
|
kind: entry.isDirectory() ? "dir" : "file"
|
|
@@ -632,6 +817,7 @@ function createNodeWorkspace(root, opts = {}) {
|
|
|
632
817
|
const fromAbsPath = await ensureExistingWorkspacePath(root, fromRelPath);
|
|
633
818
|
const toAbsPath = await ensureWritableWorkspacePath(root, toRelPath2);
|
|
634
819
|
await rename(fromAbsPath, toAbsPath);
|
|
820
|
+
cachedWatcher?.emitRename(toPosixRel(root, fromAbsPath), toPosixRel(root, toAbsPath));
|
|
635
821
|
}
|
|
636
822
|
};
|
|
637
823
|
nodeWorkspaceHostRoots.set(workspace, root);
|
|
@@ -757,8 +943,8 @@ async function buildGlobalToolMounts(workspaceRoot) {
|
|
|
757
943
|
if (globalRoot === workspaceRoot) return [];
|
|
758
944
|
const args = [];
|
|
759
945
|
const mountIfChildLacks = async (runtimeRelPath) => {
|
|
760
|
-
const parentRuntimePath =
|
|
761
|
-
const childRuntimePath =
|
|
946
|
+
const parentRuntimePath = join3(globalRoot, runtimeRelPath);
|
|
947
|
+
const childRuntimePath = join3(workspaceRoot, runtimeRelPath);
|
|
762
948
|
if (!await dirExists(parentRuntimePath)) return false;
|
|
763
949
|
if (await pathExists(childRuntimePath)) return false;
|
|
764
950
|
args.push("--ro-bind", parentRuntimePath, `${SANDBOX_HOME2}/${runtimeRelPath}`);
|
|
@@ -770,10 +956,45 @@ async function buildGlobalToolMounts(workspaceRoot) {
|
|
|
770
956
|
}
|
|
771
957
|
return args;
|
|
772
958
|
}
|
|
959
|
+
function positiveInteger(value, name) {
|
|
960
|
+
if (value === void 0) return void 0;
|
|
961
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
962
|
+
throw new Error(`${name} must be a positive integer`);
|
|
963
|
+
}
|
|
964
|
+
return Math.floor(value);
|
|
965
|
+
}
|
|
966
|
+
function buildResourceLimitCommands(limits) {
|
|
967
|
+
if (!limits) return [];
|
|
968
|
+
const commands = [];
|
|
969
|
+
const cpuSeconds = positiveInteger(limits.cpuSeconds, "resourceLimits.cpuSeconds");
|
|
970
|
+
const fileSizeBlocks = positiveInteger(limits.fileSizeBlocks, "resourceLimits.fileSizeBlocks");
|
|
971
|
+
const maxProcesses = positiveInteger(limits.maxProcesses, "resourceLimits.maxProcesses");
|
|
972
|
+
const openFiles = positiveInteger(limits.openFiles, "resourceLimits.openFiles");
|
|
973
|
+
const virtualMemoryKb = positiveInteger(limits.virtualMemoryKb, "resourceLimits.virtualMemoryKb");
|
|
974
|
+
if (cpuSeconds !== void 0) commands.push(`ulimit -t ${cpuSeconds}`);
|
|
975
|
+
if (fileSizeBlocks !== void 0) commands.push(`ulimit -f ${fileSizeBlocks}`);
|
|
976
|
+
if (maxProcesses !== void 0) commands.push(`ulimit -u ${maxProcesses}`);
|
|
977
|
+
if (openFiles !== void 0) commands.push(`ulimit -n ${openFiles}`);
|
|
978
|
+
if (virtualMemoryKb !== void 0) commands.push(`ulimit -v ${virtualMemoryKb}`);
|
|
979
|
+
return commands;
|
|
980
|
+
}
|
|
981
|
+
function buildCommandArgs(cmd, limits) {
|
|
982
|
+
const limitCommands = buildResourceLimitCommands(limits);
|
|
983
|
+
if (limitCommands.length === 0) return ["bash", "-c", cmd];
|
|
984
|
+
return [
|
|
985
|
+
"bash",
|
|
986
|
+
"-c",
|
|
987
|
+
`${limitCommands.join("\n")}
|
|
988
|
+
exec bash -c "$1"`,
|
|
989
|
+
"boring-exec",
|
|
990
|
+
cmd
|
|
991
|
+
];
|
|
992
|
+
}
|
|
773
993
|
function createBwrapSandbox(opts = {}) {
|
|
994
|
+
const sandboxOptions = opts;
|
|
774
995
|
let workspace = null;
|
|
775
|
-
let hostWorkspaceRoot =
|
|
776
|
-
let runtimeContext =
|
|
996
|
+
let hostWorkspaceRoot = sandboxOptions.hostWorkspaceRoot;
|
|
997
|
+
let runtimeContext = sandboxOptions.runtimeContext ?? { runtimeCwd: SANDBOX_HOME2 };
|
|
777
998
|
return {
|
|
778
999
|
id: "bwrap",
|
|
779
1000
|
placement: "server",
|
|
@@ -784,8 +1005,8 @@ function createBwrapSandbox(opts = {}) {
|
|
|
784
1005
|
},
|
|
785
1006
|
async init(ctx) {
|
|
786
1007
|
workspace = ctx.workspace;
|
|
787
|
-
hostWorkspaceRoot =
|
|
788
|
-
runtimeContext =
|
|
1008
|
+
hostWorkspaceRoot = sandboxOptions.hostWorkspaceRoot ?? getNodeWorkspaceHostRoot(ctx.workspace) ?? ctx.workspace.root;
|
|
1009
|
+
runtimeContext = sandboxOptions.runtimeContext ?? { runtimeCwd: SANDBOX_HOME2 };
|
|
789
1010
|
await assertBwrapAvailable();
|
|
790
1011
|
},
|
|
791
1012
|
async exec(cmd, opts2) {
|
|
@@ -798,12 +1019,14 @@ function createBwrapSandbox(opts = {}) {
|
|
|
798
1019
|
const workspaceRoot = hostWorkspaceRoot ?? workspace.root;
|
|
799
1020
|
const sandboxCwd = computeSandboxCwd(workspaceRoot, runtimeContext.runtimeCwd, opts2?.cwd);
|
|
800
1021
|
const postWorkspaceArgs = await buildGlobalToolMounts(workspaceRoot);
|
|
801
|
-
const baseArgs = buildBwrapArgs(workspaceRoot, {
|
|
1022
|
+
const baseArgs = buildBwrapArgs(workspaceRoot, {
|
|
1023
|
+
postWorkspaceArgs,
|
|
1024
|
+
network: sandboxOptions.network,
|
|
1025
|
+
dropAllCapabilities: sandboxOptions.dropAllCapabilities
|
|
1026
|
+
});
|
|
802
1027
|
const args = [
|
|
803
1028
|
...withSandboxCwd(baseArgs, sandboxCwd),
|
|
804
|
-
|
|
805
|
-
"-c",
|
|
806
|
-
cmd
|
|
1029
|
+
...buildCommandArgs(cmd, sandboxOptions.resourceLimits)
|
|
807
1030
|
];
|
|
808
1031
|
return await new Promise((resolve11, reject) => {
|
|
809
1032
|
const child = spawn2("bwrap", args, {
|
|
@@ -1634,6 +1857,492 @@ async function prepareVercelDeploymentSnapshot(opts) {
|
|
|
1634
1857
|
});
|
|
1635
1858
|
}
|
|
1636
1859
|
|
|
1860
|
+
// src/server/runtime/createServerFileSearch.ts
|
|
1861
|
+
var DEFAULT_LIMIT = 500;
|
|
1862
|
+
var MAX_LIMIT = 5e3;
|
|
1863
|
+
var SEARCH_TIMEOUT_MS = 5e3;
|
|
1864
|
+
var SEARCH_MAX_OUTPUT_BYTES = 256e3;
|
|
1865
|
+
function shellQuote2(value) {
|
|
1866
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
1867
|
+
}
|
|
1868
|
+
function normalizeLimit(limit) {
|
|
1869
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
1870
|
+
return DEFAULT_LIMIT;
|
|
1871
|
+
}
|
|
1872
|
+
const normalized = Math.trunc(limit);
|
|
1873
|
+
if (normalized <= 0) return DEFAULT_LIMIT;
|
|
1874
|
+
return Math.min(normalized, MAX_LIMIT);
|
|
1875
|
+
}
|
|
1876
|
+
function buildFindArgs(glob) {
|
|
1877
|
+
const isPathShaped = glob.includes("/") || glob.includes("**");
|
|
1878
|
+
if (!isPathShaped) {
|
|
1879
|
+
return `-iname ${shellQuote2(glob)}`;
|
|
1880
|
+
}
|
|
1881
|
+
let translated = glob.replaceAll("**", "*");
|
|
1882
|
+
translated = translated.replace(/^\/+/, "");
|
|
1883
|
+
if (!translated.startsWith("*")) {
|
|
1884
|
+
translated = `*${translated}`;
|
|
1885
|
+
}
|
|
1886
|
+
return `-ipath ${shellQuote2(translated)}`;
|
|
1887
|
+
}
|
|
1888
|
+
function createServerFileSearch(workspace, sandbox) {
|
|
1889
|
+
return {
|
|
1890
|
+
async search(glob, limit = DEFAULT_LIMIT) {
|
|
1891
|
+
const safeLimit = normalizeLimit(limit);
|
|
1892
|
+
const command = [
|
|
1893
|
+
"find .",
|
|
1894
|
+
"-maxdepth 10",
|
|
1895
|
+
"\\( -path './.boring-agent' -o -path './.git' -o -path './node_modules' \\) -prune -o",
|
|
1896
|
+
buildFindArgs(glob),
|
|
1897
|
+
"-type f",
|
|
1898
|
+
"-print",
|
|
1899
|
+
`| head -n ${safeLimit}`
|
|
1900
|
+
].join(" ");
|
|
1901
|
+
const { stdout, exitCode } = await sandbox.exec(command, {
|
|
1902
|
+
cwd: workspace.root,
|
|
1903
|
+
timeoutMs: SEARCH_TIMEOUT_MS,
|
|
1904
|
+
maxOutputBytes: SEARCH_MAX_OUTPUT_BYTES
|
|
1905
|
+
});
|
|
1906
|
+
if (exitCode !== 0) {
|
|
1907
|
+
throw new Error(`file-search failed: exit ${exitCode}`);
|
|
1908
|
+
}
|
|
1909
|
+
const decoded = new TextDecoder().decode(stdout);
|
|
1910
|
+
return decoded.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.length > 0).map((line) => line.replace(/^\.\//, ""));
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/server/sandbox/remote-worker/protocol.ts
|
|
1916
|
+
var REMOTE_WORKER_RUNTIME_CWD = "/workspace";
|
|
1917
|
+
var REMOTE_WORKER_PROVIDER = "remote-worker";
|
|
1918
|
+
var WORKER_INTERNAL_TOKEN_HEADER = "x-boring-internal-token";
|
|
1919
|
+
var WORKER_WORKSPACE_ID_HEADER = "x-boring-workspace-id";
|
|
1920
|
+
var WORKER_REQUEST_ID_HEADER = "x-boring-request-id";
|
|
1921
|
+
|
|
1922
|
+
// src/server/sandbox/remote-worker/createRemoteWorkerSandbox.ts
|
|
1923
|
+
function filteredEnv(env) {
|
|
1924
|
+
if (!env) return void 0;
|
|
1925
|
+
return Object.fromEntries(
|
|
1926
|
+
Object.entries(env).filter((entry) => typeof entry[1] === "string")
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
function createRemoteWorkerSandbox(client) {
|
|
1930
|
+
return {
|
|
1931
|
+
id: REMOTE_WORKER_PROVIDER,
|
|
1932
|
+
placement: "remote",
|
|
1933
|
+
provider: REMOTE_WORKER_PROVIDER,
|
|
1934
|
+
capabilities: ["exec"],
|
|
1935
|
+
runtimeContext: { runtimeCwd: REMOTE_WORKER_RUNTIME_CWD },
|
|
1936
|
+
async init() {
|
|
1937
|
+
await client.health();
|
|
1938
|
+
},
|
|
1939
|
+
async exec(cmd, opts = {}) {
|
|
1940
|
+
const request = {
|
|
1941
|
+
cmd,
|
|
1942
|
+
cwd: opts.cwd,
|
|
1943
|
+
env: filteredEnv(opts.env),
|
|
1944
|
+
timeoutMs: opts.timeoutMs,
|
|
1945
|
+
maxOutputBytes: opts.maxOutputBytes
|
|
1946
|
+
};
|
|
1947
|
+
const startedAt = Date.now();
|
|
1948
|
+
const heartbeat = opts.onHeartbeat ? setInterval(() => opts.onHeartbeat?.(Date.now() - startedAt), 1e3) : null;
|
|
1949
|
+
try {
|
|
1950
|
+
const result = await client.exec(request, { signal: opts.signal });
|
|
1951
|
+
opts.onStdout?.(result.stdout);
|
|
1952
|
+
opts.onStderr?.(result.stderr);
|
|
1953
|
+
return result;
|
|
1954
|
+
} finally {
|
|
1955
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/server/sandbox/remote-worker/workerClient.ts
|
|
1962
|
+
import { timingSafeEqual } from "crypto";
|
|
1963
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
|
|
1964
|
+
var DEFAULT_EXEC_TIMEOUT_MS = 3e4;
|
|
1965
|
+
var DEFAULT_EXEC_REQUEST_GRACE_MS = 1e4;
|
|
1966
|
+
var RemoteWorkerClientError = class extends Error {
|
|
1967
|
+
statusCode;
|
|
1968
|
+
code;
|
|
1969
|
+
details;
|
|
1970
|
+
constructor(message, opts) {
|
|
1971
|
+
super(message);
|
|
1972
|
+
this.name = "RemoteWorkerClientError";
|
|
1973
|
+
this.statusCode = opts.statusCode;
|
|
1974
|
+
this.code = opts.code ?? "remote_worker_error";
|
|
1975
|
+
this.details = opts.details;
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
function requireNonEmpty(value, label) {
|
|
1979
|
+
const trimmed = value.trim();
|
|
1980
|
+
if (!trimmed) throw new Error(`${label} is required for ${REMOTE_WORKER_PROVIDER} mode`);
|
|
1981
|
+
return trimmed;
|
|
1982
|
+
}
|
|
1983
|
+
function normalizeBaseUrl(value) {
|
|
1984
|
+
return requireNonEmpty(value, "BORING_WORKER_BASE_URL").replace(/\/+$/, "");
|
|
1985
|
+
}
|
|
1986
|
+
function positiveInteger2(value, fallback, label) {
|
|
1987
|
+
if (value === void 0) return fallback;
|
|
1988
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
|
|
1989
|
+
return Math.trunc(value);
|
|
1990
|
+
}
|
|
1991
|
+
function encodeWorkspaceId(workspaceId) {
|
|
1992
|
+
return encodeURIComponent(requireNonEmpty(workspaceId, "workspaceId"));
|
|
1993
|
+
}
|
|
1994
|
+
function bytesToBase64(bytes) {
|
|
1995
|
+
return Buffer.from(bytes).toString("base64");
|
|
1996
|
+
}
|
|
1997
|
+
function base64ToBytes(value) {
|
|
1998
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
1999
|
+
}
|
|
2000
|
+
function makeHeaders(opts) {
|
|
2001
|
+
const headers = new Headers();
|
|
2002
|
+
headers.set(WORKER_INTERNAL_TOKEN_HEADER, requireNonEmpty(opts.token, "BORING_WORKER_INTERNAL_TOKEN"));
|
|
2003
|
+
headers.set(WORKER_WORKSPACE_ID_HEADER, requireNonEmpty(opts.workspaceId, "workspaceId"));
|
|
2004
|
+
if (opts.requestId) headers.set(WORKER_REQUEST_ID_HEADER, opts.requestId);
|
|
2005
|
+
if (opts.contentType) headers.set("content-type", opts.contentType);
|
|
2006
|
+
return headers;
|
|
2007
|
+
}
|
|
2008
|
+
async function parseError(response) {
|
|
2009
|
+
let payload;
|
|
2010
|
+
try {
|
|
2011
|
+
payload = await response.json();
|
|
2012
|
+
} catch {
|
|
2013
|
+
}
|
|
2014
|
+
return new RemoteWorkerClientError(
|
|
2015
|
+
payload?.error?.message ?? `remote worker request failed (${response.status})`,
|
|
2016
|
+
{
|
|
2017
|
+
statusCode: payload?.error?.statusCode ?? response.status,
|
|
2018
|
+
code: payload?.error?.code,
|
|
2019
|
+
details: payload?.error?.details
|
|
2020
|
+
}
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
var RemoteWorkerClient = class {
|
|
2024
|
+
baseUrl;
|
|
2025
|
+
token;
|
|
2026
|
+
workspaceId;
|
|
2027
|
+
requestId;
|
|
2028
|
+
fetchImpl;
|
|
2029
|
+
requestTimeoutMs;
|
|
2030
|
+
execTimeoutMs;
|
|
2031
|
+
execRequestGraceMs;
|
|
2032
|
+
constructor(opts) {
|
|
2033
|
+
this.baseUrl = normalizeBaseUrl(opts.baseUrl);
|
|
2034
|
+
this.token = requireNonEmpty(opts.token, "BORING_WORKER_INTERNAL_TOKEN");
|
|
2035
|
+
this.workspaceId = requireNonEmpty(opts.workspaceId, "workspaceId");
|
|
2036
|
+
this.requestId = opts.requestId;
|
|
2037
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
2038
|
+
this.requestTimeoutMs = positiveInteger2(opts.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS, "requestTimeoutMs");
|
|
2039
|
+
this.execTimeoutMs = positiveInteger2(opts.execTimeoutMs, DEFAULT_EXEC_TIMEOUT_MS, "execTimeoutMs");
|
|
2040
|
+
this.execRequestGraceMs = positiveInteger2(opts.execRequestGraceMs, DEFAULT_EXEC_REQUEST_GRACE_MS, "execRequestGraceMs");
|
|
2041
|
+
}
|
|
2042
|
+
timeoutError(timeoutMs) {
|
|
2043
|
+
return new RemoteWorkerClientError("remote worker request timed out", {
|
|
2044
|
+
statusCode: 504,
|
|
2045
|
+
code: ErrorCode.enum.REMOTE_WORKER_TIMEOUT,
|
|
2046
|
+
details: { timeoutMs, retryable: true }
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
abortedError() {
|
|
2050
|
+
return new RemoteWorkerClientError("remote worker request aborted", {
|
|
2051
|
+
statusCode: 499,
|
|
2052
|
+
code: ErrorCode.enum.ABORTED
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
async fetchWithTimeout(url, init, timeoutMs) {
|
|
2056
|
+
const controller = new AbortController();
|
|
2057
|
+
let timedOut = false;
|
|
2058
|
+
const timer = setTimeout(() => {
|
|
2059
|
+
timedOut = true;
|
|
2060
|
+
controller.abort();
|
|
2061
|
+
}, timeoutMs);
|
|
2062
|
+
const upstreamSignal = init.signal;
|
|
2063
|
+
const abortFromUpstream = () => controller.abort();
|
|
2064
|
+
if (upstreamSignal?.aborted) {
|
|
2065
|
+
clearTimeout(timer);
|
|
2066
|
+
throw this.abortedError();
|
|
2067
|
+
}
|
|
2068
|
+
upstreamSignal?.addEventListener("abort", abortFromUpstream, { once: true });
|
|
2069
|
+
try {
|
|
2070
|
+
return await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
if (timedOut) throw this.timeoutError(timeoutMs);
|
|
2073
|
+
if (upstreamSignal?.aborted) throw this.abortedError();
|
|
2074
|
+
throw error;
|
|
2075
|
+
} finally {
|
|
2076
|
+
clearTimeout(timer);
|
|
2077
|
+
upstreamSignal?.removeEventListener("abort", abortFromUpstream);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
async health() {
|
|
2081
|
+
const response = await this.fetchWithTimeout(`${this.baseUrl}/internal/health`, {
|
|
2082
|
+
headers: makeHeaders({ token: this.token, workspaceId: this.workspaceId, requestId: this.requestId })
|
|
2083
|
+
}, this.requestTimeoutMs);
|
|
2084
|
+
if (!response.ok) throw await parseError(response);
|
|
2085
|
+
}
|
|
2086
|
+
async workspace(op) {
|
|
2087
|
+
const response = await this.fetchWithTimeout(`${this.baseUrl}/internal/workspaces/${encodeWorkspaceId(this.workspaceId)}/fs`, {
|
|
2088
|
+
method: "POST",
|
|
2089
|
+
headers: makeHeaders({ token: this.token, workspaceId: this.workspaceId, requestId: this.requestId, contentType: "application/json" }),
|
|
2090
|
+
body: JSON.stringify(op)
|
|
2091
|
+
}, this.requestTimeoutMs);
|
|
2092
|
+
if (!response.ok) throw await parseError(response);
|
|
2093
|
+
return await response.json();
|
|
2094
|
+
}
|
|
2095
|
+
async exec(input, opts = {}) {
|
|
2096
|
+
const timeoutMs = (input.timeoutMs ?? this.execTimeoutMs) + this.execRequestGraceMs;
|
|
2097
|
+
const response = await this.fetchWithTimeout(`${this.baseUrl}/internal/workspaces/${encodeWorkspaceId(this.workspaceId)}/exec`, {
|
|
2098
|
+
method: "POST",
|
|
2099
|
+
headers: makeHeaders({ token: this.token, workspaceId: this.workspaceId, requestId: this.requestId, contentType: "application/json" }),
|
|
2100
|
+
body: JSON.stringify(input),
|
|
2101
|
+
signal: opts.signal
|
|
2102
|
+
}, timeoutMs);
|
|
2103
|
+
if (!response.ok) throw await parseError(response);
|
|
2104
|
+
const body = await response.json();
|
|
2105
|
+
return {
|
|
2106
|
+
stdout: base64ToBytes(body.stdoutBase64),
|
|
2107
|
+
stderr: base64ToBytes(body.stderrBase64),
|
|
2108
|
+
exitCode: body.exitCode,
|
|
2109
|
+
durationMs: body.durationMs,
|
|
2110
|
+
truncated: body.truncated,
|
|
2111
|
+
stdoutEncoding: body.stdoutEncoding,
|
|
2112
|
+
stderrEncoding: body.stderrEncoding
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
watch(onEvent, onError) {
|
|
2116
|
+
const controller = new AbortController();
|
|
2117
|
+
void this.consumeEvents(controller.signal, onEvent).catch((error) => {
|
|
2118
|
+
if (!controller.signal.aborted) onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
2119
|
+
});
|
|
2120
|
+
return { close: () => controller.abort() };
|
|
2121
|
+
}
|
|
2122
|
+
async consumeEvents(signal, onEvent) {
|
|
2123
|
+
const response = await this.fetchImpl(`${this.baseUrl}/internal/workspaces/${encodeWorkspaceId(this.workspaceId)}/fs/events`, {
|
|
2124
|
+
headers: makeHeaders({ token: this.token, workspaceId: this.workspaceId, requestId: this.requestId }),
|
|
2125
|
+
signal
|
|
2126
|
+
});
|
|
2127
|
+
if (!response.ok) throw await parseError(response);
|
|
2128
|
+
if (!response.body) throw new RemoteWorkerClientError("remote worker event stream missing body", { statusCode: 502 });
|
|
2129
|
+
const reader = response.body.getReader();
|
|
2130
|
+
const decoder2 = new TextDecoder();
|
|
2131
|
+
let buffer = "";
|
|
2132
|
+
try {
|
|
2133
|
+
for (; ; ) {
|
|
2134
|
+
const { done, value } = await reader.read();
|
|
2135
|
+
if (done) {
|
|
2136
|
+
if (signal.aborted) return;
|
|
2137
|
+
throw new RemoteWorkerClientError("remote worker event stream closed", {
|
|
2138
|
+
statusCode: 502,
|
|
2139
|
+
code: ErrorCode.enum.REMOTE_WORKER_STREAM_CLOSED
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
buffer += decoder2.decode(value, { stream: true });
|
|
2143
|
+
let boundary = buffer.indexOf("\n\n");
|
|
2144
|
+
while (boundary >= 0) {
|
|
2145
|
+
const frame = buffer.slice(0, boundary);
|
|
2146
|
+
buffer = buffer.slice(boundary + 2);
|
|
2147
|
+
this.handleSseFrame(frame, onEvent);
|
|
2148
|
+
boundary = buffer.indexOf("\n\n");
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
} finally {
|
|
2152
|
+
reader.releaseLock();
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
handleSseFrame(frame, onEvent) {
|
|
2156
|
+
let eventName = "message";
|
|
2157
|
+
const dataLines = [];
|
|
2158
|
+
for (const line of frame.split("\n")) {
|
|
2159
|
+
if (line.startsWith(":")) continue;
|
|
2160
|
+
if (line.startsWith("event:")) eventName = line.slice("event:".length).trim();
|
|
2161
|
+
if (line.startsWith("data:")) dataLines.push(line.slice("data:".length).trimStart());
|
|
2162
|
+
}
|
|
2163
|
+
if (eventName !== "change" || dataLines.length === 0) return;
|
|
2164
|
+
const payload = JSON.parse(dataLines.join("\n"));
|
|
2165
|
+
onEvent(payload.event);
|
|
2166
|
+
}
|
|
2167
|
+
};
|
|
2168
|
+
function encodeBytesForWorker(bytes) {
|
|
2169
|
+
return bytesToBase64(bytes);
|
|
2170
|
+
}
|
|
2171
|
+
function decodeBytesFromWorker(value) {
|
|
2172
|
+
return base64ToBytes(value);
|
|
2173
|
+
}
|
|
2174
|
+
function constantTimeTokenEqual(a, b) {
|
|
2175
|
+
if (!a || !b) return false;
|
|
2176
|
+
const aBytes = Buffer.from(a);
|
|
2177
|
+
const bBytes = Buffer.from(b);
|
|
2178
|
+
if (aBytes.length !== bBytes.length) return false;
|
|
2179
|
+
return timingSafeEqual(aBytes, bBytes);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// src/server/workspace/createRemoteWorkerWorkspace.ts
|
|
2183
|
+
function expectContent(result) {
|
|
2184
|
+
if ("content" in result && typeof result.content === "string") return result.content;
|
|
2185
|
+
throw new Error("remote worker returned invalid file content response");
|
|
2186
|
+
}
|
|
2187
|
+
function expectData(result) {
|
|
2188
|
+
if ("dataBase64" in result && typeof result.dataBase64 === "string") return decodeBytesFromWorker(result.dataBase64);
|
|
2189
|
+
throw new Error("remote worker returned invalid binary response");
|
|
2190
|
+
}
|
|
2191
|
+
function expectStat(result) {
|
|
2192
|
+
if ("stat" in result && result.stat) return result.stat;
|
|
2193
|
+
throw new Error("remote worker returned invalid stat response");
|
|
2194
|
+
}
|
|
2195
|
+
function expectEntries(result) {
|
|
2196
|
+
if ("entries" in result && Array.isArray(result.entries)) return result.entries;
|
|
2197
|
+
throw new Error("remote worker returned invalid readdir response");
|
|
2198
|
+
}
|
|
2199
|
+
function createRemoteWatcher(client) {
|
|
2200
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
2201
|
+
let stream = null;
|
|
2202
|
+
let reconnectTimer = null;
|
|
2203
|
+
let closed = false;
|
|
2204
|
+
const clearReconnectTimer = () => {
|
|
2205
|
+
if (!reconnectTimer) return;
|
|
2206
|
+
clearTimeout(reconnectTimer);
|
|
2207
|
+
reconnectTimer = null;
|
|
2208
|
+
};
|
|
2209
|
+
const scheduleReconnect = () => {
|
|
2210
|
+
if (closed || listeners.size === 0 || reconnectTimer) return;
|
|
2211
|
+
reconnectTimer = setTimeout(() => {
|
|
2212
|
+
reconnectTimer = null;
|
|
2213
|
+
ensureStream();
|
|
2214
|
+
}, 1e3);
|
|
2215
|
+
};
|
|
2216
|
+
const handleStreamEnd = () => {
|
|
2217
|
+
stream = null;
|
|
2218
|
+
for (const options of [...listeners.values()]) {
|
|
2219
|
+
try {
|
|
2220
|
+
options?.onControlEvent?.({ type: "resync-required", reason: "remote_worker_stream_closed" });
|
|
2221
|
+
} catch {
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
scheduleReconnect();
|
|
2225
|
+
};
|
|
2226
|
+
const ensureStream = () => {
|
|
2227
|
+
if (stream || closed || listeners.size === 0) return;
|
|
2228
|
+
clearReconnectTimer();
|
|
2229
|
+
stream = client.watch((event) => {
|
|
2230
|
+
for (const listener of [...listeners.keys()]) {
|
|
2231
|
+
try {
|
|
2232
|
+
listener(event);
|
|
2233
|
+
} catch {
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}, handleStreamEnd);
|
|
2237
|
+
};
|
|
2238
|
+
return {
|
|
2239
|
+
subscribe(listener, options) {
|
|
2240
|
+
if (closed) return () => {
|
|
2241
|
+
};
|
|
2242
|
+
listeners.set(listener, options);
|
|
2243
|
+
ensureStream();
|
|
2244
|
+
return () => {
|
|
2245
|
+
listeners.delete(listener);
|
|
2246
|
+
if (listeners.size === 0) {
|
|
2247
|
+
clearReconnectTimer();
|
|
2248
|
+
stream?.close();
|
|
2249
|
+
stream = null;
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
},
|
|
2253
|
+
close() {
|
|
2254
|
+
if (closed) return;
|
|
2255
|
+
closed = true;
|
|
2256
|
+
listeners.clear();
|
|
2257
|
+
clearReconnectTimer();
|
|
2258
|
+
stream?.close();
|
|
2259
|
+
stream = null;
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
function createRemoteWorkerWorkspace(client) {
|
|
2264
|
+
let watcher = null;
|
|
2265
|
+
return {
|
|
2266
|
+
root: REMOTE_WORKER_RUNTIME_CWD,
|
|
2267
|
+
runtimeContext: { runtimeCwd: REMOTE_WORKER_RUNTIME_CWD },
|
|
2268
|
+
fsCapability: "best-effort",
|
|
2269
|
+
watch() {
|
|
2270
|
+
watcher ??= createRemoteWatcher(client);
|
|
2271
|
+
return watcher;
|
|
2272
|
+
},
|
|
2273
|
+
async readFile(path4) {
|
|
2274
|
+
return expectContent(await client.workspace({ op: "readFile", path: path4 }));
|
|
2275
|
+
},
|
|
2276
|
+
async readBinaryFile(path4) {
|
|
2277
|
+
return expectData(await client.workspace({ op: "readBinaryFile", path: path4 }));
|
|
2278
|
+
},
|
|
2279
|
+
async writeFile(path4, data) {
|
|
2280
|
+
await client.workspace({ op: "writeFile", path: path4, data });
|
|
2281
|
+
},
|
|
2282
|
+
async writeBinaryFile(path4, data) {
|
|
2283
|
+
await client.workspace({ op: "writeBinaryFile", path: path4, dataBase64: encodeBytesForWorker(data) });
|
|
2284
|
+
},
|
|
2285
|
+
async readFileWithStat(path4) {
|
|
2286
|
+
const result = await client.workspace({ op: "readFileWithStat", path: path4 });
|
|
2287
|
+
if ("content" in result && "stat" in result) return { content: result.content, stat: result.stat };
|
|
2288
|
+
throw new Error("remote worker returned invalid readFileWithStat response");
|
|
2289
|
+
},
|
|
2290
|
+
async writeFileWithStat(path4, data) {
|
|
2291
|
+
return expectStat(await client.workspace({ op: "writeFileWithStat", path: path4, data }));
|
|
2292
|
+
},
|
|
2293
|
+
async writeBinaryFileWithStat(path4, data) {
|
|
2294
|
+
return expectStat(await client.workspace({ op: "writeBinaryFileWithStat", path: path4, dataBase64: encodeBytesForWorker(data) }));
|
|
2295
|
+
},
|
|
2296
|
+
async unlink(path4) {
|
|
2297
|
+
await client.workspace({ op: "unlink", path: path4 });
|
|
2298
|
+
},
|
|
2299
|
+
async readdir(path4) {
|
|
2300
|
+
return expectEntries(await client.workspace({ op: "readdir", path: path4 }));
|
|
2301
|
+
},
|
|
2302
|
+
async stat(path4) {
|
|
2303
|
+
return expectStat(await client.workspace({ op: "stat", path: path4 }));
|
|
2304
|
+
},
|
|
2305
|
+
async mkdir(path4, opts) {
|
|
2306
|
+
await client.workspace({ op: "mkdir", path: path4, recursive: opts?.recursive });
|
|
2307
|
+
},
|
|
2308
|
+
async rename(from, to) {
|
|
2309
|
+
await client.workspace({ op: "rename", from, to });
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// src/server/runtime/modes/remote-worker.ts
|
|
2315
|
+
function requireOption(value, name) {
|
|
2316
|
+
const trimmed = value?.trim();
|
|
2317
|
+
if (!trimmed) throw new Error(`${name} is required for remote-worker mode`);
|
|
2318
|
+
return trimmed;
|
|
2319
|
+
}
|
|
2320
|
+
function createRemoteWorkerModeAdapter(opts = {}) {
|
|
2321
|
+
return {
|
|
2322
|
+
id: REMOTE_WORKER_PROVIDER,
|
|
2323
|
+
workspaceFsCapability: "best-effort",
|
|
2324
|
+
async create(ctx) {
|
|
2325
|
+
const workspaceId = requireOption(ctx.workspaceId ?? ctx.sessionId, "workspaceId");
|
|
2326
|
+
const client = new RemoteWorkerClient({
|
|
2327
|
+
baseUrl: requireOption(opts.baseUrl ?? getEnv("BORING_WORKER_BASE_URL"), "BORING_WORKER_BASE_URL"),
|
|
2328
|
+
token: requireOption(opts.token ?? getEnv("BORING_WORKER_INTERNAL_TOKEN"), "BORING_WORKER_INTERNAL_TOKEN"),
|
|
2329
|
+
workspaceId,
|
|
2330
|
+
requestId: ctx.requestId,
|
|
2331
|
+
fetchImpl: opts.fetchImpl
|
|
2332
|
+
});
|
|
2333
|
+
const workspace = createRemoteWorkerWorkspace(client);
|
|
2334
|
+
const sandbox = createRemoteWorkerSandbox(client);
|
|
2335
|
+
await sandbox.init?.({ workspace, sessionId: ctx.sessionId });
|
|
2336
|
+
return {
|
|
2337
|
+
runtimeContext: { runtimeCwd: REMOTE_WORKER_RUNTIME_CWD },
|
|
2338
|
+
workspace,
|
|
2339
|
+
sandbox,
|
|
2340
|
+
fileSearch: createServerFileSearch(workspace, sandbox)
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
|
|
1637
2346
|
// src/server/http/routes/file.ts
|
|
1638
2347
|
import { dirname as dirname5, extname as extname2, relative as relative4 } from "path/posix";
|
|
1639
2348
|
|
|
@@ -1682,102 +2391,22 @@ function createAuthMiddleware(opts = {}) {
|
|
|
1682
2391
|
}
|
|
1683
2392
|
return;
|
|
1684
2393
|
}
|
|
1685
|
-
const header = request.headers.authorization;
|
|
1686
|
-
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
1687
|
-
reply.code(401).send(
|
|
1688
|
-
errorPayload(
|
|
1689
|
-
ERROR_CODE_AUTH_REQUIRED,
|
|
1690
|
-
"Missing Bearer token"
|
|
1691
|
-
)
|
|
1692
|
-
);
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
const candidateToken = header.slice("Bearer ".length);
|
|
1696
|
-
if (candidateToken !== authToken) {
|
|
1697
|
-
reply.code(403).send(errorPayload(ERROR_CODE_AUTH_INVALID, "Invalid token"));
|
|
1698
|
-
return;
|
|
1699
|
-
}
|
|
1700
|
-
ensureWorkspaceContext(request, workspaceId, true);
|
|
1701
|
-
};
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
// src/server/logging.ts
|
|
1705
|
-
var SENSITIVE_KEYS = new Set([
|
|
1706
|
-
"apiKey",
|
|
1707
|
-
"api_key",
|
|
1708
|
-
"token",
|
|
1709
|
-
"secret",
|
|
1710
|
-
"password",
|
|
1711
|
-
"authorization",
|
|
1712
|
-
"cookie",
|
|
1713
|
-
"oidcToken",
|
|
1714
|
-
"accessToken",
|
|
1715
|
-
"refreshToken",
|
|
1716
|
-
"ANTHROPIC_API_KEY",
|
|
1717
|
-
"OPENAI_API_KEY",
|
|
1718
|
-
"VERCEL_OIDC_TOKEN",
|
|
1719
|
-
"VERCEL_TEAM_ID"
|
|
1720
|
-
].map((key) => key.toLowerCase()));
|
|
1721
|
-
function isSensitiveKey(key) {
|
|
1722
|
-
return SENSITIVE_KEYS.has(key.toLowerCase());
|
|
1723
|
-
}
|
|
1724
|
-
function redactValue(key, value, seen) {
|
|
1725
|
-
if (key && isSensitiveKey(key) && value != null) return "***";
|
|
1726
|
-
if (value == null || typeof value !== "object") return value;
|
|
1727
|
-
if (value instanceof Date) return value;
|
|
1728
|
-
if (seen.has(value)) return "[Circular]";
|
|
1729
|
-
seen.add(value);
|
|
1730
|
-
if (Array.isArray(value)) {
|
|
1731
|
-
const out2 = value.map((item) => redactValue(void 0, item, seen));
|
|
1732
|
-
seen.delete(value);
|
|
1733
|
-
return out2;
|
|
1734
|
-
}
|
|
1735
|
-
const out = {};
|
|
1736
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
1737
|
-
out[childKey] = redactValue(childKey, childValue, seen);
|
|
1738
|
-
}
|
|
1739
|
-
seen.delete(value);
|
|
1740
|
-
return out;
|
|
1741
|
-
}
|
|
1742
|
-
function redact(fields) {
|
|
1743
|
-
const out = {};
|
|
1744
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
1745
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
1746
|
-
out[k] = redactValue(k, v, seen);
|
|
1747
|
-
}
|
|
1748
|
-
return out;
|
|
1749
|
-
}
|
|
1750
|
-
var verbose = typeof process !== "undefined" && process.env?.BORING_AGENT_VERBOSE === "1";
|
|
1751
|
-
function createLogger(prefix) {
|
|
1752
|
-
function emit(level, msg, fields) {
|
|
1753
|
-
const entry = {
|
|
1754
|
-
level,
|
|
1755
|
-
prefix,
|
|
1756
|
-
msg,
|
|
1757
|
-
...fields ? redact(fields) : {},
|
|
1758
|
-
t: (/* @__PURE__ */ new Date()).toISOString()
|
|
1759
|
-
};
|
|
1760
|
-
if (level === "error") {
|
|
1761
|
-
console.error(JSON.stringify(entry));
|
|
1762
|
-
} else if (level === "warn") {
|
|
1763
|
-
console.warn(JSON.stringify(entry));
|
|
1764
|
-
} else {
|
|
1765
|
-
console.log(JSON.stringify(entry));
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
return {
|
|
1769
|
-
debug(msg, fields) {
|
|
1770
|
-
if (verbose) emit("debug", msg, fields);
|
|
1771
|
-
},
|
|
1772
|
-
info(msg, fields) {
|
|
1773
|
-
emit("info", msg, fields);
|
|
1774
|
-
},
|
|
1775
|
-
warn(msg, fields) {
|
|
1776
|
-
emit("warn", msg, fields);
|
|
1777
|
-
},
|
|
1778
|
-
error(msg, fields) {
|
|
1779
|
-
emit("error", msg, fields);
|
|
2394
|
+
const header = request.headers.authorization;
|
|
2395
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
2396
|
+
reply.code(401).send(
|
|
2397
|
+
errorPayload(
|
|
2398
|
+
ERROR_CODE_AUTH_REQUIRED,
|
|
2399
|
+
"Missing Bearer token"
|
|
2400
|
+
)
|
|
2401
|
+
);
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
const candidateToken = header.slice("Bearer ".length);
|
|
2405
|
+
if (candidateToken !== authToken) {
|
|
2406
|
+
reply.code(403).send(errorPayload(ERROR_CODE_AUTH_INVALID, "Invalid token"));
|
|
2407
|
+
return;
|
|
1780
2408
|
}
|
|
2409
|
+
ensureWorkspaceContext(request, workspaceId, true);
|
|
1781
2410
|
};
|
|
1782
2411
|
}
|
|
1783
2412
|
|
|
@@ -1987,7 +2616,7 @@ function buildFileRecordsResult(args) {
|
|
|
1987
2616
|
}
|
|
1988
2617
|
|
|
1989
2618
|
// src/server/http/routes/file.ts
|
|
1990
|
-
var
|
|
2619
|
+
var log2 = createLogger("boring/workspace-settings");
|
|
1991
2620
|
var BORING_SETTINGS_PATH = ".boring/settings";
|
|
1992
2621
|
var DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR = "assets/images";
|
|
1993
2622
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
@@ -2062,7 +2691,7 @@ function parseWorkspaceSettings(raw) {
|
|
|
2062
2691
|
}
|
|
2063
2692
|
};
|
|
2064
2693
|
} catch (err) {
|
|
2065
|
-
|
|
2694
|
+
log2.warn("failed to parse .boring/settings \u2014 falling back to defaults", {
|
|
2066
2695
|
error: err instanceof Error ? err.message : String(err)
|
|
2067
2696
|
});
|
|
2068
2697
|
return defaultWorkspaceSettings();
|
|
@@ -2410,8 +3039,8 @@ function fileRoutes(app, opts, done) {
|
|
|
2410
3039
|
// src/server/workspace/provisionRuntime.ts
|
|
2411
3040
|
import { createHash as createHash3 } from "crypto";
|
|
2412
3041
|
import { constants as constants2 } from "fs";
|
|
2413
|
-
import { access as access2, chmod as chmod3, cp, mkdir as mkdir4, readdir as
|
|
2414
|
-
import { dirname as dirname6, isAbsolute as isAbsolute4, join as
|
|
3042
|
+
import { access as access2, chmod as chmod3, cp, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, realpath as realpath2, stat as stat4, writeFile as writeFile4 } from "fs/promises";
|
|
3043
|
+
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join4, relative as relative5, resolve as resolve5 } from "path";
|
|
2415
3044
|
import { fileURLToPath } from "url";
|
|
2416
3045
|
import { execFile } from "child_process";
|
|
2417
3046
|
import { promisify } from "util";
|
|
@@ -2447,11 +3076,11 @@ async function commandExists(cmd) {
|
|
|
2447
3076
|
async function hashPath(path4, hash) {
|
|
2448
3077
|
const info = await stat4(path4);
|
|
2449
3078
|
if (info.isDirectory()) {
|
|
2450
|
-
const entries = (await
|
|
3079
|
+
const entries = (await readdir3(path4)).filter((entry) => entry !== "__pycache__" && entry !== "build" && !entry.endsWith(".egg-info")).sort();
|
|
2451
3080
|
for (const entry of entries) {
|
|
2452
3081
|
hash.update(`dir:${entry}
|
|
2453
3082
|
`);
|
|
2454
|
-
await hashPath(
|
|
3083
|
+
await hashPath(join4(path4, entry), hash);
|
|
2455
3084
|
}
|
|
2456
3085
|
return;
|
|
2457
3086
|
}
|
|
@@ -2484,16 +3113,16 @@ async function fingerprint(contributions) {
|
|
|
2484
3113
|
const packageRoot = toPath(spec.packageRoot);
|
|
2485
3114
|
hash.update(`node-package:${spec.id}:${spec.packageName}:${packageRoot}
|
|
2486
3115
|
`);
|
|
2487
|
-
const packageJson =
|
|
2488
|
-
const distDir =
|
|
2489
|
-
const docsDir =
|
|
2490
|
-
const skillsDir =
|
|
2491
|
-
const referencesDir =
|
|
2492
|
-
const sourceDocsDir =
|
|
3116
|
+
const packageJson = join4(packageRoot, "package.json");
|
|
3117
|
+
const distDir = join4(packageRoot, "dist");
|
|
3118
|
+
const docsDir = join4(packageRoot, "docs");
|
|
3119
|
+
const skillsDir = join4(packageRoot, "skills");
|
|
3120
|
+
const referencesDir = join4(packageRoot, "references");
|
|
3121
|
+
const sourceDocsDir = join4(packageRoot, "src", "server", "docs");
|
|
2493
3122
|
if (await exists(packageJson)) await hashPath(packageJson, hash);
|
|
2494
3123
|
if (await exists(distDir)) await hashPath(distDir, hash);
|
|
2495
|
-
const templatesDir =
|
|
2496
|
-
const publicDir =
|
|
3124
|
+
const templatesDir = join4(packageRoot, "templates");
|
|
3125
|
+
const publicDir = join4(packageRoot, "public");
|
|
2497
3126
|
if (await exists(templatesDir)) await hashPath(templatesDir, hash);
|
|
2498
3127
|
if (await exists(publicDir)) await hashPath(publicDir, hash);
|
|
2499
3128
|
if (await exists(docsDir)) await hashPath(docsDir, hash);
|
|
@@ -2544,7 +3173,7 @@ function nodePackageTarget(workspaceRoot, packageName) {
|
|
|
2544
3173
|
if (parts.length === 0 || parts.some((part) => part === "." || part === "..")) {
|
|
2545
3174
|
throw new Error(`Invalid node package name: ${packageName}`);
|
|
2546
3175
|
}
|
|
2547
|
-
return
|
|
3176
|
+
return join4(workspaceRoot, "node_modules", ...parts);
|
|
2548
3177
|
}
|
|
2549
3178
|
async function copyIfExists(source, target) {
|
|
2550
3179
|
if (!await exists(source)) return false;
|
|
@@ -2562,27 +3191,27 @@ async function ensureNodePackages(workspaceRoot, specs) {
|
|
|
2562
3191
|
const packageRoot = toPath(spec.packageRoot);
|
|
2563
3192
|
const target = nodePackageTarget(workspaceRoot, spec.packageName);
|
|
2564
3193
|
await mkdir4(target, { recursive: true });
|
|
2565
|
-
const copiedPackageJson = await copyIfExists(
|
|
3194
|
+
const copiedPackageJson = await copyIfExists(join4(packageRoot, "package.json"), join4(target, "package.json"));
|
|
2566
3195
|
if (!copiedPackageJson) {
|
|
2567
3196
|
throw new Error(`Node package provisioning source is missing package.json: ${packageRoot}`);
|
|
2568
3197
|
}
|
|
2569
|
-
await copyIfExists(
|
|
2570
|
-
await copyIfExists(
|
|
2571
|
-
await copyIfExists(
|
|
2572
|
-
if (!await exists(
|
|
2573
|
-
await copyIfExists(
|
|
3198
|
+
await copyIfExists(join4(packageRoot, "dist"), join4(target, "dist"));
|
|
3199
|
+
await copyIfExists(join4(packageRoot, "templates"), join4(target, "templates"));
|
|
3200
|
+
await copyIfExists(join4(packageRoot, "public"), join4(target, "public"));
|
|
3201
|
+
if (!await exists(join4(target, "dist", "docs", "plugins.md"))) {
|
|
3202
|
+
await copyIfExists(join4(packageRoot, "src", "server", "docs"), join4(target, "dist", "docs"));
|
|
2574
3203
|
}
|
|
2575
|
-
if (!await copyIfExists(
|
|
2576
|
-
await copyIfExists(
|
|
3204
|
+
if (!await copyIfExists(join4(packageRoot, "docs"), join4(target, "docs"))) {
|
|
3205
|
+
await copyIfExists(join4(packageRoot, "src", "server", "docs"), join4(target, "docs"));
|
|
2577
3206
|
}
|
|
2578
|
-
await copyIfExists(
|
|
2579
|
-
await copyIfExists(
|
|
2580
|
-
await copyIfExists(
|
|
3207
|
+
await copyIfExists(join4(packageRoot, "skills"), join4(target, "skills"));
|
|
3208
|
+
await copyIfExists(join4(packageRoot, "references"), join4(target, "references"));
|
|
3209
|
+
await copyIfExists(join4(packageRoot, "src", "globals.css"), join4(target, "src", "globals.css"));
|
|
2581
3210
|
}
|
|
2582
3211
|
}
|
|
2583
3212
|
async function ensurePython(workspaceRoot, specs) {
|
|
2584
3213
|
if (specs.length === 0) return;
|
|
2585
|
-
const venvPython =
|
|
3214
|
+
const venvPython = join4(workspaceRoot, ".boring-agent", "venv", "bin", "python");
|
|
2586
3215
|
const uv = await commandExists("uv");
|
|
2587
3216
|
if (!await exists(venvPython)) {
|
|
2588
3217
|
if (uv) await run("uv", ["venv", ".boring-agent/venv"], workspaceRoot);
|
|
@@ -2615,17 +3244,17 @@ function assertEnvKey(key) {
|
|
|
2615
3244
|
}
|
|
2616
3245
|
async function isRuntimeMaterialized(workspaceRoot, contributions) {
|
|
2617
3246
|
const hasPython = contributions.some(({ provisioning }) => (provisioning.python ?? []).length > 0);
|
|
2618
|
-
if (hasPython && !await exists(
|
|
3247
|
+
if (hasPython && !await exists(join4(workspaceRoot, ".boring-agent", "venv", "bin", "python"))) return false;
|
|
2619
3248
|
for (const { provisioning } of contributions) {
|
|
2620
3249
|
for (const spec of provisioning.nodePackages ?? []) {
|
|
2621
|
-
if (!await exists(
|
|
3250
|
+
if (!await exists(join4(nodePackageTarget(workspaceRoot, spec.packageName), "package.json"))) return false;
|
|
2622
3251
|
}
|
|
2623
3252
|
}
|
|
2624
3253
|
return true;
|
|
2625
3254
|
}
|
|
2626
3255
|
async function writeShims(workspaceRoot, env) {
|
|
2627
|
-
const shimDir =
|
|
2628
|
-
const venvBin =
|
|
3256
|
+
const shimDir = join4(workspaceRoot, ".boring-agent", "bin");
|
|
3257
|
+
const venvBin = join4(workspaceRoot, ".boring-agent", "venv", "bin");
|
|
2629
3258
|
await mkdir4(shimDir, { recursive: true });
|
|
2630
3259
|
const exports = Object.entries(env).map(([key, value]) => {
|
|
2631
3260
|
assertEnvKey(key);
|
|
@@ -2639,21 +3268,21 @@ export BORING_AGENT_WORKSPACE_ROOT="$WORKSPACE_ROOT"
|
|
|
2639
3268
|
${exports}
|
|
2640
3269
|
VENV_BIN="$WORKSPACE_ROOT/.boring-agent/venv/bin"
|
|
2641
3270
|
`;
|
|
2642
|
-
await writeExecutable(
|
|
3271
|
+
await writeExecutable(join4(shimDir, "python"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2643
3272
|
`);
|
|
2644
|
-
await writeExecutable(
|
|
3273
|
+
await writeExecutable(join4(shimDir, "python3"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2645
3274
|
`);
|
|
2646
|
-
await writeExecutable(
|
|
3275
|
+
await writeExecutable(join4(shimDir, "pip"), `${base}exec "$VENV_BIN/python" -m pip "$@"
|
|
2647
3276
|
`);
|
|
2648
|
-
await writeExecutable(
|
|
3277
|
+
await writeExecutable(join4(shimDir, "pip3"), `${base}exec "$VENV_BIN/python" -m pip "$@"
|
|
2649
3278
|
`);
|
|
2650
3279
|
if (await exists(venvBin)) {
|
|
2651
|
-
for (const entry of await
|
|
3280
|
+
for (const entry of await readdir3(venvBin)) {
|
|
2652
3281
|
if (["python", "python3", "pip", "pip3"].includes(entry)) continue;
|
|
2653
|
-
const full =
|
|
3282
|
+
const full = join4(venvBin, entry);
|
|
2654
3283
|
const info = await stat4(full).catch(() => null);
|
|
2655
3284
|
if (!info?.isFile()) continue;
|
|
2656
|
-
await writeExecutable(
|
|
3285
|
+
await writeExecutable(join4(shimDir, entry), `${base}TARGET="$VENV_BIN"/${bashSingleQuote(entry)}
|
|
2657
3286
|
SHEBANG="$(head -n 1 "$TARGET" 2>/dev/null || true)"
|
|
2658
3287
|
case "$SHEBANG" in
|
|
2659
3288
|
*python*) exec "$VENV_BIN/python" "$TARGET" "$@" ;;
|
|
@@ -2676,8 +3305,8 @@ async function provisionRuntimeWorkspace({
|
|
|
2676
3305
|
await mkdir4(workspaceRoot, { recursive: true });
|
|
2677
3306
|
const env = collectEnv(active);
|
|
2678
3307
|
const hash = await fingerprint(active);
|
|
2679
|
-
const markerPath =
|
|
2680
|
-
const binDir =
|
|
3308
|
+
const markerPath = join4(workspaceRoot, ".boring-agent", "provisioning.json");
|
|
3309
|
+
const binDir = join4(workspaceRoot, ".boring-agent", "bin");
|
|
2681
3310
|
if (!force && await exists(markerPath)) {
|
|
2682
3311
|
try {
|
|
2683
3312
|
const marker = JSON.parse(await readFile4(markerPath, "utf8"));
|
|
@@ -2748,7 +3377,7 @@ function cloneStat(stat11) {
|
|
|
2748
3377
|
function cloneEntries(entries) {
|
|
2749
3378
|
return entries.map((entry) => ({ name: entry.name, kind: entry.kind }));
|
|
2750
3379
|
}
|
|
2751
|
-
function
|
|
3380
|
+
function shellQuote3(value) {
|
|
2752
3381
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
2753
3382
|
}
|
|
2754
3383
|
async function runJson(sandbox, script) {
|
|
@@ -2828,7 +3457,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2828
3457
|
async function assertRealPathWithinSandboxRoot(sandboxPath) {
|
|
2829
3458
|
const isWithinRoot = await runJson(
|
|
2830
3459
|
remote,
|
|
2831
|
-
`node -e ${
|
|
3460
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const path=require('path'); const root=fs.realpathSync(process.argv[1]); const target=fs.realpathSync(process.argv[2]); const rel=path.relative(root,target); process.stdout.write(JSON.stringify(rel===''||(!rel.startsWith('..')&&!path.isAbsolute(rel))))`)} ${shellQuote3(VERCEL_SANDBOX_REMOTE_ROOT)} ${shellQuote3(sandboxPath)}`
|
|
2832
3461
|
);
|
|
2833
3462
|
if (!isWithinRoot) {
|
|
2834
3463
|
throw Object.assign(new Error("resolved path escapes workspace root"), { code: EPERM_CODE2 });
|
|
@@ -2837,7 +3466,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2837
3466
|
async function isSandboxSymlink(sandboxPath) {
|
|
2838
3467
|
return await runJson(
|
|
2839
3468
|
remote,
|
|
2840
|
-
`node -e ${
|
|
3469
|
+
`node -e ${shellQuote3(`const fs=require('fs'); process.stdout.write(JSON.stringify(fs.lstatSync(process.argv[1]).isSymbolicLink()))`)} ${shellQuote3(sandboxPath)}`
|
|
2841
3470
|
);
|
|
2842
3471
|
}
|
|
2843
3472
|
async function listDescendantPaths(relPath, sandboxPath) {
|
|
@@ -2857,7 +3486,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2857
3486
|
}
|
|
2858
3487
|
return await runJson(
|
|
2859
3488
|
remote,
|
|
2860
|
-
`node -e ${
|
|
3489
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const path=require('path'); const root=process.argv[1]; const relRoot=process.argv[2]; function walk(abs,rel){ const s=fs.statSync(abs); if(!s.isDirectory()) return []; const out=[]; for (const entry of fs.readdirSync(abs,{withFileTypes:true})) { const childRel=rel==='.'?entry.name:rel+'/'+entry.name; out.push(childRel); if(entry.isDirectory()) out.push(...walk(path.join(abs,entry.name),childRel)); } return out; } process.stdout.write(JSON.stringify(walk(root,relRoot)))`)} ${shellQuote3(sandboxPath)} ${shellQuote3(relPath)}`
|
|
2861
3490
|
);
|
|
2862
3491
|
}
|
|
2863
3492
|
return {
|
|
@@ -2950,7 +3579,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2950
3579
|
const version = metadataVersion;
|
|
2951
3580
|
const result = await runJson(
|
|
2952
3581
|
remote,
|
|
2953
|
-
`node -e ${
|
|
3582
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const p=process.argv[1]; const s=fs.statSync(p); const content=fs.readFileSync(p,'utf8'); process.stdout.write(JSON.stringify({content,stat:{size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}}))`)} ${shellQuote3(sandboxPath)}`
|
|
2954
3583
|
);
|
|
2955
3584
|
if (metadataVersion === version) {
|
|
2956
3585
|
statCache.set(sandboxPath, result.stat);
|
|
@@ -2978,7 +3607,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2978
3607
|
};
|
|
2979
3608
|
})() : await runJson(
|
|
2980
3609
|
remote,
|
|
2981
|
-
`node -e ${
|
|
3610
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const p=process.argv[1]; const s=fs.statSync(p); process.stdout.write(JSON.stringify({size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}))`)} ${shellQuote3(sandboxPath)}`
|
|
2982
3611
|
);
|
|
2983
3612
|
statCache.set(sandboxPath, writtenStat2);
|
|
2984
3613
|
emitChange({ op: "write", path: relPath, mtimeMs: writtenStat2.mtimeMs });
|
|
@@ -2987,7 +3616,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2987
3616
|
const encoded = payload.toString("base64");
|
|
2988
3617
|
const writtenStat = await runJson(
|
|
2989
3618
|
remote,
|
|
2990
|
-
`node -e ${
|
|
3619
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const p=process.argv[1]; const data=Buffer.from(process.argv[2],'base64'); fs.writeFileSync(p,data); const s=fs.statSync(p); process.stdout.write(JSON.stringify({size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}))`)} ${shellQuote3(sandboxPath)} ${shellQuote3(encoded)}`
|
|
2991
3620
|
);
|
|
2992
3621
|
invalidateMetadataCache();
|
|
2993
3622
|
statCache.set(sandboxPath, writtenStat);
|
|
@@ -3016,7 +3645,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3016
3645
|
};
|
|
3017
3646
|
})() : await runJson(
|
|
3018
3647
|
remote,
|
|
3019
|
-
`node -e ${
|
|
3648
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const p=process.argv[1]; const s=fs.statSync(p); process.stdout.write(JSON.stringify({size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}))`)} ${shellQuote3(sandboxPath)}`
|
|
3020
3649
|
);
|
|
3021
3650
|
statCache.set(sandboxPath, writtenStat2);
|
|
3022
3651
|
emitChange({ op: "write", path: relPath, mtimeMs: writtenStat2.mtimeMs });
|
|
@@ -3025,7 +3654,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3025
3654
|
const encoded = payload.toString("base64");
|
|
3026
3655
|
const writtenStat = await runJson(
|
|
3027
3656
|
remote,
|
|
3028
|
-
`node -e ${
|
|
3657
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const p=process.argv[1]; const data=Buffer.from(process.argv[2],'base64'); fs.writeFileSync(p,data); const s=fs.statSync(p); process.stdout.write(JSON.stringify({size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}))`)} ${shellQuote3(sandboxPath)} ${shellQuote3(encoded)}`
|
|
3029
3658
|
);
|
|
3030
3659
|
invalidateMetadataCache();
|
|
3031
3660
|
statCache.set(sandboxPath, writtenStat);
|
|
@@ -3041,7 +3670,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3041
3670
|
await assertRealPathWithinSandboxRoot(sandboxPath);
|
|
3042
3671
|
const descendantPaths = await isSandboxSymlink(sandboxPath) ? [] : await listDescendantPaths(relPath, sandboxPath);
|
|
3043
3672
|
if (remote.fs?.rm) await remote.fs.rm(sandboxPath, { recursive: true, force: false });
|
|
3044
|
-
else await runShell(remote, `rm -r -- ${
|
|
3673
|
+
else await runShell(remote, `rm -r -- ${shellQuote3(sandboxPath)}`);
|
|
3045
3674
|
invalidateMetadataCache();
|
|
3046
3675
|
workspaceOpts.onMutation?.();
|
|
3047
3676
|
emitChange({ op: "unlink", path: relPath });
|
|
@@ -3059,7 +3688,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3059
3688
|
kind: entry.isDirectory() ? "dir" : "file"
|
|
3060
3689
|
})) : await runJson(
|
|
3061
3690
|
remote,
|
|
3062
|
-
`node -e ${
|
|
3691
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const p=process.argv[1]; const entries=fs.readdirSync(p,{withFileTypes:true}).map((e)=>({name:e.name,kind:e.isDirectory()?'dir':'file'})); process.stdout.write(JSON.stringify(entries))`)} ${shellQuote3(sandboxPath)}`
|
|
3063
3692
|
);
|
|
3064
3693
|
if (metadataVersion === version) {
|
|
3065
3694
|
readdirCache.set(sandboxPath, mappedEntries);
|
|
@@ -3082,7 +3711,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3082
3711
|
} else {
|
|
3083
3712
|
mappedStat = await runJson(
|
|
3084
3713
|
remote,
|
|
3085
|
-
`node -e ${
|
|
3714
|
+
`node -e ${shellQuote3(`const fs=require('fs'); const p=process.argv[1]; const s=fs.statSync(p); process.stdout.write(JSON.stringify({size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}))`)} ${shellQuote3(sandboxPath)}`
|
|
3086
3715
|
);
|
|
3087
3716
|
}
|
|
3088
3717
|
if (metadataVersion === version) {
|
|
@@ -3093,9 +3722,9 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3093
3722
|
async mkdir(relPath, opts) {
|
|
3094
3723
|
const sandboxPath = toSandboxPath(relPath);
|
|
3095
3724
|
if (remote.fs?.mkdir) await remote.fs.mkdir(sandboxPath, { recursive: opts?.recursive ?? false });
|
|
3096
|
-
else if (opts?.recursive) await runShell(remote, `mkdir -p -- ${
|
|
3725
|
+
else if (opts?.recursive) await runShell(remote, `mkdir -p -- ${shellQuote3(sandboxPath)}`);
|
|
3097
3726
|
else if (remote.mkDir) await remote.mkDir(sandboxPath);
|
|
3098
|
-
else await runShell(remote, `mkdir -- ${
|
|
3727
|
+
else await runShell(remote, `mkdir -- ${shellQuote3(sandboxPath)}`);
|
|
3099
3728
|
invalidateMetadataCache();
|
|
3100
3729
|
workspaceOpts.onMutation?.();
|
|
3101
3730
|
emitChange({ op: "mkdir", path: relPath });
|
|
@@ -3104,7 +3733,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3104
3733
|
const fromSandboxPath = toSandboxPath(fromRelPath);
|
|
3105
3734
|
const toSandboxAbsolutePath = toSandboxPath(toRelPath2);
|
|
3106
3735
|
if (remote.fs?.rename) await remote.fs.rename(fromSandboxPath, toSandboxAbsolutePath);
|
|
3107
|
-
else await runShell(remote, `mv -- ${
|
|
3736
|
+
else await runShell(remote, `mv -- ${shellQuote3(fromSandboxPath)} ${shellQuote3(toSandboxAbsolutePath)}`);
|
|
3108
3737
|
invalidateMetadataCache();
|
|
3109
3738
|
workspaceOpts.onMutation?.();
|
|
3110
3739
|
emitChange({ op: "rename", path: toRelPath2, oldPath: fromRelPath });
|
|
@@ -3115,7 +3744,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3115
3744
|
// src/server/workspace/provisioning/packArtifact.ts
|
|
3116
3745
|
import { execFile as execFile2 } from "child_process";
|
|
3117
3746
|
import { mkdir as mkdir5, mkdtemp, rename as rename4 } from "fs/promises";
|
|
3118
|
-
import { dirname as dirname7, isAbsolute as isAbsolute5, join as
|
|
3747
|
+
import { dirname as dirname7, isAbsolute as isAbsolute5, join as join5 } from "path";
|
|
3119
3748
|
import { tmpdir } from "os";
|
|
3120
3749
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3121
3750
|
import { promisify as promisify2 } from "util";
|
|
@@ -3160,7 +3789,7 @@ async function packProvisioningArtifact(request) {
|
|
|
3160
3789
|
], { maxBuffer: 1024 * 1024 * 20 });
|
|
3161
3790
|
const packedName = stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
3162
3791
|
if (!packedName) throw new Error(`pnpm pack produced no artifact for ${sourcePath}`);
|
|
3163
|
-
const packedPath = isAbsolute5(packedName) ? packedName :
|
|
3792
|
+
const packedPath = isAbsolute5(packedName) ? packedName : join5(dirname7(request.outputPath), packedName);
|
|
3164
3793
|
await rename4(packedPath, request.outputPath);
|
|
3165
3794
|
return;
|
|
3166
3795
|
}
|
|
@@ -3182,8 +3811,8 @@ async function resolveArtifactInstallSource(args) {
|
|
|
3182
3811
|
const name = provisioningArtifactName(kind, id, fingerprint2);
|
|
3183
3812
|
const workspaceRel = `.boring-agent/tmp/${name}`;
|
|
3184
3813
|
if (!await args.workspaceFs.exists(workspaceRel)) {
|
|
3185
|
-
const artifactDir = await mkdtemp(
|
|
3186
|
-
const outputPath =
|
|
3814
|
+
const artifactDir = await mkdtemp(join5(tmpdir(), "boring-agent-artifact-"));
|
|
3815
|
+
const outputPath = join5(artifactDir, name);
|
|
3187
3816
|
try {
|
|
3188
3817
|
await args.prepareArtifact({ kind, id, fingerprint: fingerprint2, source: args.source, outputPath });
|
|
3189
3818
|
await args.workspaceFs.copyFromHost(outputPath, workspaceRel);
|
|
@@ -3290,7 +3919,7 @@ function createPythonRuntimeFingerprint(input) {
|
|
|
3290
3919
|
}
|
|
3291
3920
|
|
|
3292
3921
|
// src/server/workspace/provisioning/node.ts
|
|
3293
|
-
import { join as
|
|
3922
|
+
import { join as join6, relative as relative6, sep as sep3 } from "path";
|
|
3294
3923
|
var NODE_RUNTIME_REL = ".boring-agent/node";
|
|
3295
3924
|
var NODE_PACKAGE_JSON_REL = `${NODE_RUNTIME_REL}/package.json`;
|
|
3296
3925
|
var NODE_FINGERPRINT_REL = `${NODE_RUNTIME_REL}/.fingerprint`;
|
|
@@ -3328,9 +3957,9 @@ function nodeInstallSource(spec) {
|
|
|
3328
3957
|
}
|
|
3329
3958
|
function expectedNodeOutputs(paths, packages) {
|
|
3330
3959
|
return [
|
|
3331
|
-
|
|
3960
|
+
join6(paths.node, "package-lock.json"),
|
|
3332
3961
|
...packages.flatMap(
|
|
3333
|
-
(pkg) => (pkg.expectedBins ?? []).map((bin) =>
|
|
3962
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join6(paths.nodeBin, bin))
|
|
3334
3963
|
)
|
|
3335
3964
|
];
|
|
3336
3965
|
}
|
|
@@ -3421,7 +4050,7 @@ async function ensureNodeRuntime(options) {
|
|
|
3421
4050
|
}
|
|
3422
4051
|
|
|
3423
4052
|
// src/server/workspace/provisioning/python.ts
|
|
3424
|
-
import { dirname as dirname9, join as
|
|
4053
|
+
import { dirname as dirname9, join as join7, relative as relative7, sep as sep4 } from "path";
|
|
3425
4054
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3426
4055
|
var VENV_REL = ".boring-agent/venv";
|
|
3427
4056
|
var VENV_FINGERPRINT_REL = `${VENV_REL}/.fingerprint`;
|
|
@@ -3440,7 +4069,7 @@ async function ensureUv(options) {
|
|
|
3440
4069
|
const uvVersion = await commandOutput2(options.adapter, explicitUvBin, ["--version"]) || "uv unknown";
|
|
3441
4070
|
try {
|
|
3442
4071
|
await options.adapter.exec("mkdir", ["-p", options.runtimeLayout.uvBin], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3443
|
-
await options.adapter.exec("ln", ["-sf", explicitUvBin,
|
|
4072
|
+
await options.adapter.exec("ln", ["-sf", explicitUvBin, join7(options.runtimeLayout.uvBin, "uv")], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3444
4073
|
} catch {
|
|
3445
4074
|
}
|
|
3446
4075
|
return { uvBin: explicitUvBin, uvVersion, installedWorkspaceUv: false };
|
|
@@ -3467,7 +4096,7 @@ async function ensureUv(options) {
|
|
|
3467
4096
|
await options.adapter.workspaceFs.mkdir(".boring-agent/sdk/uv/bin");
|
|
3468
4097
|
await options.adapter.workspaceFs.rm(UV_BIN_REL);
|
|
3469
4098
|
await options.adapter.workspaceFs.copyFromHost(options.uvStandaloneSource, UV_BIN_REL);
|
|
3470
|
-
const uvBin =
|
|
4099
|
+
const uvBin = join7(options.runtimeLayout.uvBin, "uv");
|
|
3471
4100
|
await options.adapter.exec("chmod", ["+x", uvBin], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3472
4101
|
return {
|
|
3473
4102
|
uvBin,
|
|
@@ -3500,7 +4129,7 @@ function expectedPythonOutputs(paths, packages) {
|
|
|
3500
4129
|
return [
|
|
3501
4130
|
paths.venvPython,
|
|
3502
4131
|
...packages.flatMap(
|
|
3503
|
-
(pkg) => (pkg.expectedBins ?? []).map((bin) =>
|
|
4132
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join7(paths.venvBin, bin))
|
|
3504
4133
|
)
|
|
3505
4134
|
];
|
|
3506
4135
|
}
|
|
@@ -3613,7 +4242,7 @@ async function ensurePythonRuntime(options) {
|
|
|
3613
4242
|
|
|
3614
4243
|
// src/server/workspace/provisioning/skills.ts
|
|
3615
4244
|
import { stat as stat5 } from "fs/promises";
|
|
3616
|
-
import { join as
|
|
4245
|
+
import { join as join8 } from "path";
|
|
3617
4246
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3618
4247
|
var GENERATED_SKILLS_REL = ".boring-agent/skills";
|
|
3619
4248
|
var USER_SKILLS_REL = ".agents/skills";
|
|
@@ -3626,7 +4255,7 @@ function assertSafeSegment(kind, value) {
|
|
|
3626
4255
|
}
|
|
3627
4256
|
}
|
|
3628
4257
|
function getProvisionedSkillPaths(paths) {
|
|
3629
|
-
return [paths.skills,
|
|
4258
|
+
return [paths.skills, join8(paths.workspaceRoot, USER_SKILLS_REL)];
|
|
3630
4259
|
}
|
|
3631
4260
|
async function mirrorPluginSkills(options) {
|
|
3632
4261
|
await options.adapter.workspaceFs.rm(GENERATED_SKILLS_REL);
|
|
@@ -3659,7 +4288,7 @@ async function mirrorPluginSkills(options) {
|
|
|
3659
4288
|
// src/server/workspace/provisioning/workspaceFiles.ts
|
|
3660
4289
|
import { basename, join as joinPath } from "path";
|
|
3661
4290
|
import { posix as posix2 } from "path";
|
|
3662
|
-
import { readdir as
|
|
4291
|
+
import { readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
3663
4292
|
import { fileURLToPath as fileURLToPath5, pathToFileURL } from "url";
|
|
3664
4293
|
function sourceToPath3(source) {
|
|
3665
4294
|
return source instanceof URL ? fileURLToPath5(source) : source;
|
|
@@ -3701,7 +4330,7 @@ async function collectTemplateWorkItems(template) {
|
|
|
3701
4330
|
kind: "directory"
|
|
3702
4331
|
});
|
|
3703
4332
|
}
|
|
3704
|
-
const entries = await
|
|
4333
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
3705
4334
|
for (const entry of entries) {
|
|
3706
4335
|
const childPath = joinPath(dir, entry.name);
|
|
3707
4336
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
@@ -3958,61 +4587,6 @@ import { spawnSync } from "child_process";
|
|
|
3958
4587
|
// src/server/runtime/modes/direct.ts
|
|
3959
4588
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
3960
4589
|
|
|
3961
|
-
// src/server/runtime/createServerFileSearch.ts
|
|
3962
|
-
var DEFAULT_LIMIT = 500;
|
|
3963
|
-
var MAX_LIMIT = 5e3;
|
|
3964
|
-
var SEARCH_TIMEOUT_MS = 5e3;
|
|
3965
|
-
var SEARCH_MAX_OUTPUT_BYTES = 256e3;
|
|
3966
|
-
function shellQuote3(value) {
|
|
3967
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3968
|
-
}
|
|
3969
|
-
function normalizeLimit(limit) {
|
|
3970
|
-
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
3971
|
-
return DEFAULT_LIMIT;
|
|
3972
|
-
}
|
|
3973
|
-
const normalized = Math.trunc(limit);
|
|
3974
|
-
if (normalized <= 0) return DEFAULT_LIMIT;
|
|
3975
|
-
return Math.min(normalized, MAX_LIMIT);
|
|
3976
|
-
}
|
|
3977
|
-
function buildFindArgs(glob) {
|
|
3978
|
-
const isPathShaped = glob.includes("/") || glob.includes("**");
|
|
3979
|
-
if (!isPathShaped) {
|
|
3980
|
-
return `-iname ${shellQuote3(glob)}`;
|
|
3981
|
-
}
|
|
3982
|
-
let translated = glob.replaceAll("**", "*");
|
|
3983
|
-
translated = translated.replace(/^\/+/, "");
|
|
3984
|
-
if (!translated.startsWith("*")) {
|
|
3985
|
-
translated = `*${translated}`;
|
|
3986
|
-
}
|
|
3987
|
-
return `-ipath ${shellQuote3(translated)}`;
|
|
3988
|
-
}
|
|
3989
|
-
function createServerFileSearch(workspace, sandbox) {
|
|
3990
|
-
return {
|
|
3991
|
-
async search(glob, limit = DEFAULT_LIMIT) {
|
|
3992
|
-
const safeLimit = normalizeLimit(limit);
|
|
3993
|
-
const command = [
|
|
3994
|
-
"find .",
|
|
3995
|
-
"-maxdepth 10",
|
|
3996
|
-
"\\( -path './.boring-agent' -o -path './.git' -o -path './node_modules' \\) -prune -o",
|
|
3997
|
-
buildFindArgs(glob),
|
|
3998
|
-
"-type f",
|
|
3999
|
-
"-print",
|
|
4000
|
-
`| head -n ${safeLimit}`
|
|
4001
|
-
].join(" ");
|
|
4002
|
-
const { stdout, exitCode } = await sandbox.exec(command, {
|
|
4003
|
-
cwd: workspace.root,
|
|
4004
|
-
timeoutMs: SEARCH_TIMEOUT_MS,
|
|
4005
|
-
maxOutputBytes: SEARCH_MAX_OUTPUT_BYTES
|
|
4006
|
-
});
|
|
4007
|
-
if (exitCode !== 0) {
|
|
4008
|
-
throw new Error(`file-search failed: exit ${exitCode}`);
|
|
4009
|
-
}
|
|
4010
|
-
const decoded = new TextDecoder().decode(stdout);
|
|
4011
|
-
return decoded.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.length > 0).map((line) => line.replace(/^\.\//, ""));
|
|
4012
|
-
}
|
|
4013
|
-
};
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
4590
|
// src/server/workspace/provision.ts
|
|
4017
4591
|
import { cp as cp2 } from "fs/promises";
|
|
4018
4592
|
async function copyTemplate(templatePath, workspaceRoot) {
|
|
@@ -4300,8 +4874,8 @@ var localModeAdapter = {
|
|
|
4300
4874
|
};
|
|
4301
4875
|
|
|
4302
4876
|
// src/server/runtime/modes/vercel-sandbox.ts
|
|
4303
|
-
import { readFile as readFile8, readdir as
|
|
4304
|
-
import { join as
|
|
4877
|
+
import { readFile as readFile8, readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
4878
|
+
import { join as join9 } from "path";
|
|
4305
4879
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
4306
4880
|
import { Sandbox as VercelSandbox } from "@vercel/sandbox";
|
|
4307
4881
|
|
|
@@ -4447,20 +5021,20 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
|
4447
5021
|
|
|
4448
5022
|
// src/server/sandbox/vercel-sandbox/packageTemplate.ts
|
|
4449
5023
|
import { createHash as createHash5 } from "crypto";
|
|
4450
|
-
import { readFile as readFile7, readdir as
|
|
5024
|
+
import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
|
|
4451
5025
|
import path3 from "path";
|
|
4452
5026
|
import { Readable } from "stream";
|
|
4453
5027
|
import { createGzip } from "zlib";
|
|
4454
5028
|
import { put } from "@vercel/blob";
|
|
4455
5029
|
var urlCache = /* @__PURE__ */ new Map();
|
|
4456
5030
|
var LOG_PREFIX = "[template-tarball]";
|
|
4457
|
-
function
|
|
5031
|
+
function log3(msg, meta = {}) {
|
|
4458
5032
|
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
|
|
4459
5033
|
process.stderr.write(`${LOG_PREFIX} ${msg}${metaStr}
|
|
4460
5034
|
`);
|
|
4461
5035
|
}
|
|
4462
5036
|
async function collectFiles(dir, base = "") {
|
|
4463
|
-
const entries = await
|
|
5037
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
4464
5038
|
const files = [];
|
|
4465
5039
|
for (const entry of entries) {
|
|
4466
5040
|
const fullPath = path3.join(dir, entry.name);
|
|
@@ -4537,11 +5111,11 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
4537
5111
|
const hash = computeTemplateHash(files);
|
|
4538
5112
|
const cached = urlCache.get(hash);
|
|
4539
5113
|
if (cached) {
|
|
4540
|
-
|
|
5114
|
+
log3("cache hit", { hash, fileCount: files.length });
|
|
4541
5115
|
return { url: cached, hash };
|
|
4542
5116
|
}
|
|
4543
5117
|
const tarball = await buildTarGz(files);
|
|
4544
|
-
|
|
5118
|
+
log3("tarball built", {
|
|
4545
5119
|
hash,
|
|
4546
5120
|
fileCount: files.length,
|
|
4547
5121
|
sizeBytes: tarball.length,
|
|
@@ -4550,7 +5124,7 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
4550
5124
|
const upload = opts.uploadFn ?? defaultUpload;
|
|
4551
5125
|
const url = await upload(hash, tarball);
|
|
4552
5126
|
urlCache.set(hash, url);
|
|
4553
|
-
|
|
5127
|
+
log3("uploaded", { hash, url, totalMs: Date.now() - startMs });
|
|
4554
5128
|
return { url, hash };
|
|
4555
5129
|
}
|
|
4556
5130
|
|
|
@@ -4716,10 +5290,10 @@ async function copyHostPathToVercelWorkspace(options) {
|
|
|
4716
5290
|
const sourceStat = await stat8(sourcePath);
|
|
4717
5291
|
if (sourceStat.isDirectory()) {
|
|
4718
5292
|
await options.workspace.mkdir(options.targetRel, { recursive: true });
|
|
4719
|
-
for (const entry of await
|
|
5293
|
+
for (const entry of await readdir6(sourcePath, { withFileTypes: true })) {
|
|
4720
5294
|
await copyHostPathToVercelWorkspace({
|
|
4721
5295
|
workspace: options.workspace,
|
|
4722
|
-
source:
|
|
5296
|
+
source: join9(sourcePath, entry.name),
|
|
4723
5297
|
targetRel: `${options.targetRel}/${entry.name}`
|
|
4724
5298
|
});
|
|
4725
5299
|
}
|
|
@@ -5147,7 +5721,7 @@ function getRuntimeBundleStorageRoot(bundle) {
|
|
|
5147
5721
|
|
|
5148
5722
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
5149
5723
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
5150
|
-
import { extname as extname3, join as
|
|
5724
|
+
import { extname as extname3, join as join11 } from "path";
|
|
5151
5725
|
import {
|
|
5152
5726
|
createAgentSession,
|
|
5153
5727
|
SessionManager,
|
|
@@ -5427,7 +6001,7 @@ function createPiAgentSessionAdapter(session, options = {}) {
|
|
|
5427
6001
|
// src/server/harness/pi-coding-agent/sessions.ts
|
|
5428
6002
|
import { randomUUID } from "crypto";
|
|
5429
6003
|
import {
|
|
5430
|
-
readdir as
|
|
6004
|
+
readdir as readdir7,
|
|
5431
6005
|
readFile as readFile9,
|
|
5432
6006
|
stat as fsStat,
|
|
5433
6007
|
rm as rm4,
|
|
@@ -5438,7 +6012,7 @@ import {
|
|
|
5438
6012
|
open as open2
|
|
5439
6013
|
} from "fs/promises";
|
|
5440
6014
|
import { closeSync, openSync, readFileSync, readSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5441
|
-
import { join as
|
|
6015
|
+
import { join as join10, basename as basename2, resolve as resolve7 } from "path";
|
|
5442
6016
|
import { homedir as homedir3 } from "os";
|
|
5443
6017
|
import {
|
|
5444
6018
|
parseSessionEntries,
|
|
@@ -5446,7 +6020,7 @@ import {
|
|
|
5446
6020
|
} from "@mariozechner/pi-coding-agent";
|
|
5447
6021
|
function defaultSessionDir(cwd) {
|
|
5448
6022
|
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
5449
|
-
return
|
|
6023
|
+
return join10(homedir3(), ".pi", "agent", "sessions", safePath);
|
|
5450
6024
|
}
|
|
5451
6025
|
var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
5452
6026
|
var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
|
|
@@ -5456,7 +6030,7 @@ function sessionDirForNamespace(namespace) {
|
|
|
5456
6030
|
if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
|
|
5457
6031
|
throw new Error("session namespace must contain only letters, numbers, underscores, and dashes");
|
|
5458
6032
|
}
|
|
5459
|
-
return
|
|
6033
|
+
return join10(homedir3(), ".pi", "agent", "sessions", safeNamespace);
|
|
5460
6034
|
}
|
|
5461
6035
|
function normalizeListOptions(options) {
|
|
5462
6036
|
return {
|
|
@@ -5501,9 +6075,9 @@ var PiSessionStore = class {
|
|
|
5501
6075
|
}
|
|
5502
6076
|
}
|
|
5503
6077
|
async listUncached(_ctx, options) {
|
|
5504
|
-
const files = await
|
|
6078
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5505
6079
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
5506
|
-
const filepaths = jsonlFiles.map((f) =>
|
|
6080
|
+
const filepaths = jsonlFiles.map((f) => join10(this.sessionDir, f));
|
|
5507
6081
|
const fileStats = await Promise.all(filepaths.map(async (filepath) => {
|
|
5508
6082
|
try {
|
|
5509
6083
|
return { filepath, stat: await fsStat(filepath) };
|
|
@@ -5547,7 +6121,7 @@ var PiSessionStore = class {
|
|
|
5547
6121
|
};
|
|
5548
6122
|
lines.push(JSON.stringify(infoEntry));
|
|
5549
6123
|
}
|
|
5550
|
-
const filepath =
|
|
6124
|
+
const filepath = join10(this.sessionDir, `${id}.jsonl`);
|
|
5551
6125
|
await writeFile6(filepath, lines.join("\n") + "\n", "utf-8");
|
|
5552
6126
|
return {
|
|
5553
6127
|
id,
|
|
@@ -5629,7 +6203,7 @@ var PiSessionStore = class {
|
|
|
5629
6203
|
loadPiSessionFileSync(sessionId) {
|
|
5630
6204
|
if (!SAFE_ID.test(sessionId)) return null;
|
|
5631
6205
|
try {
|
|
5632
|
-
const direct =
|
|
6206
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5633
6207
|
let filepath = direct;
|
|
5634
6208
|
let content;
|
|
5635
6209
|
try {
|
|
@@ -5639,7 +6213,7 @@ var PiSessionStore = class {
|
|
|
5639
6213
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
5640
6214
|
);
|
|
5641
6215
|
if (files.length === 0) return null;
|
|
5642
|
-
filepath =
|
|
6216
|
+
filepath = join10(this.sessionDir, files[0]);
|
|
5643
6217
|
content = readFileSync(filepath, "utf-8");
|
|
5644
6218
|
}
|
|
5645
6219
|
const entries = safeParseEntries(content);
|
|
@@ -5695,18 +6269,18 @@ var PiSessionStore = class {
|
|
|
5695
6269
|
if (!SAFE_ID.test(sessionId)) {
|
|
5696
6270
|
throw new Error(`Session not found: ${sessionId}`);
|
|
5697
6271
|
}
|
|
5698
|
-
const direct =
|
|
6272
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5699
6273
|
try {
|
|
5700
6274
|
await fsStat(direct);
|
|
5701
6275
|
return direct;
|
|
5702
6276
|
} catch {
|
|
5703
6277
|
}
|
|
5704
|
-
const files = await
|
|
6278
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5705
6279
|
const match = files.find(
|
|
5706
6280
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
5707
6281
|
);
|
|
5708
6282
|
if (!match) throw new Error(`Session not found: ${sessionId}`);
|
|
5709
|
-
const matchedPath =
|
|
6283
|
+
const matchedPath = join10(this.sessionDir, match);
|
|
5710
6284
|
if (!isTimestampNamedPiSessionFile(matchedPath, sessionId)) return matchedPath;
|
|
5711
6285
|
const existingWrapper = await this.findWrapperReferencingNativeSession(matchedPath);
|
|
5712
6286
|
if (existingWrapper) {
|
|
@@ -5871,7 +6445,7 @@ var PiSessionStore = class {
|
|
|
5871
6445
|
try {
|
|
5872
6446
|
const files = readdirSync(this.sessionDir).filter((file) => file.endsWith(".jsonl"));
|
|
5873
6447
|
for (const file of files) {
|
|
5874
|
-
const filepath =
|
|
6448
|
+
const filepath = join10(this.sessionDir, file);
|
|
5875
6449
|
if (resolve7(filepath) === resolvedNativePath) continue;
|
|
5876
6450
|
try {
|
|
5877
6451
|
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries(readJsonlPrefixSync(filepath)));
|
|
@@ -5886,10 +6460,10 @@ var PiSessionStore = class {
|
|
|
5886
6460
|
}
|
|
5887
6461
|
async findWrapperReferencingNativeSession(nativePath) {
|
|
5888
6462
|
const resolvedNativePath = resolve7(nativePath);
|
|
5889
|
-
const files = await
|
|
6463
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5890
6464
|
for (const file of files) {
|
|
5891
6465
|
if (!file.endsWith(".jsonl")) continue;
|
|
5892
|
-
const filepath =
|
|
6466
|
+
const filepath = join10(this.sessionDir, file);
|
|
5893
6467
|
if (resolve7(filepath) === resolvedNativePath) continue;
|
|
5894
6468
|
try {
|
|
5895
6469
|
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries(await readJsonlPrefix(filepath)));
|
|
@@ -5900,7 +6474,7 @@ var PiSessionStore = class {
|
|
|
5900
6474
|
return null;
|
|
5901
6475
|
}
|
|
5902
6476
|
ensureWrapperForNativeSessionSync(sessionId, nativePath, entries) {
|
|
5903
|
-
const wrapperPath =
|
|
6477
|
+
const wrapperPath = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5904
6478
|
if (resolve7(wrapperPath) === resolve7(nativePath)) return wrapperPath;
|
|
5905
6479
|
try {
|
|
5906
6480
|
readFileSync(wrapperPath, "utf-8");
|
|
@@ -5920,7 +6494,7 @@ var PiSessionStore = class {
|
|
|
5920
6494
|
return wrapperPath;
|
|
5921
6495
|
}
|
|
5922
6496
|
async ensureWrapperForNativeSession(sessionId, nativePath) {
|
|
5923
|
-
const wrapperPath =
|
|
6497
|
+
const wrapperPath = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5924
6498
|
if (resolve7(wrapperPath) === resolve7(nativePath)) return wrapperPath;
|
|
5925
6499
|
try {
|
|
5926
6500
|
await fsStat(wrapperPath);
|
|
@@ -6087,6 +6661,11 @@ var INFOMANIAK_PROVIDER = "infomaniak";
|
|
|
6087
6661
|
var INFOMANIAK_API_BASE = "https://api.infomaniak.com";
|
|
6088
6662
|
var DEFAULT_CUSTOM_MODEL_MAX_TOKENS = 16384;
|
|
6089
6663
|
var DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW = 2e5;
|
|
6664
|
+
var DEFAULT_INFOMANIAK_MODELS = [
|
|
6665
|
+
"moonshotai/Kimi-K2.6",
|
|
6666
|
+
"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8",
|
|
6667
|
+
"Qwen/Qwen3.5-122B-A10B-FP8"
|
|
6668
|
+
];
|
|
6090
6669
|
function clean(value) {
|
|
6091
6670
|
const trimmed = value?.trim();
|
|
6092
6671
|
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
@@ -6110,6 +6689,19 @@ function readModelInput(name) {
|
|
|
6110
6689
|
const parsed = raw.split(",").map((part) => part.trim()).filter((part) => part === "text" || part === "image");
|
|
6111
6690
|
return parsed.length > 0 ? parsed : ["text"];
|
|
6112
6691
|
}
|
|
6692
|
+
function readMaxTokensField(name, fallback = "max_completion_tokens") {
|
|
6693
|
+
const raw = clean(getEnv(name));
|
|
6694
|
+
return raw === "max_tokens" || raw === "max_completion_tokens" ? raw : fallback;
|
|
6695
|
+
}
|
|
6696
|
+
function buildOpenAICompletionsCompat(envPrefix, defaults = {}) {
|
|
6697
|
+
return {
|
|
6698
|
+
supportsStore: readBoolean(`${envPrefix}_SUPPORTS_STORE`, defaults.supportsStore ?? false),
|
|
6699
|
+
supportsDeveloperRole: readBoolean(`${envPrefix}_SUPPORTS_DEVELOPER_ROLE`, defaults.supportsDeveloperRole ?? true),
|
|
6700
|
+
supportsReasoningEffort: readBoolean(`${envPrefix}_SUPPORTS_REASONING_EFFORT`, defaults.supportsReasoningEffort ?? true),
|
|
6701
|
+
supportsUsageInStreaming: readBoolean(`${envPrefix}_SUPPORTS_USAGE_IN_STREAMING`, defaults.supportsUsageInStreaming ?? true),
|
|
6702
|
+
maxTokensField: readMaxTokensField(`${envPrefix}_MAX_TOKENS_FIELD`, defaults.maxTokensField)
|
|
6703
|
+
};
|
|
6704
|
+
}
|
|
6113
6705
|
function readApiKeyEnv(candidates) {
|
|
6114
6706
|
for (const candidate of candidates) {
|
|
6115
6707
|
const envName = clean(candidate);
|
|
@@ -6122,31 +6714,23 @@ function buildOpenAICompatibleProviderConfig(opts) {
|
|
|
6122
6714
|
baseUrl: opts.baseUrl,
|
|
6123
6715
|
apiKey: opts.apiKeyEnv,
|
|
6124
6716
|
api: "openai-completions",
|
|
6125
|
-
models:
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
supportsStore: false,
|
|
6143
|
-
supportsDeveloperRole: true,
|
|
6144
|
-
supportsReasoningEffort: true,
|
|
6145
|
-
supportsUsageInStreaming: true,
|
|
6146
|
-
maxTokensField: "max_completion_tokens"
|
|
6147
|
-
}
|
|
6148
|
-
}
|
|
6149
|
-
]
|
|
6717
|
+
models: opts.models.map((model) => ({
|
|
6718
|
+
id: model.id,
|
|
6719
|
+
name: model.name ?? model.id,
|
|
6720
|
+
api: "openai-completions",
|
|
6721
|
+
reasoning: readBoolean(`${opts.envPrefix}_REASONING`, true),
|
|
6722
|
+
input: readModelInput(`${opts.envPrefix}_INPUT`),
|
|
6723
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
6724
|
+
contextWindow: readPositiveInt(
|
|
6725
|
+
`${opts.envPrefix}_CONTEXT_WINDOW`,
|
|
6726
|
+
DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW
|
|
6727
|
+
),
|
|
6728
|
+
maxTokens: readPositiveInt(
|
|
6729
|
+
`${opts.envPrefix}_MAX_TOKENS`,
|
|
6730
|
+
DEFAULT_CUSTOM_MODEL_MAX_TOKENS
|
|
6731
|
+
),
|
|
6732
|
+
compat: buildOpenAICompletionsCompat(opts.envPrefix, opts.compatDefaults)
|
|
6733
|
+
}))
|
|
6150
6734
|
};
|
|
6151
6735
|
}
|
|
6152
6736
|
function readInfomaniakBaseUrl() {
|
|
@@ -6156,25 +6740,46 @@ function readInfomaniakBaseUrl() {
|
|
|
6156
6740
|
if (!productId) return void 0;
|
|
6157
6741
|
return `${INFOMANIAK_API_BASE}/2/ai/${productId}/openai/v1`;
|
|
6158
6742
|
}
|
|
6743
|
+
function readInfomaniakModelIds() {
|
|
6744
|
+
const configured = clean(getEnv("BORING_AGENT_INFOMANIAK_MODELS"))?.split(",").map((part) => part.trim()).filter(Boolean);
|
|
6745
|
+
const ids = configured?.length ? configured : [...DEFAULT_INFOMANIAK_MODELS];
|
|
6746
|
+
const legacyModel = clean(getEnv("BORING_AGENT_INFOMANIAK_MODEL"));
|
|
6747
|
+
if (legacyModel && !ids.includes(legacyModel)) ids.push(legacyModel);
|
|
6748
|
+
return Array.from(new Set(ids));
|
|
6749
|
+
}
|
|
6159
6750
|
function readInfomaniakProvider() {
|
|
6160
|
-
const
|
|
6751
|
+
const modelIds = readInfomaniakModelIds();
|
|
6752
|
+
const defaultModelId = clean(getEnv("BORING_AGENT_INFOMANIAK_MODEL")) ?? clean(getEnv("BORING_AGENT_DEFAULT_MODEL_ID")) ?? modelIds[0];
|
|
6161
6753
|
const baseUrl = readInfomaniakBaseUrl();
|
|
6162
6754
|
const apiKeyEnv = readApiKeyEnv([
|
|
6163
6755
|
clean(getEnv("BORING_AGENT_INFOMANIAK_API_KEY_ENV")) ?? "",
|
|
6164
6756
|
"INFOMANIAK_API_TOKEN",
|
|
6165
6757
|
"BORING_AGENT_INFOMANIAK_API_KEY"
|
|
6166
6758
|
]);
|
|
6167
|
-
if (!
|
|
6759
|
+
if (!defaultModelId || !baseUrl || !apiKeyEnv) return void 0;
|
|
6168
6760
|
const provider = clean(getEnv("BORING_AGENT_INFOMANIAK_PROVIDER")) ?? INFOMANIAK_PROVIDER;
|
|
6761
|
+
const modelName = clean(getEnv("BORING_AGENT_INFOMANIAK_MODEL_NAME"));
|
|
6762
|
+
const models = modelIds.map((id) => {
|
|
6763
|
+
const model = { id };
|
|
6764
|
+
if (id === defaultModelId && modelName) model.name = modelName;
|
|
6765
|
+
return model;
|
|
6766
|
+
});
|
|
6767
|
+
if (!models.some((model) => model.id === defaultModelId)) {
|
|
6768
|
+
models.push({ id: defaultModelId, name: modelName });
|
|
6769
|
+
}
|
|
6169
6770
|
return {
|
|
6170
6771
|
provider,
|
|
6171
|
-
|
|
6772
|
+
models: models.map((model) => ({ provider, id: model.id })),
|
|
6773
|
+
defaultModel: { provider, id: defaultModelId },
|
|
6172
6774
|
config: buildOpenAICompatibleProviderConfig({
|
|
6173
|
-
|
|
6174
|
-
modelName: clean(getEnv("BORING_AGENT_INFOMANIAK_MODEL_NAME")),
|
|
6775
|
+
models,
|
|
6175
6776
|
apiKeyEnv,
|
|
6176
6777
|
baseUrl,
|
|
6177
|
-
envPrefix: "BORING_AGENT_INFOMANIAK"
|
|
6778
|
+
envPrefix: "BORING_AGENT_INFOMANIAK",
|
|
6779
|
+
// Infomaniak's OpenAI-compatible chat endpoint rejects OpenAI's newer
|
|
6780
|
+
// `developer` role (surfacing as "400 Unexpected message role"). Hosts
|
|
6781
|
+
// can opt back in with BORING_AGENT_INFOMANIAK_SUPPORTS_DEVELOPER_ROLE=1.
|
|
6782
|
+
compatDefaults: { supportsDeveloperRole: false, supportsReasoningEffort: false }
|
|
6178
6783
|
})
|
|
6179
6784
|
};
|
|
6180
6785
|
}
|
|
@@ -6187,12 +6792,15 @@ function readCustomProvider() {
|
|
|
6187
6792
|
"BORING_AGENT_CUSTOM_MODEL_API_KEY"
|
|
6188
6793
|
]);
|
|
6189
6794
|
if (!provider || !modelId || !baseUrl || !apiKeyEnv) return void 0;
|
|
6795
|
+
const modelName = clean(getEnv("BORING_AGENT_CUSTOM_MODEL_NAME"));
|
|
6796
|
+
const model = { id: modelId };
|
|
6797
|
+
if (modelName) model.name = modelName;
|
|
6190
6798
|
return {
|
|
6191
6799
|
provider,
|
|
6192
|
-
|
|
6800
|
+
models: [{ provider, id: modelId }],
|
|
6801
|
+
defaultModel: { provider, id: modelId },
|
|
6193
6802
|
config: buildOpenAICompatibleProviderConfig({
|
|
6194
|
-
|
|
6195
|
-
modelName: clean(getEnv("BORING_AGENT_CUSTOM_MODEL_NAME")),
|
|
6803
|
+
models: [model],
|
|
6196
6804
|
apiKeyEnv,
|
|
6197
6805
|
baseUrl,
|
|
6198
6806
|
envPrefix: "BORING_AGENT_CUSTOM_MODEL"
|
|
@@ -6206,7 +6814,7 @@ function registerConfiguredModelProviders(registry) {
|
|
|
6206
6814
|
if (!provider) continue;
|
|
6207
6815
|
if (seen.has(provider.provider)) continue;
|
|
6208
6816
|
registry.registerProvider(provider.provider, provider.config);
|
|
6209
|
-
registered.push(provider.
|
|
6817
|
+
registered.push(...provider.models);
|
|
6210
6818
|
seen.add(provider.provider);
|
|
6211
6819
|
}
|
|
6212
6820
|
return registered;
|
|
@@ -6234,7 +6842,7 @@ function readConfiguredDefaultModel() {
|
|
|
6234
6842
|
if (explicitProvider && explicitId) {
|
|
6235
6843
|
return { provider: explicitProvider, id: explicitId };
|
|
6236
6844
|
}
|
|
6237
|
-
return readPiSettingsDefaultModel() ?? readInfomaniakProvider()?.
|
|
6845
|
+
return readPiSettingsDefaultModel() ?? readInfomaniakProvider()?.defaultModel ?? readCustomProvider()?.defaultModel;
|
|
6238
6846
|
}
|
|
6239
6847
|
|
|
6240
6848
|
// src/server/piPackages.ts
|
|
@@ -6299,6 +6907,10 @@ var PYTHON_RUNTIME_GUIDELINE = [
|
|
|
6299
6907
|
function composeSystemPromptAppend(hostAppend) {
|
|
6300
6908
|
return [WORKSPACE_PATHS_GUIDELINE, PYTHON_RUNTIME_GUIDELINE, hostAppend?.trim()].filter(Boolean).join("\n\n");
|
|
6301
6909
|
}
|
|
6910
|
+
function withPiHarnessDefaults(pi) {
|
|
6911
|
+
const { noContextFiles = true, noSkills = true, ...rest } = pi ?? {};
|
|
6912
|
+
return { ...rest, noContextFiles, noSkills };
|
|
6913
|
+
}
|
|
6302
6914
|
function buildDynamicPromptExtension(source) {
|
|
6303
6915
|
return (pi) => {
|
|
6304
6916
|
pi.on("before_agent_start", async (event) => {
|
|
@@ -6354,8 +6966,8 @@ function mergeInjectedProjectPackages(settingsJson, piPackages) {
|
|
|
6354
6966
|
}
|
|
6355
6967
|
function createResourceSettingsManager(cwd, agentDir, piPackages) {
|
|
6356
6968
|
if (piPackages.length === 0) return SettingsManager2.create(cwd, agentDir);
|
|
6357
|
-
const globalSettingsPath =
|
|
6358
|
-
const projectSettingsPath =
|
|
6969
|
+
const globalSettingsPath = join11(agentDir, "settings.json");
|
|
6970
|
+
const projectSettingsPath = join11(cwd, ".pi", "settings.json");
|
|
6359
6971
|
let globalSettingsOverrideJson;
|
|
6360
6972
|
let projectSettingsOverrideJson;
|
|
6361
6973
|
const storage = {
|
|
@@ -6385,7 +6997,7 @@ async function applyRequestedSessionOptions(handle, input) {
|
|
|
6385
6997
|
handle.piSession.setThinkingLevel(input.thinkingLevel);
|
|
6386
6998
|
}
|
|
6387
6999
|
}
|
|
6388
|
-
var
|
|
7000
|
+
var log4 = createLogger("pi-harness");
|
|
6389
7001
|
function deriveSourcePlugin(sourceInfo) {
|
|
6390
7002
|
if (!sourceInfo) return void 0;
|
|
6391
7003
|
const path4 = typeof sourceInfo.path === "string" ? sourceInfo.path : "";
|
|
@@ -6410,6 +7022,7 @@ function normalizeSlashCommandInfo(command) {
|
|
|
6410
7022
|
};
|
|
6411
7023
|
}
|
|
6412
7024
|
function createPiCodingAgentHarness(opts) {
|
|
7025
|
+
const pi = withPiHarnessDefaults(opts.pi);
|
|
6413
7026
|
const sessionStore = new PiSessionStore(opts.runtimeCwd ?? opts.cwd, {
|
|
6414
7027
|
sessionNamespace: opts.sessionNamespace,
|
|
6415
7028
|
sessionDir: opts.sessionDir,
|
|
@@ -6420,22 +7033,22 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6420
7033
|
const effectivePackages = [];
|
|
6421
7034
|
const effectiveExtensionPaths = [];
|
|
6422
7035
|
const refreshEffectiveResources = () => {
|
|
6423
|
-
const dynamic =
|
|
7036
|
+
const dynamic = pi.getHotReloadableResources?.() ?? {};
|
|
6424
7037
|
effectiveSkillPaths.splice(
|
|
6425
7038
|
0,
|
|
6426
7039
|
effectiveSkillPaths.length,
|
|
6427
|
-
...
|
|
7040
|
+
...pi.additionalSkillPaths ?? [],
|
|
6428
7041
|
...dynamic.additionalSkillPaths ?? []
|
|
6429
7042
|
);
|
|
6430
7043
|
effectivePackages.splice(
|
|
6431
7044
|
0,
|
|
6432
7045
|
effectivePackages.length,
|
|
6433
|
-
...mergePiPackageSources(
|
|
7046
|
+
...mergePiPackageSources(pi.packages ?? [], dynamic.packages ?? [])
|
|
6434
7047
|
);
|
|
6435
7048
|
effectiveExtensionPaths.splice(
|
|
6436
7049
|
0,
|
|
6437
7050
|
effectiveExtensionPaths.length,
|
|
6438
|
-
...
|
|
7051
|
+
...pi.extensionPaths ?? [],
|
|
6439
7052
|
...dynamic.extensionPaths ?? []
|
|
6440
7053
|
);
|
|
6441
7054
|
};
|
|
@@ -6492,7 +7105,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6492
7105
|
const extensionFactories = [
|
|
6493
7106
|
toolErrorResultExtension,
|
|
6494
7107
|
...dynamicPromptExtension ? [dynamicPromptExtension] : [],
|
|
6495
|
-
...
|
|
7108
|
+
...pi.extensionFactories ?? []
|
|
6496
7109
|
];
|
|
6497
7110
|
const settingsManager = createResourceSettingsManager(
|
|
6498
7111
|
opts.cwd,
|
|
@@ -6506,8 +7119,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6506
7119
|
appendSystemPromptOverride: (base) => [...base, composedSystemPromptAppend],
|
|
6507
7120
|
...effectiveExtensionPaths.length ? { additionalExtensionPaths: effectiveExtensionPaths } : {},
|
|
6508
7121
|
...extensionFactories.length ? { extensionFactories } : {},
|
|
6509
|
-
...
|
|
6510
|
-
...
|
|
7122
|
+
...pi.noContextFiles ? { noContextFiles: true } : {},
|
|
7123
|
+
...pi.noSkills ? { noSkills: true } : {},
|
|
6511
7124
|
...effectiveSkillPaths.length ? { additionalSkillPaths: effectiveSkillPaths } : {},
|
|
6512
7125
|
// skillsOverride REPLACES Pi's resolved skill set, which includes
|
|
6513
7126
|
// skills contributed by host-declared pi packages (e.g.
|
|
@@ -6516,7 +7129,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6516
7129
|
// Passing additionalSkillPaths is not, by itself, a request to throw
|
|
6517
7130
|
// away package skills — those should keep flowing through Pi's loader
|
|
6518
7131
|
// and merge with the additional paths.
|
|
6519
|
-
...
|
|
7132
|
+
...pi.noSkills ? {
|
|
6520
7133
|
skillsOverride: () => loadSkills({
|
|
6521
7134
|
cwd: opts.cwd,
|
|
6522
7135
|
agentDir,
|
|
@@ -6647,12 +7260,12 @@ ${entry.message}`;
|
|
|
6647
7260
|
}
|
|
6648
7261
|
|
|
6649
7262
|
// src/server/harness/pi-coding-agent/pluginLoader.ts
|
|
6650
|
-
import { readdir as
|
|
6651
|
-
import { join as
|
|
7263
|
+
import { readdir as readdir8, stat as stat9, readFile as readFile10 } from "fs/promises";
|
|
7264
|
+
import { join as join12, extname as extname4, resolve as resolve8, sep as sep6, relative as relative9 } from "path";
|
|
6652
7265
|
import { homedir as homedir4 } from "os";
|
|
6653
7266
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6654
7267
|
var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs"]);
|
|
6655
|
-
var GLOBAL_DIR =
|
|
7268
|
+
var GLOBAL_DIR = join12(homedir4(), ".pi", "agent", "extensions");
|
|
6656
7269
|
var LOCAL_DIR = ".pi/extensions";
|
|
6657
7270
|
var EXTENSIONS_JSON = ".pi/extensions.json";
|
|
6658
7271
|
async function dirExists2(path4) {
|
|
@@ -6673,8 +7286,8 @@ async function fileExists(path4) {
|
|
|
6673
7286
|
}
|
|
6674
7287
|
async function discoverFromDir(dir, source) {
|
|
6675
7288
|
if (!await dirExists2(dir)) return [];
|
|
6676
|
-
const entries = await
|
|
6677
|
-
return entries.filter((e) => VALID_EXTENSIONS.has(extname4(e))).map((e) => ({ path:
|
|
7289
|
+
const entries = await readdir8(dir);
|
|
7290
|
+
return entries.filter((e) => VALID_EXTENSIONS.has(extname4(e))).map((e) => ({ path: join12(dir, e), source }));
|
|
6678
7291
|
}
|
|
6679
7292
|
function extractTools(mod) {
|
|
6680
7293
|
const tools = [];
|
|
@@ -6704,17 +7317,17 @@ async function loadModule(filePath, importFn) {
|
|
|
6704
7317
|
return extractTools(mod);
|
|
6705
7318
|
}
|
|
6706
7319
|
async function discoverNpmPlugins(cwd) {
|
|
6707
|
-
const nodeModulesDir =
|
|
7320
|
+
const nodeModulesDir = join12(cwd, "node_modules");
|
|
6708
7321
|
if (!await dirExists2(nodeModulesDir)) return [];
|
|
6709
7322
|
try {
|
|
6710
|
-
const entries = await
|
|
6711
|
-
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) =>
|
|
7323
|
+
const entries = await readdir8(nodeModulesDir);
|
|
7324
|
+
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) => join12(nodeModulesDir, e));
|
|
6712
7325
|
} catch {
|
|
6713
7326
|
return [];
|
|
6714
7327
|
}
|
|
6715
7328
|
}
|
|
6716
7329
|
async function loadNpmPlugin(pkgDir, importFn) {
|
|
6717
|
-
const pkgJsonPath =
|
|
7330
|
+
const pkgJsonPath = join12(pkgDir, "package.json");
|
|
6718
7331
|
if (!await fileExists(pkgJsonPath)) return [];
|
|
6719
7332
|
const pkgJson = JSON.parse(await readFile10(pkgJsonPath, "utf-8"));
|
|
6720
7333
|
const main = pkgJson.main ?? "index.js";
|
|
@@ -6730,7 +7343,7 @@ async function loadNpmPlugin(pkgDir, importFn) {
|
|
|
6730
7343
|
return loadModule(resolvedMain, importFn);
|
|
6731
7344
|
}
|
|
6732
7345
|
async function loadExtensionsJson(cwd) {
|
|
6733
|
-
const configPath =
|
|
7346
|
+
const configPath = join12(cwd, EXTENSIONS_JSON);
|
|
6734
7347
|
if (!await fileExists(configPath)) return null;
|
|
6735
7348
|
try {
|
|
6736
7349
|
const raw = await readFile10(configPath, "utf-8");
|
|
@@ -6748,7 +7361,7 @@ async function loadPlugins(options) {
|
|
|
6748
7361
|
const globals = await discoverFromDir(GLOBAL_DIR, "global");
|
|
6749
7362
|
candidates.push(...globals);
|
|
6750
7363
|
}
|
|
6751
|
-
const localDir =
|
|
7364
|
+
const localDir = join12(options.cwd, LOCAL_DIR);
|
|
6752
7365
|
const locals = await discoverFromDir(localDir, "local");
|
|
6753
7366
|
candidates.push(...locals);
|
|
6754
7367
|
const npmDirs = await discoverNpmPlugins(options.cwd);
|
|
@@ -6758,7 +7371,7 @@ async function loadPlugins(options) {
|
|
|
6758
7371
|
const config = await loadExtensionsJson(options.cwd);
|
|
6759
7372
|
if (config?.npm) {
|
|
6760
7373
|
for (const pkg of config.npm) {
|
|
6761
|
-
const pkgDir =
|
|
7374
|
+
const pkgDir = join12(options.cwd, "node_modules", pkg);
|
|
6762
7375
|
if (await dirExists2(pkgDir)) {
|
|
6763
7376
|
const already = candidates.some((c) => c.path === pkgDir);
|
|
6764
7377
|
if (!already) {
|
|
@@ -6807,8 +7420,8 @@ import {
|
|
|
6807
7420
|
|
|
6808
7421
|
// src/server/tools/operations/bound.ts
|
|
6809
7422
|
import { constants as constants4 } from "fs";
|
|
6810
|
-
import { access as access4, lstat as lstat4, mkdir as mkdir12, readFile as readFile11, readdir as
|
|
6811
|
-
import { dirname as dirname11, isAbsolute as isAbsolute7, join as
|
|
7423
|
+
import { access as access4, lstat as lstat4, mkdir as mkdir12, readFile as readFile11, readdir as readdir9, readlink, realpath as realpath4, stat as stat10, writeFile as writeFile7 } from "fs/promises";
|
|
7424
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join13, relative as relative10, resolve as resolve9 } from "path";
|
|
6812
7425
|
function toPosixPath(value) {
|
|
6813
7426
|
return value.split("\\").join("/");
|
|
6814
7427
|
}
|
|
@@ -6861,7 +7474,7 @@ function shouldSkipDir(relativePath, ignore) {
|
|
|
6861
7474
|
}
|
|
6862
7475
|
async function walkMatches(root, current, pattern, ignore, limit, out) {
|
|
6863
7476
|
if (out.length >= limit) return;
|
|
6864
|
-
const entries = await
|
|
7477
|
+
const entries = await readdir9(current, { withFileTypes: true });
|
|
6865
7478
|
for (const entry of entries) {
|
|
6866
7479
|
if (out.length >= limit) return;
|
|
6867
7480
|
const absolutePath = resolve9(current, entry.name);
|
|
@@ -6939,7 +7552,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
6939
7552
|
const normalized = toPosixPath(absolutePath);
|
|
6940
7553
|
if (normalized === runtimeRoot) return workspaceRoot;
|
|
6941
7554
|
if (normalized.startsWith(`${runtimeRoot}/`)) {
|
|
6942
|
-
return
|
|
7555
|
+
return join13(workspaceRoot, ...normalized.slice(runtimeRoot.length + 1).split("/"));
|
|
6943
7556
|
}
|
|
6944
7557
|
return absolutePath;
|
|
6945
7558
|
};
|
|
@@ -7043,7 +7656,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7043
7656
|
async readdir(absolutePath) {
|
|
7044
7657
|
const storagePath = toStoragePath(absolutePath);
|
|
7045
7658
|
await assertWithinWorkspace(workspaceRoot, storagePath);
|
|
7046
|
-
return await
|
|
7659
|
+
return await readdir9(storagePath);
|
|
7047
7660
|
}
|
|
7048
7661
|
};
|
|
7049
7662
|
return { read, write, edit, find, grep, ls };
|
|
@@ -7083,10 +7696,10 @@ function vercelBashOps(sandbox, opts = {}) {
|
|
|
7083
7696
|
return {
|
|
7084
7697
|
exec(command, cwd, { onData, signal, timeout, env }) {
|
|
7085
7698
|
const effectiveEnv = opts.mergeEnv ? opts.mergeEnv(env) : env;
|
|
7086
|
-
const
|
|
7699
|
+
const filteredEnv2 = effectiveEnv ? Object.fromEntries(Object.entries(effectiveEnv).filter((e) => e[1] != null)) : void 0;
|
|
7087
7700
|
return sandbox.exec(command, {
|
|
7088
7701
|
cwd,
|
|
7089
|
-
env:
|
|
7702
|
+
env: filteredEnv2,
|
|
7090
7703
|
signal,
|
|
7091
7704
|
timeoutMs: timeout ? timeout * 1e3 : void 0,
|
|
7092
7705
|
onStdout: (chunk) => onData(Buffer.from(chunk)),
|
|
@@ -7470,8 +8083,7 @@ function adaptPiTool(piTool) {
|
|
|
7470
8083
|
}
|
|
7471
8084
|
function buildFilesystemAgentTools(bundle) {
|
|
7472
8085
|
const cwd = bundle.workspace.root;
|
|
7473
|
-
|
|
7474
|
-
if (bundle.sandbox.provider === "vercel-sandbox") {
|
|
8086
|
+
if (bundle.sandbox.provider === "vercel-sandbox" || bundle.sandbox.provider === "remote-worker") {
|
|
7475
8087
|
return [
|
|
7476
8088
|
adaptPiTool(createReadToolDefinition(cwd, { operations: vercelReadOps(bundle.workspace) })),
|
|
7477
8089
|
adaptPiTool(createWriteToolDefinition(cwd, { operations: vercelWriteOps(bundle.workspace) })),
|
|
@@ -7481,6 +8093,7 @@ function buildFilesystemAgentTools(bundle) {
|
|
|
7481
8093
|
adaptPiTool(createLsToolDefinition(cwd, { operations: vercelLsOps(bundle.workspace) }))
|
|
7482
8094
|
];
|
|
7483
8095
|
}
|
|
8096
|
+
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
7484
8097
|
const ops = boundFs(storageRoot, { runtimeRoot: cwd });
|
|
7485
8098
|
return [
|
|
7486
8099
|
adaptPiTool(createReadToolDefinition(cwd, { operations: ops.read })),
|
|
@@ -7616,9 +8229,9 @@ function directSpawnHook(workspaceRoot, runtime) {
|
|
|
7616
8229
|
}
|
|
7617
8230
|
var VERCEL_SAFE_DEFAULT_PATH = "/vercel/runtimes/node24/bin:/vercel/runtimes/node22/bin:/usr/local/bin:/usr/bin:/bin";
|
|
7618
8231
|
function bashOptionsForMode(bundle, runtime) {
|
|
7619
|
-
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
7620
8232
|
switch (bundle.sandbox.provider) {
|
|
7621
8233
|
case "vercel-sandbox":
|
|
8234
|
+
case "remote-worker":
|
|
7622
8235
|
return {
|
|
7623
8236
|
operations: vercelBashOps(bundle.sandbox, {
|
|
7624
8237
|
// The pi bash tool's env may include the host process env. Never
|
|
@@ -7627,16 +8240,20 @@ function bashOptionsForMode(bundle, runtime) {
|
|
|
7627
8240
|
mergeEnv: () => mergeRuntimeEnv(runtime, { PATH: VERCEL_SAFE_DEFAULT_PATH })
|
|
7628
8241
|
})
|
|
7629
8242
|
};
|
|
7630
|
-
case "bwrap":
|
|
8243
|
+
case "bwrap": {
|
|
8244
|
+
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
7631
8245
|
return {
|
|
7632
8246
|
operations: createLocalBashOperations(),
|
|
7633
8247
|
spawnHook: bwrapSpawnHook(storageRoot, runtime)
|
|
7634
8248
|
};
|
|
7635
|
-
|
|
8249
|
+
}
|
|
8250
|
+
default: {
|
|
8251
|
+
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
7636
8252
|
return {
|
|
7637
8253
|
operations: createLocalBashOperations(),
|
|
7638
8254
|
spawnHook: directSpawnHook(storageRoot, runtime)
|
|
7639
8255
|
};
|
|
8256
|
+
}
|
|
7640
8257
|
}
|
|
7641
8258
|
}
|
|
7642
8259
|
function isRuntimeReady(readiness) {
|
|
@@ -7813,9 +8430,21 @@ var DEFAULT_BUFFER_SIZE = 1e3;
|
|
|
7813
8430
|
function createFsEventBroadcaster(watcher, opts = {}) {
|
|
7814
8431
|
const bufferSize = opts.bufferSize ?? DEFAULT_BUFFER_SIZE;
|
|
7815
8432
|
const buffer = [];
|
|
7816
|
-
const listeners = /* @__PURE__ */ new
|
|
8433
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
7817
8434
|
let nextSeq = 1;
|
|
7818
8435
|
let closed = false;
|
|
8436
|
+
let gapAfterSeq = null;
|
|
8437
|
+
const handleControlEvent = (event) => {
|
|
8438
|
+
if (closed || event.type !== "resync-required") return;
|
|
8439
|
+
gapAfterSeq = nextSeq - 1;
|
|
8440
|
+
buffer.length = 0;
|
|
8441
|
+
for (const { onResyncRequired } of [...listeners.values()]) {
|
|
8442
|
+
try {
|
|
8443
|
+
onResyncRequired?.();
|
|
8444
|
+
} catch {
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
};
|
|
7819
8448
|
const watcherUnsub = watcher.subscribe((change) => {
|
|
7820
8449
|
if (closed) return;
|
|
7821
8450
|
const env = {
|
|
@@ -7826,13 +8455,13 @@ function createFsEventBroadcaster(watcher, opts = {}) {
|
|
|
7826
8455
|
};
|
|
7827
8456
|
buffer.push(env);
|
|
7828
8457
|
if (buffer.length > bufferSize) buffer.shift();
|
|
7829
|
-
for (const l of [...listeners]) {
|
|
8458
|
+
for (const l of [...listeners.keys()]) {
|
|
7830
8459
|
try {
|
|
7831
8460
|
l(env);
|
|
7832
8461
|
} catch {
|
|
7833
8462
|
}
|
|
7834
8463
|
}
|
|
7835
|
-
});
|
|
8464
|
+
}, { onControlEvent: handleControlEvent });
|
|
7836
8465
|
return {
|
|
7837
8466
|
subscribe(listener, sopts) {
|
|
7838
8467
|
if (closed) {
|
|
@@ -7843,13 +8472,13 @@ function createFsEventBroadcaster(watcher, opts = {}) {
|
|
|
7843
8472
|
let resyncRequired = false;
|
|
7844
8473
|
if (typeof sopts?.lastSeenSeq === "number" && sopts.lastSeenSeq > 0) {
|
|
7845
8474
|
const head = buffer.length > 0 ? buffer[0].seq : nextSeq;
|
|
7846
|
-
if (sopts.lastSeenSeq < head - 1) {
|
|
8475
|
+
if (gapAfterSeq != null && sopts.lastSeenSeq <= gapAfterSeq || sopts.lastSeenSeq < head - 1) {
|
|
7847
8476
|
resyncRequired = true;
|
|
7848
8477
|
} else {
|
|
7849
8478
|
replay = buffer.filter((e) => e.seq > sopts.lastSeenSeq);
|
|
7850
8479
|
}
|
|
7851
8480
|
}
|
|
7852
|
-
listeners.
|
|
8481
|
+
listeners.set(listener, { onResyncRequired: sopts?.onResyncRequired });
|
|
7853
8482
|
return {
|
|
7854
8483
|
replay,
|
|
7855
8484
|
resyncRequired,
|
|
@@ -7877,11 +8506,20 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7877
8506
|
const ensureBroadcaster = async (request) => {
|
|
7878
8507
|
const workspace = opts.getWorkspace ? await opts.getWorkspace(request) : opts.workspace;
|
|
7879
8508
|
if (!workspace) throw new Error("fs event route requires workspace or getWorkspace");
|
|
7880
|
-
if (typeof workspace.watch !== "function")
|
|
8509
|
+
if (typeof workspace.watch !== "function") {
|
|
8510
|
+
return { unsupported: { reason: "watch_not_implemented" } };
|
|
8511
|
+
}
|
|
7881
8512
|
const workspaceId = request.workspaceContext?.workspaceId ?? "default";
|
|
7882
8513
|
const existing = broadcasters.get(workspaceId);
|
|
7883
8514
|
if (existing) return { workspaceId, entry: existing };
|
|
7884
|
-
const
|
|
8515
|
+
const watcher = workspace.watch();
|
|
8516
|
+
const readiness = await watcher.whenReady?.() ?? { ok: true };
|
|
8517
|
+
if (!readiness.ok) {
|
|
8518
|
+
return { unsupported: { reason: readiness.reason, ...readiness.message ? { message: readiness.message } : {} } };
|
|
8519
|
+
}
|
|
8520
|
+
const existingAfterWait = broadcasters.get(workspaceId);
|
|
8521
|
+
if (existingAfterWait) return { workspaceId, entry: existingAfterWait };
|
|
8522
|
+
const broadcaster = createFsEventBroadcaster(watcher);
|
|
7885
8523
|
const entry = { broadcaster, subscribers: 0 };
|
|
7886
8524
|
broadcasters.set(workspaceId, entry);
|
|
7887
8525
|
return { workspaceId, entry };
|
|
@@ -7901,8 +8539,8 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7901
8539
|
const resolved = await ensureBroadcaster(request);
|
|
7902
8540
|
reply.hijack();
|
|
7903
8541
|
setupSse(request, reply.raw);
|
|
7904
|
-
if (
|
|
7905
|
-
writeSse(reply.raw, "unsupported",
|
|
8542
|
+
if ("unsupported" in resolved) {
|
|
8543
|
+
writeSse(reply.raw, "unsupported", resolved.unsupported);
|
|
7906
8544
|
reply.raw.end();
|
|
7907
8545
|
return;
|
|
7908
8546
|
}
|
|
@@ -7913,7 +8551,10 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7913
8551
|
try {
|
|
7914
8552
|
sub = entry.broadcaster.subscribe(
|
|
7915
8553
|
(env) => writeChange(reply.raw, env),
|
|
7916
|
-
|
|
8554
|
+
{
|
|
8555
|
+
...lastSeenSeq != null ? { lastSeenSeq } : {},
|
|
8556
|
+
onResyncRequired: () => writeSse(reply.raw, "resync-required", {})
|
|
8557
|
+
}
|
|
7917
8558
|
);
|
|
7918
8559
|
} catch (error) {
|
|
7919
8560
|
releaseBroadcaster(workspaceId, entry);
|
|
@@ -7924,10 +8565,6 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7924
8565
|
}
|
|
7925
8566
|
if (sub.resyncRequired) {
|
|
7926
8567
|
writeSse(reply.raw, "resync-required", {});
|
|
7927
|
-
sub.unsubscribe();
|
|
7928
|
-
releaseBroadcaster(workspaceId, entry);
|
|
7929
|
-
reply.raw.end();
|
|
7930
|
-
return;
|
|
7931
8568
|
}
|
|
7932
8569
|
for (const env of sub.replay) writeChange(reply.raw, env);
|
|
7933
8570
|
const heartbeat = setInterval(() => {
|
|
@@ -8128,8 +8765,8 @@ function skillsRoutes(app, opts, done) {
|
|
|
8128
8765
|
const workspaceRoot = opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(request) : opts.workspaceRoot;
|
|
8129
8766
|
const additionalSkillPaths = opts.getAdditionalSkillPaths ? await opts.getAdditionalSkillPaths(request) : opts.additionalSkillPaths;
|
|
8130
8767
|
const piPackages = opts.getPiPackages ? await opts.getPiPackages(request) : opts.piPackages;
|
|
8131
|
-
const noSkills = opts.getNoSkills ? await opts.getNoSkills(request) : opts.noSkills;
|
|
8132
|
-
const cacheKey = JSON.stringify([workspaceRoot, additionalSkillPaths ?? [], piPackages ?? [],
|
|
8768
|
+
const noSkills = (opts.getNoSkills ? await opts.getNoSkills(request) : opts.noSkills) ?? withPiHarnessDefaults().noSkills;
|
|
8769
|
+
const cacheKey = JSON.stringify([workspaceRoot, additionalSkillPaths ?? [], piPackages ?? [], noSkills]);
|
|
8133
8770
|
const now = Date.now();
|
|
8134
8771
|
for (const [key, entry] of cached) {
|
|
8135
8772
|
if (entry.expiresAt <= now) cached.delete(key);
|
|
@@ -8158,7 +8795,7 @@ function skillsRoutes(app, opts, done) {
|
|
|
8158
8795
|
cwd: workspaceRoot,
|
|
8159
8796
|
agentDir,
|
|
8160
8797
|
skillPaths: [...packageSkillPaths, ...additionalSkillPaths ?? []],
|
|
8161
|
-
includeDefaults:
|
|
8798
|
+
includeDefaults: !noSkills
|
|
8162
8799
|
});
|
|
8163
8800
|
const skills = result.skills.map((s) => ({
|
|
8164
8801
|
name: s.name,
|
|
@@ -8567,6 +9204,7 @@ function statusCodeFromError(err) {
|
|
|
8567
9204
|
}
|
|
8568
9205
|
const parsedCode = ErrorCode.safeParse(err?.code);
|
|
8569
9206
|
if (parsedCode.success && parsedCode.data === ErrorCode.enum.SESSION_NOT_FOUND) return 404;
|
|
9207
|
+
if (parsedCode.success && parsedCode.data === ErrorCode.enum.PAYMENT_REQUIRED) return 402;
|
|
8570
9208
|
return 500;
|
|
8571
9209
|
}
|
|
8572
9210
|
|
|
@@ -9012,9 +9650,10 @@ function gitRoutes(app, opts, done) {
|
|
|
9012
9650
|
if (path4 === null) return;
|
|
9013
9651
|
const workspaceRoot = await resolveWorkspaceRoot(request);
|
|
9014
9652
|
if (!workspaceRoot) {
|
|
9015
|
-
return
|
|
9016
|
-
|
|
9017
|
-
|
|
9653
|
+
return {
|
|
9654
|
+
enabled: false,
|
|
9655
|
+
reason: "Git file URLs are unavailable for this runtime."
|
|
9656
|
+
};
|
|
9018
9657
|
}
|
|
9019
9658
|
try {
|
|
9020
9659
|
return await resolveGitFileUrl(workspaceRoot, path4);
|
|
@@ -9406,7 +10045,9 @@ var PiChatEventMapper = class {
|
|
|
9406
10045
|
const mapped = [
|
|
9407
10046
|
...this.mapAgentEndFinalAssistant(event, turnId),
|
|
9408
10047
|
...this.mapAgentEndError(event, turnId, status),
|
|
9409
|
-
|
|
10048
|
+
// willRetry marks a non-terminal end (auto-retry coming) so once-per-settle
|
|
10049
|
+
// consumers can ignore it; mirrors mapAgentEndError's own willRetry gate.
|
|
10050
|
+
this.event({ type: "agent-end", turnId, status, ...event.willRetry === true ? { willRetry: true } : {} })
|
|
9410
10051
|
];
|
|
9411
10052
|
this.activeAssistantMessageId = void 0;
|
|
9412
10053
|
this.toolCallMessageIds.clear();
|
|
@@ -10105,6 +10746,454 @@ function messageCreatedAtMs(message) {
|
|
|
10105
10746
|
return Number.isFinite(timestamp) ? timestamp : void 0;
|
|
10106
10747
|
}
|
|
10107
10748
|
|
|
10749
|
+
// src/server/pi-chat/metering.ts
|
|
10750
|
+
var meteringLogger = createLogger("pi-chat-metering");
|
|
10751
|
+
var defaultMeteringErrorLogger = (message, error) => {
|
|
10752
|
+
meteringLogger.warn(message, { error });
|
|
10753
|
+
};
|
|
10754
|
+
function promptRunId(sessionId, clientNonce) {
|
|
10755
|
+
return `pi-run:${sessionId}:prompt:${clientNonce}`;
|
|
10756
|
+
}
|
|
10757
|
+
function followUpRunId(sessionId, clientNonce, clientSeq) {
|
|
10758
|
+
return `pi-run:${sessionId}:followup:${clientNonce}:${clientSeq}`;
|
|
10759
|
+
}
|
|
10760
|
+
function followUpMatches(run2, selector) {
|
|
10761
|
+
const fu = run2.followUp;
|
|
10762
|
+
if (!fu) return false;
|
|
10763
|
+
if (selector.clientNonce !== void 0) return fu.clientNonce === selector.clientNonce;
|
|
10764
|
+
if (selector.clientSeq !== void 0) return fu.clientSeq === selector.clientSeq;
|
|
10765
|
+
return false;
|
|
10766
|
+
}
|
|
10767
|
+
function takeQueuedFollowUp(state, selector) {
|
|
10768
|
+
const index = state.queued.findIndex((run2) => followUpMatches(run2, selector));
|
|
10769
|
+
if (index < 0) return void 0;
|
|
10770
|
+
return state.queued.splice(index, 1)[0];
|
|
10771
|
+
}
|
|
10772
|
+
function isRecord3(value) {
|
|
10773
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10774
|
+
}
|
|
10775
|
+
function readTokenCount(value) {
|
|
10776
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
10777
|
+
}
|
|
10778
|
+
function normalizeMeteringUsage(value) {
|
|
10779
|
+
if (!isRecord3(value)) return void 0;
|
|
10780
|
+
const cost = isRecord3(value.cost) ? value.cost : {};
|
|
10781
|
+
return {
|
|
10782
|
+
input: readTokenCount(value.input),
|
|
10783
|
+
output: readTokenCount(value.output),
|
|
10784
|
+
cacheRead: readTokenCount(value.cacheRead),
|
|
10785
|
+
cacheWrite: readTokenCount(value.cacheWrite),
|
|
10786
|
+
cost: {
|
|
10787
|
+
input: readTokenCount(cost.input),
|
|
10788
|
+
output: readTokenCount(cost.output),
|
|
10789
|
+
cacheRead: readTokenCount(cost.cacheRead),
|
|
10790
|
+
cacheWrite: readTokenCount(cost.cacheWrite),
|
|
10791
|
+
total: readTokenCount(cost.total)
|
|
10792
|
+
}
|
|
10793
|
+
};
|
|
10794
|
+
}
|
|
10795
|
+
var PiChatMeteringCoordinator = class {
|
|
10796
|
+
sessions = /* @__PURE__ */ new Map();
|
|
10797
|
+
inflightOps = /* @__PURE__ */ new Set();
|
|
10798
|
+
runInstances = 0;
|
|
10799
|
+
sink;
|
|
10800
|
+
logError;
|
|
10801
|
+
constructor(sink, logError) {
|
|
10802
|
+
this.sink = sink;
|
|
10803
|
+
this.logError = logError ?? defaultMeteringErrorLogger;
|
|
10804
|
+
}
|
|
10805
|
+
/** Reserve a prompt run. Throws (fail closed) when the sink rejects. */
|
|
10806
|
+
async reservePrompt(input) {
|
|
10807
|
+
const state = this.sessionState(input.sessionId);
|
|
10808
|
+
const runId = promptRunId(input.sessionId, input.clientNonce);
|
|
10809
|
+
const existing = this.findRun(state, runId);
|
|
10810
|
+
if (existing) {
|
|
10811
|
+
await existing.reservation;
|
|
10812
|
+
return "duplicate";
|
|
10813
|
+
}
|
|
10814
|
+
const run2 = this.createRun(input, "prompt", runId);
|
|
10815
|
+
state.pendingPrompts.push(run2);
|
|
10816
|
+
return this.materializeReservation(state, run2, input);
|
|
10817
|
+
}
|
|
10818
|
+
/** Reserve a follow-up run. Throws (fail closed) when the sink rejects.
|
|
10819
|
+
* Returns 'duplicate' when this selector already has a tracked/consumed run. */
|
|
10820
|
+
async reserveFollowUp(input) {
|
|
10821
|
+
const state = this.sessionState(input.sessionId);
|
|
10822
|
+
const runId = followUpRunId(input.sessionId, input.clientNonce, input.clientSeq);
|
|
10823
|
+
const existing = this.findRun(state, runId);
|
|
10824
|
+
if (existing) {
|
|
10825
|
+
await existing.reservation;
|
|
10826
|
+
return "duplicate";
|
|
10827
|
+
}
|
|
10828
|
+
if (state.consumedFollowUpNonces.has(input.clientNonce)) return "duplicate";
|
|
10829
|
+
const run2 = this.createRun(input, "followup", runId);
|
|
10830
|
+
run2.followUp = { clientNonce: input.clientNonce, clientSeq: input.clientSeq };
|
|
10831
|
+
state.queued.push(run2);
|
|
10832
|
+
return this.materializeReservation(state, run2, input);
|
|
10833
|
+
}
|
|
10834
|
+
/**
|
|
10835
|
+
* Acquire the reservation for a freshly-registered run. The reserve is put on
|
|
10836
|
+
* the run's ops chain so a concurrent release/settle is ordered strictly
|
|
10837
|
+
* after the reservation row exists. Returns 'cancelled' (skip execution) when
|
|
10838
|
+
* a concurrent stop/interrupt/delete terminated the run while the reserve was
|
|
10839
|
+
* in flight; throws (fail closed) when the sink rejects.
|
|
10840
|
+
*/
|
|
10841
|
+
async materializeReservation(state, run2, input) {
|
|
10842
|
+
run2.reservation = this.applyReservation(run2, input);
|
|
10843
|
+
const reserveOp = run2.reservation.catch(() => {
|
|
10844
|
+
});
|
|
10845
|
+
run2.ops = reserveOp;
|
|
10846
|
+
this.inflightOps.add(reserveOp);
|
|
10847
|
+
void reserveOp.finally(() => this.inflightOps.delete(reserveOp));
|
|
10848
|
+
try {
|
|
10849
|
+
await run2.reservation;
|
|
10850
|
+
} catch (err) {
|
|
10851
|
+
this.removeReservingRun(state, run2, input.sessionId);
|
|
10852
|
+
throw err;
|
|
10853
|
+
}
|
|
10854
|
+
return run2.terminal ? "cancelled" : "created";
|
|
10855
|
+
}
|
|
10856
|
+
/** True while a non-terminal prompt run exists for this nonce (accept →
|
|
10857
|
+
* agent-end). Used to suppress duplicate-nonce execution for the full run
|
|
10858
|
+
* lifetime, not just until the user message-start is consumed. */
|
|
10859
|
+
hasPromptRun(sessionId, clientNonce) {
|
|
10860
|
+
const state = this.sessions.get(sessionId);
|
|
10861
|
+
if (!state) return false;
|
|
10862
|
+
const run2 = this.findRun(state, promptRunId(sessionId, clientNonce));
|
|
10863
|
+
return run2 !== void 0 && !run2.terminal;
|
|
10864
|
+
}
|
|
10865
|
+
/** True while a non-terminal follow-up run exists for this selector (queued
|
|
10866
|
+
* or consumed-and-active). Used to suppress duplicate follow-up enqueues. */
|
|
10867
|
+
hasFollowUpRun(sessionId, selector) {
|
|
10868
|
+
const state = this.sessions.get(sessionId);
|
|
10869
|
+
if (!state) return false;
|
|
10870
|
+
if (selector.clientNonce !== void 0 && state.consumedFollowUpNonces.has(selector.clientNonce)) return true;
|
|
10871
|
+
if (state.queued.some((run2) => followUpMatches(run2, selector))) return true;
|
|
10872
|
+
const active = state.active;
|
|
10873
|
+
return active !== void 0 && !active.terminal && active.followUp !== void 0 && followUpMatches(active, selector);
|
|
10874
|
+
}
|
|
10875
|
+
/** The accepted prompt failed before/without running (sync throw or run rejection). */
|
|
10876
|
+
failPromptRun(sessionId, clientNonce) {
|
|
10877
|
+
const state = this.sessions.get(sessionId);
|
|
10878
|
+
if (!state) return;
|
|
10879
|
+
const runId = promptRunId(sessionId, clientNonce);
|
|
10880
|
+
const pendingIndex = state.pendingPrompts.findIndex((run2) => run2.scope.runId === runId);
|
|
10881
|
+
if (pendingIndex >= 0) {
|
|
10882
|
+
const [run2] = state.pendingPrompts.splice(pendingIndex, 1);
|
|
10883
|
+
if (run2) this.finishRun(run2, "error");
|
|
10884
|
+
} else if (state.active?.scope.runId === runId) {
|
|
10885
|
+
this.finishRun(state.active, "error");
|
|
10886
|
+
state.active = void 0;
|
|
10887
|
+
}
|
|
10888
|
+
this.pruneSession(sessionId, state);
|
|
10889
|
+
}
|
|
10890
|
+
/** A queued follow-up was rejected by the adapter before being queued. */
|
|
10891
|
+
failFollowUpRun(sessionId, selector) {
|
|
10892
|
+
const state = this.sessions.get(sessionId);
|
|
10893
|
+
if (!state) return;
|
|
10894
|
+
const run2 = takeQueuedFollowUp(state, selector);
|
|
10895
|
+
if (!run2) return;
|
|
10896
|
+
this.release(run2, "run-rejected");
|
|
10897
|
+
this.pruneSession(sessionId, state);
|
|
10898
|
+
}
|
|
10899
|
+
/**
|
|
10900
|
+
* A queued follow-up is being re-posted as a plain prompt (interrupt
|
|
10901
|
+
* fallback for runtimes without continueQueuedFollowUp). No
|
|
10902
|
+
* `followup-consumed` event will arrive, so bind its reservation to the
|
|
10903
|
+
* next agent-start instead.
|
|
10904
|
+
*/
|
|
10905
|
+
promoteQueuedToPrompt(sessionId, selector) {
|
|
10906
|
+
const state = this.sessions.get(sessionId);
|
|
10907
|
+
if (!state) return;
|
|
10908
|
+
const run2 = takeQueuedFollowUp(state, selector);
|
|
10909
|
+
if (!run2) return;
|
|
10910
|
+
state.pendingPrompts.push(run2);
|
|
10911
|
+
}
|
|
10912
|
+
/**
|
|
10913
|
+
* A promoted-to-prompt follow-up failed before agent-start (the fallback
|
|
10914
|
+
* repost rejected). Release its reservation instead of stranding it in
|
|
10915
|
+
* pendingPrompts, where a later agent-start would otherwise misattribute
|
|
10916
|
+
* usage to it.
|
|
10917
|
+
*/
|
|
10918
|
+
failPromotedFollowUp(sessionId, selector) {
|
|
10919
|
+
const state = this.sessions.get(sessionId);
|
|
10920
|
+
if (!state || selector.clientNonce === void 0 || selector.clientSeq === void 0) return;
|
|
10921
|
+
const runId = followUpRunId(sessionId, selector.clientNonce, selector.clientSeq);
|
|
10922
|
+
const index = state.pendingPrompts.findIndex((run3) => run3.scope.runId === runId);
|
|
10923
|
+
if (index < 0) return;
|
|
10924
|
+
const [run2] = state.pendingPrompts.splice(index, 1);
|
|
10925
|
+
if (run2) this.release(run2, "run-rejected");
|
|
10926
|
+
this.pruneSession(sessionId, state);
|
|
10927
|
+
}
|
|
10928
|
+
/**
|
|
10929
|
+
* Release prompt runs reserved but not yet bound to an agent-start —
|
|
10930
|
+
* e.g. a stop/interrupt landing in the window between acceptance and the
|
|
10931
|
+
* native agent_start. Without this they would sit `active` in the store
|
|
10932
|
+
* until their TTL, holding the user's balance. No charge.
|
|
10933
|
+
*/
|
|
10934
|
+
releasePending(sessionId) {
|
|
10935
|
+
const state = this.sessions.get(sessionId);
|
|
10936
|
+
if (!state) return;
|
|
10937
|
+
for (const run2 of state.pendingPrompts) this.release(run2, "cancelled");
|
|
10938
|
+
state.pendingPrompts = [];
|
|
10939
|
+
this.pruneSession(sessionId, state);
|
|
10940
|
+
}
|
|
10941
|
+
/** Queue cleared via selector or entirely; release affected reservations. */
|
|
10942
|
+
releaseQueued(sessionId, selector) {
|
|
10943
|
+
const state = this.sessions.get(sessionId);
|
|
10944
|
+
if (!state) return;
|
|
10945
|
+
if (selector) {
|
|
10946
|
+
const run2 = takeQueuedFollowUp(state, selector);
|
|
10947
|
+
if (run2) this.release(run2, "queue-cleared");
|
|
10948
|
+
} else {
|
|
10949
|
+
for (const run2 of state.queued) this.release(run2, "queue-cleared");
|
|
10950
|
+
state.queued = [];
|
|
10951
|
+
}
|
|
10952
|
+
this.pruneSession(sessionId, state);
|
|
10953
|
+
}
|
|
10954
|
+
/** Session deleted: tear down every non-terminal run without charging. */
|
|
10955
|
+
releaseSession(sessionId) {
|
|
10956
|
+
const state = this.sessions.get(sessionId);
|
|
10957
|
+
if (!state) return;
|
|
10958
|
+
for (const run2 of state.queued) this.release(run2, "cancelled");
|
|
10959
|
+
for (const run2 of state.pendingPrompts) this.release(run2, "cancelled");
|
|
10960
|
+
if (state.active) this.finishRun(state.active, "aborted");
|
|
10961
|
+
this.sessions.delete(sessionId);
|
|
10962
|
+
}
|
|
10963
|
+
/**
|
|
10964
|
+
* Feed one native adapter event and its mapped PiChatEvents through the
|
|
10965
|
+
* correlation state machine. Must be called after the mapped events were
|
|
10966
|
+
* published (the mapper assigns turn ids during mapping).
|
|
10967
|
+
*/
|
|
10968
|
+
observe(sessionId, nativeEvent, mappedEvents) {
|
|
10969
|
+
const state = this.sessions.get(sessionId);
|
|
10970
|
+
if (!state) return;
|
|
10971
|
+
for (const event of mappedEvents) {
|
|
10972
|
+
switch (event.type) {
|
|
10973
|
+
case "agent-start": {
|
|
10974
|
+
let next = state.pendingPrompts.shift();
|
|
10975
|
+
while (next && next.terminal) next = state.pendingPrompts.shift();
|
|
10976
|
+
if (next) {
|
|
10977
|
+
if (state.active && !state.active.terminal) this.finishRun(state.active, "ok");
|
|
10978
|
+
state.active = next;
|
|
10979
|
+
}
|
|
10980
|
+
break;
|
|
10981
|
+
}
|
|
10982
|
+
case "message-start": {
|
|
10983
|
+
if (event.role === "user" && event.clientSeq !== void 0) {
|
|
10984
|
+
this.consumeFollowUp(state, event);
|
|
10985
|
+
}
|
|
10986
|
+
break;
|
|
10987
|
+
}
|
|
10988
|
+
case "followup-consumed": {
|
|
10989
|
+
this.consumeFollowUp(state, event);
|
|
10990
|
+
break;
|
|
10991
|
+
}
|
|
10992
|
+
case "auto-retry-start": {
|
|
10993
|
+
if (state.active) {
|
|
10994
|
+
state.active.recordedMessageIds.clear();
|
|
10995
|
+
state.active.lastIdlessUsageKey = void 0;
|
|
10996
|
+
}
|
|
10997
|
+
break;
|
|
10998
|
+
}
|
|
10999
|
+
case "agent-end": {
|
|
11000
|
+
this.harvestAgentEndUsage(state, nativeEvent);
|
|
11001
|
+
if (isRecord3(nativeEvent) && nativeEvent.willRetry === true) break;
|
|
11002
|
+
if (state.active && !state.active.terminal) this.finishRun(state.active, event.status);
|
|
11003
|
+
state.active = void 0;
|
|
11004
|
+
break;
|
|
11005
|
+
}
|
|
11006
|
+
default:
|
|
11007
|
+
break;
|
|
11008
|
+
}
|
|
11009
|
+
}
|
|
11010
|
+
this.observeMessageEndUsage(state, nativeEvent);
|
|
11011
|
+
this.pruneSession(sessionId, state);
|
|
11012
|
+
}
|
|
11013
|
+
/** Promote a queued follow-up to the active run, settling the previous one. */
|
|
11014
|
+
consumeFollowUp(state, selector) {
|
|
11015
|
+
const run2 = takeQueuedFollowUp(state, selector);
|
|
11016
|
+
if (!run2) return;
|
|
11017
|
+
if (run2.followUp?.clientNonce) state.consumedFollowUpNonces.add(run2.followUp.clientNonce);
|
|
11018
|
+
if (state.active && !state.active.terminal) this.finishRun(state.active, "ok");
|
|
11019
|
+
state.active = run2;
|
|
11020
|
+
}
|
|
11021
|
+
/** Test/diagnostic hook: resolves after every queued sink call settles. */
|
|
11022
|
+
async flush() {
|
|
11023
|
+
while (this.inflightOps.size > 0) {
|
|
11024
|
+
await Promise.all([...this.inflightOps]);
|
|
11025
|
+
}
|
|
11026
|
+
}
|
|
11027
|
+
observeMessageEndUsage(state, nativeEvent) {
|
|
11028
|
+
if (!isRecord3(nativeEvent) || nativeEvent.type !== "message_end") return;
|
|
11029
|
+
this.recordAssistantUsage(state, nativeEvent.message);
|
|
11030
|
+
}
|
|
11031
|
+
/**
|
|
11032
|
+
* Some failure/abort paths never emit message_end; the final assistant
|
|
11033
|
+
* message (and its usage) only rides on agent_end's messages array. Runs
|
|
11034
|
+
* with already-recorded usage dedupe via recordedMessageIds.
|
|
11035
|
+
*/
|
|
11036
|
+
harvestAgentEndUsage(state, nativeEvent) {
|
|
11037
|
+
if (!isRecord3(nativeEvent) || !Array.isArray(nativeEvent.messages)) return;
|
|
11038
|
+
for (let index = nativeEvent.messages.length - 1; index >= 0; index -= 1) {
|
|
11039
|
+
const message = nativeEvent.messages[index];
|
|
11040
|
+
if (!isRecord3(message) || message.role !== "assistant") continue;
|
|
11041
|
+
this.recordAssistantUsage(state, message, { isAgentEndFinal: true });
|
|
11042
|
+
return;
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
11045
|
+
recordAssistantUsage(state, message, opts = {}) {
|
|
11046
|
+
if (!isRecord3(message) || message.role !== "assistant") return;
|
|
11047
|
+
const usage = normalizeMeteringUsage(message.usage);
|
|
11048
|
+
if (!usage) return;
|
|
11049
|
+
const run2 = state.active ?? state.pendingPrompts[0];
|
|
11050
|
+
if (!run2 || run2.terminal) {
|
|
11051
|
+
this.logError("assistant usage arrived with no reserved run; usage not metered", { messageRole: "assistant" });
|
|
11052
|
+
return;
|
|
11053
|
+
}
|
|
11054
|
+
const messageId = typeof message.id === "string" && message.id.length > 0 ? message.id : void 0;
|
|
11055
|
+
if (messageId) {
|
|
11056
|
+
if (run2.recordedMessageIds.has(messageId)) return;
|
|
11057
|
+
run2.recordedMessageIds.add(messageId);
|
|
11058
|
+
} else {
|
|
11059
|
+
const stopReason2 = typeof message.stopReason === "string" ? message.stopReason : "";
|
|
11060
|
+
const signature = `sig:${usage.input}:${usage.output}:${usage.cacheRead}:${usage.cacheWrite}:${usage.cost.total}:${stopReason2}`;
|
|
11061
|
+
if (opts.isAgentEndFinal && signature === run2.lastIdlessUsageKey) return;
|
|
11062
|
+
run2.lastIdlessUsageKey = signature;
|
|
11063
|
+
}
|
|
11064
|
+
run2.usageCount += 1;
|
|
11065
|
+
const model = typeof message.model === "string" && message.model.length > 0 ? message.model : void 0;
|
|
11066
|
+
const provider = typeof message.provider === "string" && message.provider.length > 0 ? message.provider : void 0;
|
|
11067
|
+
const stopReason = typeof message.stopReason === "string" ? message.stopReason : void 0;
|
|
11068
|
+
const usageId = messageId ? `pi-usage:${run2.scope.sessionId}:message:${messageId}` : run2.reservationId ? `pi-usage:reservation:${run2.reservationId}:${run2.usageCount}` : `pi-usage:${run2.scope.runId}:${run2.instanceId}:${run2.usageCount}`;
|
|
11069
|
+
this.enqueue(
|
|
11070
|
+
run2,
|
|
11071
|
+
async () => {
|
|
11072
|
+
try {
|
|
11073
|
+
const result = await this.sink.recordUsage({
|
|
11074
|
+
...run2.scope,
|
|
11075
|
+
reservationId: run2.reservationId,
|
|
11076
|
+
usageId,
|
|
11077
|
+
messageId,
|
|
11078
|
+
model: model || provider ? { provider, id: model } : void 0,
|
|
11079
|
+
usage,
|
|
11080
|
+
stopReason
|
|
11081
|
+
});
|
|
11082
|
+
if (result.billedMicros > 0) run2.billableUsageCount += 1;
|
|
11083
|
+
} catch (error) {
|
|
11084
|
+
const couldBill = usage.input + usage.output + usage.cacheRead + usage.cacheWrite > 0 || usage.cost.total > 0;
|
|
11085
|
+
if (couldBill) run2.usageWriteFailed = true;
|
|
11086
|
+
throw error;
|
|
11087
|
+
}
|
|
11088
|
+
},
|
|
11089
|
+
"recordUsage failed"
|
|
11090
|
+
);
|
|
11091
|
+
}
|
|
11092
|
+
/** Build a run synchronously (no await) so it can be registered before the
|
|
11093
|
+
* reserve sink call, closing the concurrent-duplicate race. */
|
|
11094
|
+
createRun(input, kind, runId) {
|
|
11095
|
+
return {
|
|
11096
|
+
scope: {
|
|
11097
|
+
workspaceId: input.workspaceId,
|
|
11098
|
+
userId: input.userId,
|
|
11099
|
+
sessionId: input.sessionId,
|
|
11100
|
+
runId,
|
|
11101
|
+
source: "pi-chat"
|
|
11102
|
+
},
|
|
11103
|
+
kind,
|
|
11104
|
+
reservationId: void 0,
|
|
11105
|
+
instanceId: this.runInstances += 1,
|
|
11106
|
+
usageCount: 0,
|
|
11107
|
+
billableUsageCount: 0,
|
|
11108
|
+
recordedMessageIds: /* @__PURE__ */ new Set(),
|
|
11109
|
+
lastIdlessUsageKey: void 0,
|
|
11110
|
+
usageWriteFailed: false,
|
|
11111
|
+
reservation: Promise.resolve(),
|
|
11112
|
+
terminal: false,
|
|
11113
|
+
ops: Promise.resolve()
|
|
11114
|
+
};
|
|
11115
|
+
}
|
|
11116
|
+
/** Acquire the host reservation; throws (fail closed) on a rejecting sink. */
|
|
11117
|
+
async applyReservation(run2, input) {
|
|
11118
|
+
const result = await this.sink.reserveRun({
|
|
11119
|
+
...run2.scope,
|
|
11120
|
+
kind: run2.kind,
|
|
11121
|
+
message: input.message,
|
|
11122
|
+
model: input.model
|
|
11123
|
+
});
|
|
11124
|
+
run2.reservationId = result?.reservationId;
|
|
11125
|
+
}
|
|
11126
|
+
/** Remove a still-reserving run whose reservation failed, from either list. */
|
|
11127
|
+
removeReservingRun(state, run2, sessionId) {
|
|
11128
|
+
const pendingIndex = state.pendingPrompts.indexOf(run2);
|
|
11129
|
+
if (pendingIndex >= 0) state.pendingPrompts.splice(pendingIndex, 1);
|
|
11130
|
+
const queuedIndex = state.queued.indexOf(run2);
|
|
11131
|
+
if (queuedIndex >= 0) state.queued.splice(queuedIndex, 1);
|
|
11132
|
+
this.pruneSession(sessionId, state);
|
|
11133
|
+
}
|
|
11134
|
+
findRun(state, runId) {
|
|
11135
|
+
if (state.active && !state.active.terminal && state.active.scope.runId === runId) return state.active;
|
|
11136
|
+
const pending = state.pendingPrompts.find((run2) => run2.scope.runId === runId);
|
|
11137
|
+
if (pending) return pending;
|
|
11138
|
+
return state.queued.find((run2) => run2.scope.runId === runId);
|
|
11139
|
+
}
|
|
11140
|
+
finishRun(run2, status) {
|
|
11141
|
+
if (run2.terminal) return;
|
|
11142
|
+
run2.terminal = true;
|
|
11143
|
+
this.enqueue(
|
|
11144
|
+
run2,
|
|
11145
|
+
() => {
|
|
11146
|
+
if (run2.usageWriteFailed) {
|
|
11147
|
+
return this.sink.releaseRun({ ...run2.scope, reservationId: run2.reservationId, reason: "usage-write-failed" });
|
|
11148
|
+
}
|
|
11149
|
+
if (run2.billableUsageCount > 0) {
|
|
11150
|
+
return this.sink.settleRun({ ...run2.scope, reservationId: run2.reservationId, status });
|
|
11151
|
+
}
|
|
11152
|
+
const didPaidWork = status === "ok";
|
|
11153
|
+
if (didPaidWork) {
|
|
11154
|
+
return this.sink.releaseRun({ ...run2.scope, reservationId: run2.reservationId, reason: "fallback-hold-charge" });
|
|
11155
|
+
}
|
|
11156
|
+
return this.sink.releaseRun({
|
|
11157
|
+
...run2.scope,
|
|
11158
|
+
reservationId: run2.reservationId,
|
|
11159
|
+
reason: status === "error" ? "error-before-usage" : "cancelled"
|
|
11160
|
+
});
|
|
11161
|
+
},
|
|
11162
|
+
"finishRun failed"
|
|
11163
|
+
);
|
|
11164
|
+
}
|
|
11165
|
+
release(run2, reason) {
|
|
11166
|
+
if (run2.terminal) return;
|
|
11167
|
+
run2.terminal = true;
|
|
11168
|
+
this.enqueue(
|
|
11169
|
+
run2,
|
|
11170
|
+
() => this.sink.releaseRun({ ...run2.scope, reservationId: run2.reservationId, reason }),
|
|
11171
|
+
"releaseRun failed"
|
|
11172
|
+
);
|
|
11173
|
+
}
|
|
11174
|
+
enqueue(run2, op, failureMessage) {
|
|
11175
|
+
const chained = run2.ops.then(op).catch((error) => {
|
|
11176
|
+
this.logError(`${failureMessage} (run ${run2.scope.runId})`, error);
|
|
11177
|
+
});
|
|
11178
|
+
run2.ops = chained;
|
|
11179
|
+
this.inflightOps.add(chained);
|
|
11180
|
+
void chained.finally(() => this.inflightOps.delete(chained));
|
|
11181
|
+
}
|
|
11182
|
+
sessionState(sessionId) {
|
|
11183
|
+
let state = this.sessions.get(sessionId);
|
|
11184
|
+
if (!state) {
|
|
11185
|
+
state = { pendingPrompts: [], queued: [], consumedFollowUpNonces: /* @__PURE__ */ new Set() };
|
|
11186
|
+
this.sessions.set(sessionId, state);
|
|
11187
|
+
}
|
|
11188
|
+
return state;
|
|
11189
|
+
}
|
|
11190
|
+
pruneSession(sessionId, state) {
|
|
11191
|
+
if (!state.active && state.pendingPrompts.length === 0 && state.queued.length === 0 && state.consumedFollowUpNonces.size === 0) {
|
|
11192
|
+
this.sessions.delete(sessionId);
|
|
11193
|
+
}
|
|
11194
|
+
}
|
|
11195
|
+
};
|
|
11196
|
+
|
|
10108
11197
|
// src/server/pi-chat/harnessPiChatService.ts
|
|
10109
11198
|
var HarnessPiChatService = class {
|
|
10110
11199
|
harness;
|
|
@@ -10120,10 +11209,16 @@ var HarnessPiChatService = class {
|
|
|
10120
11209
|
activePromptRuns = /* @__PURE__ */ new Map();
|
|
10121
11210
|
syntheticPromptFailures = /* @__PURE__ */ new Map();
|
|
10122
11211
|
activeSyntheticPromptErrors = /* @__PURE__ */ new Map();
|
|
11212
|
+
metering;
|
|
10123
11213
|
constructor(options) {
|
|
10124
11214
|
this.harness = options.harness;
|
|
10125
11215
|
this.sessionStore = options.sessionStore;
|
|
10126
11216
|
this.workdir = options.workdir;
|
|
11217
|
+
this.metering = options.metering ? new PiChatMeteringCoordinator(options.metering, options.meteringLogger) : void 0;
|
|
11218
|
+
}
|
|
11219
|
+
/** Test/diagnostic hook: resolves once queued metering sink calls settle. */
|
|
11220
|
+
async flushMetering() {
|
|
11221
|
+
await this.metering?.flush();
|
|
10127
11222
|
}
|
|
10128
11223
|
async listSessions(ctx, options) {
|
|
10129
11224
|
return this.sessionStore.list(toSessionCtx(ctx), options);
|
|
@@ -10132,8 +11227,16 @@ var HarnessPiChatService = class {
|
|
|
10132
11227
|
return this.sessionStore.create(toSessionCtx(ctx), init);
|
|
10133
11228
|
}
|
|
10134
11229
|
async deleteSession(ctx, sessionId) {
|
|
10135
|
-
this.channels.get(sessionId)
|
|
11230
|
+
const channel = this.channels.get(sessionId);
|
|
11231
|
+
if (channel) {
|
|
11232
|
+
const activeRun = this.activePromptRuns.get(sessionId);
|
|
11233
|
+
await channel.adapter.abort();
|
|
11234
|
+
await activeRun?.catch(() => {
|
|
11235
|
+
});
|
|
11236
|
+
}
|
|
11237
|
+
channel?.unsubscribe();
|
|
10136
11238
|
this.channels.delete(sessionId);
|
|
11239
|
+
this.metering?.releaseSession(sessionId);
|
|
10137
11240
|
this.messageMetadata.clearSession(sessionId);
|
|
10138
11241
|
this.syntheticPromptFailures.delete(sessionId);
|
|
10139
11242
|
this.activeSyntheticPromptErrors.delete(sessionId);
|
|
@@ -10183,16 +11286,35 @@ var HarnessPiChatService = class {
|
|
|
10183
11286
|
async prompt(ctx, sessionId, payload) {
|
|
10184
11287
|
const adapter = await this.getAdapter(ctx, sessionId, payload);
|
|
10185
11288
|
await this.ensureChannel(ctx, sessionId, adapter);
|
|
11289
|
+
const outcome = await this.metering?.reservePrompt({
|
|
11290
|
+
workspaceId: ctx.workspaceId,
|
|
11291
|
+
userId: ctx.authSubject,
|
|
11292
|
+
sessionId,
|
|
11293
|
+
clientNonce: payload.clientNonce,
|
|
11294
|
+
message: payload.message,
|
|
11295
|
+
model: payload.model
|
|
11296
|
+
}) ?? "created";
|
|
11297
|
+
if (outcome === "duplicate") {
|
|
11298
|
+
return {
|
|
11299
|
+
accepted: true,
|
|
11300
|
+
cursor: this.channels.get(sessionId)?.buffer.latestSeq ?? 0,
|
|
11301
|
+
clientNonce: payload.clientNonce,
|
|
11302
|
+
duplicate: true
|
|
11303
|
+
};
|
|
11304
|
+
}
|
|
11305
|
+
if (outcome === "cancelled") throw promptCancelledError();
|
|
10186
11306
|
this.messageMetadata.recordPrompt(sessionId, payload);
|
|
10187
11307
|
const channel = this.channels.get(sessionId);
|
|
10188
11308
|
const receiptCursor = nextPromptReceiptCursor(channel);
|
|
10189
11309
|
try {
|
|
10190
11310
|
const run2 = this.trackActiveRun(sessionId, adapter.prompt(toPiPromptInput(payload)));
|
|
10191
11311
|
run2.catch((error) => {
|
|
11312
|
+
this.metering?.failPromptRun(sessionId, payload.clientNonce);
|
|
10192
11313
|
if (!this.messageMetadata.hasPrompt(sessionId, { clientNonce: payload.clientNonce, displayText: payload.displayMessage ?? payload.message })) return;
|
|
10193
11314
|
this.publishPromptRunError(sessionId, channel, payload, error);
|
|
10194
11315
|
});
|
|
10195
11316
|
} catch (err) {
|
|
11317
|
+
this.metering?.failPromptRun(sessionId, payload.clientNonce);
|
|
10196
11318
|
this.messageMetadata.removePrompt(sessionId, { clientNonce: payload.clientNonce });
|
|
10197
11319
|
throw err;
|
|
10198
11320
|
}
|
|
@@ -10201,6 +11323,25 @@ var HarnessPiChatService = class {
|
|
|
10201
11323
|
async followUp(ctx, sessionId, payload) {
|
|
10202
11324
|
const adapter = await this.getAdapter(ctx, sessionId, payload.message);
|
|
10203
11325
|
await this.ensureChannel(ctx, sessionId, adapter);
|
|
11326
|
+
const outcome = await this.metering?.reserveFollowUp({
|
|
11327
|
+
workspaceId: ctx.workspaceId,
|
|
11328
|
+
userId: ctx.authSubject,
|
|
11329
|
+
sessionId,
|
|
11330
|
+
clientNonce: payload.clientNonce,
|
|
11331
|
+
clientSeq: payload.clientSeq,
|
|
11332
|
+
message: payload.message
|
|
11333
|
+
}) ?? "created";
|
|
11334
|
+
if (outcome === "duplicate") {
|
|
11335
|
+
return {
|
|
11336
|
+
accepted: true,
|
|
11337
|
+
queued: true,
|
|
11338
|
+
cursor: this.channels.get(sessionId)?.buffer.latestSeq ?? 0,
|
|
11339
|
+
clientNonce: payload.clientNonce,
|
|
11340
|
+
clientSeq: payload.clientSeq,
|
|
11341
|
+
duplicate: true
|
|
11342
|
+
};
|
|
11343
|
+
}
|
|
11344
|
+
if (outcome === "cancelled") throw promptCancelledError();
|
|
10204
11345
|
this.messageMetadata.recordFollowUp(sessionId, payload);
|
|
10205
11346
|
try {
|
|
10206
11347
|
await adapter.followUp(payload.message, {
|
|
@@ -10209,6 +11350,7 @@ var HarnessPiChatService = class {
|
|
|
10209
11350
|
clientSeq: payload.clientSeq
|
|
10210
11351
|
});
|
|
10211
11352
|
} catch (err) {
|
|
11353
|
+
this.metering?.failFollowUpRun(sessionId, payload);
|
|
10212
11354
|
this.messageMetadata.removeFollowUp(sessionId, payload);
|
|
10213
11355
|
throw err;
|
|
10214
11356
|
}
|
|
@@ -10220,10 +11362,14 @@ var HarnessPiChatService = class {
|
|
|
10220
11362
|
const before = adapter.readSnapshot().followUpMessages.length;
|
|
10221
11363
|
adapter.clearFollowUp(payload);
|
|
10222
11364
|
const after = adapter.readSnapshot().followUpMessages.length;
|
|
10223
|
-
if (after < before)
|
|
11365
|
+
if (after < before) {
|
|
11366
|
+
this.messageMetadata.removeFollowUp(sessionId, payload);
|
|
11367
|
+
this.metering?.releaseQueued(sessionId, payload);
|
|
11368
|
+
}
|
|
10224
11369
|
return { accepted: true, cursor: this.channels.get(sessionId)?.buffer.latestSeq ?? 0, cleared: Math.max(0, before - after) };
|
|
10225
11370
|
}
|
|
10226
11371
|
const clearedQueue = this.clearAllFollowUps(adapter, sessionId);
|
|
11372
|
+
this.metering?.releaseQueued(sessionId);
|
|
10227
11373
|
return { accepted: true, cursor: this.channels.get(sessionId)?.buffer.latestSeq ?? 0, cleared: clearedQueue.length };
|
|
10228
11374
|
}
|
|
10229
11375
|
async interrupt(ctx, sessionId, _payload) {
|
|
@@ -10236,12 +11382,15 @@ var HarnessPiChatService = class {
|
|
|
10236
11382
|
if (wasActive) await adapter.abort();
|
|
10237
11383
|
await activeRun?.catch(() => {
|
|
10238
11384
|
});
|
|
11385
|
+
this.metering?.releasePending(sessionId);
|
|
10239
11386
|
if (nextFollowUp) await this.autoPostInterruptedFollowUp(sessionId, adapter, nextFollowUp);
|
|
10240
11387
|
return { accepted: true, cursor: this.channels.get(sessionId)?.buffer.latestSeq ?? 0 };
|
|
10241
11388
|
}
|
|
10242
11389
|
async stop(ctx, sessionId, _payload) {
|
|
10243
11390
|
const adapter = await this.getAdapter(ctx, sessionId, "");
|
|
10244
11391
|
const clearedQueue = this.clearAllFollowUps(adapter, sessionId);
|
|
11392
|
+
this.metering?.releaseQueued(sessionId);
|
|
11393
|
+
this.metering?.releasePending(sessionId);
|
|
10245
11394
|
await adapter.abort();
|
|
10246
11395
|
return { accepted: true, stopped: true, cursor: this.channels.get(sessionId)?.buffer.latestSeq ?? 0, clearedQueue: buildPiChatQueuedFollowUps(sessionId, clearedQueue) };
|
|
10247
11396
|
}
|
|
@@ -10263,13 +11412,24 @@ var HarnessPiChatService = class {
|
|
|
10263
11412
|
const metadata = this.messageMetadata.findFollowUpForQueueItem(sessionId, followUp);
|
|
10264
11413
|
this.messageMetadata.recordConsumingFollowUp(sessionId, followUp, metadata?.serverText);
|
|
10265
11414
|
if (adapter.continueQueuedFollowUp) {
|
|
10266
|
-
|
|
11415
|
+
try {
|
|
11416
|
+
await this.trackActiveRun(sessionId, adapter.continueQueuedFollowUp());
|
|
11417
|
+
} catch (err) {
|
|
11418
|
+
this.metering?.failFollowUpRun(sessionId, followUp);
|
|
11419
|
+
throw err;
|
|
11420
|
+
}
|
|
10267
11421
|
return;
|
|
10268
11422
|
}
|
|
10269
11423
|
if (!this.canClearAutoPostedFollowUpForFallback(adapter, followUp)) {
|
|
10270
11424
|
throw new AutoPostFollowUpError("Cannot auto-post queued follow-up because this runtime cannot safely remove only the consumed queued item.");
|
|
10271
11425
|
}
|
|
10272
|
-
|
|
11426
|
+
this.metering?.promoteQueuedToPrompt(sessionId, followUp);
|
|
11427
|
+
try {
|
|
11428
|
+
await this.runPrompt(sessionId, adapter, metadata?.serverText ?? followUp.displayText);
|
|
11429
|
+
} catch (err) {
|
|
11430
|
+
this.metering?.failPromotedFollowUp(sessionId, followUp);
|
|
11431
|
+
throw err;
|
|
11432
|
+
}
|
|
10273
11433
|
this.clearAutoPostedFollowUpForFallback(sessionId, adapter, followUp);
|
|
10274
11434
|
}
|
|
10275
11435
|
async runPrompt(sessionId, adapter, input) {
|
|
@@ -10414,10 +11574,14 @@ var HarnessPiChatService = class {
|
|
|
10414
11574
|
const channel = { buffer, adapter, unsubscribe: () => {
|
|
10415
11575
|
}, mapper, messageTurnIds: /* @__PURE__ */ new Map() };
|
|
10416
11576
|
const unsubscribe = adapter.subscribe((event) => {
|
|
10417
|
-
|
|
11577
|
+
const mappedEvents = mapper.map(event);
|
|
11578
|
+
const enrichedEvents = [];
|
|
11579
|
+
for (const mapped of mappedEvents) {
|
|
10418
11580
|
const enriched = this.messageMetadata.enrichEvent(sessionId, mapped);
|
|
11581
|
+
enrichedEvents.push(enriched);
|
|
10419
11582
|
this.publishChannelEvent(sessionId, channel, enriched);
|
|
10420
11583
|
}
|
|
11584
|
+
this.metering?.observe(sessionId, event, enrichedEvents);
|
|
10421
11585
|
});
|
|
10422
11586
|
channel.unsubscribe = unsubscribe;
|
|
10423
11587
|
this.channels.set(sessionId, channel);
|
|
@@ -10426,6 +11590,13 @@ var HarnessPiChatService = class {
|
|
|
10426
11590
|
};
|
|
10427
11591
|
var AutoPostFollowUpError = class extends Error {
|
|
10428
11592
|
};
|
|
11593
|
+
function promptCancelledError() {
|
|
11594
|
+
return Object.assign(new Error("request cancelled before execution"), {
|
|
11595
|
+
statusCode: 409,
|
|
11596
|
+
code: ErrorCode.enum.ABORTED,
|
|
11597
|
+
retryable: true
|
|
11598
|
+
});
|
|
11599
|
+
}
|
|
10429
11600
|
function nextPromptReceiptCursor(channel) {
|
|
10430
11601
|
return (channel?.buffer.latestSeq ?? 0) + 1;
|
|
10431
11602
|
}
|
|
@@ -10558,7 +11729,8 @@ async function createAgentApp(opts = {}) {
|
|
|
10558
11729
|
templatePath
|
|
10559
11730
|
});
|
|
10560
11731
|
const pluginTools = [];
|
|
10561
|
-
|
|
11732
|
+
const externalPluginsEnabled = opts.externalPlugins !== false;
|
|
11733
|
+
if (externalPluginsEnabled && modeAdapter.workspaceFsCapability === "strong") {
|
|
10562
11734
|
const pluginResult = await loadPlugins({ cwd: workspaceRoot });
|
|
10563
11735
|
if (pluginResult.errors.length > 0) {
|
|
10564
11736
|
for (const e of pluginResult.errors) {
|
|
@@ -10571,7 +11743,7 @@ async function createAgentApp(opts = {}) {
|
|
|
10571
11743
|
}
|
|
10572
11744
|
const getRuntimeProvisioning = opts.getRuntimeProvisioning ?? (() => opts.runtimeProvisioning);
|
|
10573
11745
|
const runtimePi = {
|
|
10574
|
-
...opts.pi,
|
|
11746
|
+
...withPiHarnessDefaults(opts.pi),
|
|
10575
11747
|
additionalSkillPaths: [
|
|
10576
11748
|
...getRuntimeProvisioning()?.skillPaths ?? [],
|
|
10577
11749
|
...opts.pi?.additionalSkillPaths ?? []
|
|
@@ -10589,21 +11761,17 @@ async function createAgentApp(opts = {}) {
|
|
|
10589
11761
|
...opts.disableDefaultFileTools ? [] : buildFilesystemAgentTools(runtimeBundle),
|
|
10590
11762
|
...opts.extraTools ?? [],
|
|
10591
11763
|
...pluginTools,
|
|
10592
|
-
createPluginDiagnosticsTool({
|
|
11764
|
+
...externalPluginsEnabled ? [createPluginDiagnosticsTool({
|
|
10593
11765
|
getLastReloadDiagnostics: () => lastReloadDiagnostics,
|
|
10594
11766
|
getHarness: () => harnessRef,
|
|
10595
11767
|
...opts.getPluginDiagnostics ? {
|
|
10596
11768
|
getPluginErrors: () => opts.getPluginDiagnostics({ workspaceId: sessionId, workspaceRoot })
|
|
10597
11769
|
} : {}
|
|
10598
|
-
})
|
|
11770
|
+
})] : []
|
|
10599
11771
|
];
|
|
10600
11772
|
const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
|
|
10601
11773
|
...input,
|
|
10602
|
-
pi:
|
|
10603
|
-
noContextFiles: true,
|
|
10604
|
-
noSkills: true,
|
|
10605
|
-
...runtimePi
|
|
10606
|
-
}
|
|
11774
|
+
pi: runtimePi
|
|
10607
11775
|
}));
|
|
10608
11776
|
const harness = await harnessFactory({
|
|
10609
11777
|
tools,
|
|
@@ -10638,11 +11806,17 @@ async function createAgentApp(opts = {}) {
|
|
|
10638
11806
|
await app.register(fsEventsRoutes, { workspace: runtimeBundle.workspace });
|
|
10639
11807
|
await app.register(treeRoutes, { workspace: runtimeBundle.workspace });
|
|
10640
11808
|
await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
|
|
10641
|
-
await app.register(gitRoutes, {
|
|
11809
|
+
await app.register(gitRoutes, {
|
|
11810
|
+
getWorkspaceRoot: () => {
|
|
11811
|
+
if (runtimeBundle.sandbox.provider === "remote-worker") return void 0;
|
|
11812
|
+
return getRuntimeBundleStorageRoot(runtimeBundle);
|
|
11813
|
+
}
|
|
11814
|
+
});
|
|
10642
11815
|
const piChatService = new HarnessPiChatService({
|
|
10643
11816
|
harness,
|
|
10644
11817
|
sessionStore: harness.sessions,
|
|
10645
|
-
workdir: runtimeBundle.workspace.root
|
|
11818
|
+
workdir: runtimeBundle.workspace.root,
|
|
11819
|
+
metering: opts.metering
|
|
10646
11820
|
});
|
|
10647
11821
|
await app.register(piChatRoutes, { service: piChatService });
|
|
10648
11822
|
await app.register(systemPromptRoutes, { harness });
|
|
@@ -10739,7 +11913,7 @@ function mergeTools(options) {
|
|
|
10739
11913
|
|
|
10740
11914
|
// src/server/tools/upload/index.ts
|
|
10741
11915
|
import { readFile as readFile12 } from "fs/promises";
|
|
10742
|
-
import { extname as extname5, join as
|
|
11916
|
+
import { extname as extname5, join as join14 } from "path";
|
|
10743
11917
|
var DEFAULT_UPLOAD_DIR = "assets/images";
|
|
10744
11918
|
var MAX_UPLOAD_BYTES2 = 10 * 1024 * 1024;
|
|
10745
11919
|
function contentTypeFromExt(path4) {
|
|
@@ -10779,7 +11953,6 @@ function basenameForUpload2(filename) {
|
|
|
10779
11953
|
}
|
|
10780
11954
|
function buildUploadAgentTools(bundle) {
|
|
10781
11955
|
const { workspace } = bundle;
|
|
10782
|
-
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
10783
11956
|
return [
|
|
10784
11957
|
{
|
|
10785
11958
|
name: "upload_file",
|
|
@@ -10808,7 +11981,7 @@ function buildUploadAgentTools(bundle) {
|
|
|
10808
11981
|
const rawDir = typeof input.directory === "string" ? input.directory.trim() : "";
|
|
10809
11982
|
const dir = rawDir ? rawDir.replace(/^\.\/+/, "").replace(/\/+$/, "") : DEFAULT_UPLOAD_DIR;
|
|
10810
11983
|
try {
|
|
10811
|
-
const bytes = workspace.readBinaryFile ? Buffer.from(await workspace.readBinaryFile(filePath)) : await readFile12(
|
|
11984
|
+
const bytes = workspace.readBinaryFile ? Buffer.from(await workspace.readBinaryFile(filePath)) : await readFile12(join14(getRuntimeBundleStorageRoot(bundle), filePath));
|
|
10812
11985
|
if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES2) {
|
|
10813
11986
|
return {
|
|
10814
11987
|
content: [{ type: "text", text: `file must be between 1 byte and ${MAX_UPLOAD_BYTES2} bytes` }],
|
|
@@ -10962,6 +12135,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
10962
12135
|
});
|
|
10963
12136
|
const requestScopedRuntime = typeof opts.getWorkspaceId === "function" || typeof opts.getWorkspaceRoot === "function" || typeof opts.getTemplatePath === "function" || typeof opts.getPi === "function" || typeof opts.getSessionNamespace === "function" || typeof opts.getSystemPromptDynamic === "function";
|
|
10964
12137
|
const sessionChangesTracker = new InMemorySessionChangesTracker();
|
|
12138
|
+
const externalPluginsEnabled = opts.externalPlugins !== false;
|
|
10965
12139
|
const runtimeBindings = /* @__PURE__ */ new Map();
|
|
10966
12140
|
const MAX_RUNTIME_BINDINGS = 256;
|
|
10967
12141
|
function evictRuntimeBindings() {
|
|
@@ -10971,10 +12145,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
10971
12145
|
runtimeBindings.delete(keys[i]);
|
|
10972
12146
|
}
|
|
10973
12147
|
}
|
|
12148
|
+
async function resolveScopePi(workspaceId, root, request) {
|
|
12149
|
+
return withPiHarnessDefaults(opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot: root, request }) : opts.pi);
|
|
12150
|
+
}
|
|
10974
12151
|
async function resolveRuntimeScope(workspaceId, request) {
|
|
10975
12152
|
const root = request && opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(workspaceId, request) : workspaceRoot;
|
|
10976
12153
|
const scopedTemplatePath = opts.getTemplatePath ? await opts.getTemplatePath({ workspaceId, workspaceRoot: root, request }) : templatePath;
|
|
10977
|
-
const pi =
|
|
12154
|
+
const pi = await resolveScopePi(workspaceId, root, request);
|
|
10978
12155
|
const sessionNamespace = normalizeSessionNamespace(opts.getSessionNamespace ? await opts.getSessionNamespace({ workspaceId, workspaceRoot: root, request }) : opts.sessionNamespace);
|
|
10979
12156
|
return {
|
|
10980
12157
|
root,
|
|
@@ -10986,29 +12163,29 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
10986
12163
|
workspaceId,
|
|
10987
12164
|
root,
|
|
10988
12165
|
scopedTemplatePath ?? null,
|
|
10989
|
-
pi
|
|
12166
|
+
pi,
|
|
10990
12167
|
sessionNamespace ?? null
|
|
10991
12168
|
])
|
|
10992
12169
|
};
|
|
10993
12170
|
}
|
|
10994
12171
|
async function resolveSkillScope(workspaceId, request) {
|
|
10995
12172
|
const root = request && opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(workspaceId, request) : workspaceRoot;
|
|
10996
|
-
const pi =
|
|
10997
|
-
const hot = pi
|
|
12173
|
+
const pi = await resolveScopePi(workspaceId, root, request);
|
|
12174
|
+
const hot = pi.getHotReloadableResources?.();
|
|
10998
12175
|
return {
|
|
10999
12176
|
root,
|
|
11000
12177
|
pi: hot ? {
|
|
11001
12178
|
...pi,
|
|
11002
12179
|
additionalSkillPaths: [
|
|
11003
|
-
...pi
|
|
12180
|
+
...pi.additionalSkillPaths ?? [],
|
|
11004
12181
|
...hot.additionalSkillPaths ?? []
|
|
11005
12182
|
],
|
|
11006
12183
|
packages: [
|
|
11007
|
-
...pi
|
|
12184
|
+
...pi.packages ?? [],
|
|
11008
12185
|
...hot.packages ?? []
|
|
11009
12186
|
],
|
|
11010
12187
|
extensionPaths: [
|
|
11011
|
-
...pi
|
|
12188
|
+
...pi.extensionPaths ?? [],
|
|
11012
12189
|
...hot.extensionPaths ?? []
|
|
11013
12190
|
]
|
|
11014
12191
|
} : pi
|
|
@@ -11142,17 +12319,17 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11142
12319
|
}),
|
|
11143
12320
|
...buildFilesystemAgentTools(runtimeBundle),
|
|
11144
12321
|
...buildUploadAgentTools(runtimeBundle),
|
|
11145
|
-
createPluginDiagnosticsTool({
|
|
12322
|
+
...externalPluginsEnabled ? [createPluginDiagnosticsTool({
|
|
11146
12323
|
// `binding` is assigned later in this function; read through thunks.
|
|
11147
12324
|
getLastReloadDiagnostics: () => binding?.lastReloadDiagnostics ?? [],
|
|
11148
12325
|
getHarness: () => binding?.harness,
|
|
11149
12326
|
...opts.getPluginDiagnostics ? {
|
|
11150
12327
|
getPluginErrors: () => opts.getPluginDiagnostics({ workspaceId, workspaceRoot: root })
|
|
11151
12328
|
} : {}
|
|
11152
|
-
})
|
|
12329
|
+
})] : []
|
|
11153
12330
|
];
|
|
11154
12331
|
const pluginTools = [];
|
|
11155
|
-
if (modeAdapter.workspaceFsCapability === "strong") {
|
|
12332
|
+
if (externalPluginsEnabled && modeAdapter.workspaceFsCapability === "strong") {
|
|
11156
12333
|
const pluginResult = await loadPlugins({ cwd: root });
|
|
11157
12334
|
if (pluginResult.errors.length > 0) {
|
|
11158
12335
|
for (const e of pluginResult.errors) {
|
|
@@ -11185,14 +12362,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11185
12362
|
const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
|
|
11186
12363
|
...input,
|
|
11187
12364
|
pi: {
|
|
11188
|
-
|
|
11189
|
-
noSkills: true,
|
|
12365
|
+
// scope.pi is already defaulted at the resolveScopePi chokepoint.
|
|
11190
12366
|
...scope.pi,
|
|
11191
12367
|
additionalSkillPaths: [
|
|
11192
|
-
...scope.pi
|
|
12368
|
+
...scope.pi.additionalSkillPaths ?? []
|
|
11193
12369
|
],
|
|
11194
12370
|
getHotReloadableResources: () => {
|
|
11195
|
-
const hot = scope.pi
|
|
12371
|
+
const hot = scope.pi.getHotReloadableResources?.() ?? {};
|
|
11196
12372
|
return {
|
|
11197
12373
|
...hot,
|
|
11198
12374
|
additionalSkillPaths: [
|
|
@@ -11412,7 +12588,11 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11412
12588
|
getFileSearch: async (request) => (await getBindingForRequest(request)).runtimeBundle.fileSearch
|
|
11413
12589
|
});
|
|
11414
12590
|
await app.register(gitRoutes, {
|
|
11415
|
-
getWorkspaceRoot: async (request) =>
|
|
12591
|
+
getWorkspaceRoot: async (request) => {
|
|
12592
|
+
const bundle = (await getBindingForRequest(request)).runtimeBundle;
|
|
12593
|
+
if (bundle.sandbox.provider === "remote-worker") return void 0;
|
|
12594
|
+
return getRuntimeBundleStorageRoot(bundle);
|
|
12595
|
+
}
|
|
11416
12596
|
});
|
|
11417
12597
|
await app.register(piChatRoutes, {
|
|
11418
12598
|
getService: async (request) => {
|
|
@@ -11420,7 +12600,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11420
12600
|
binding.piChatService ??= new HarnessPiChatService({
|
|
11421
12601
|
harness: binding.harness,
|
|
11422
12602
|
sessionStore: binding.harness.sessions,
|
|
11423
|
-
workdir: binding.runtimeBundle.workspace.root
|
|
12603
|
+
workdir: binding.runtimeBundle.workspace.root,
|
|
12604
|
+
metering: opts.metering
|
|
11424
12605
|
});
|
|
11425
12606
|
return binding.piChatService;
|
|
11426
12607
|
}
|
|
@@ -11436,19 +12617,21 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11436
12617
|
...opts.pi?.additionalSkillPaths ?? []
|
|
11437
12618
|
],
|
|
11438
12619
|
piPackages: opts.pi?.packages,
|
|
12620
|
+
// Undefined is fine: skillsRoutes resolves it through the canonical
|
|
12621
|
+
// harness policy (withPiHarnessDefaults), same as the factory above.
|
|
11439
12622
|
noSkills: opts.pi?.noSkills,
|
|
11440
12623
|
getWorkspaceRoot: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).root,
|
|
11441
12624
|
getAdditionalSkillPaths: staticBinding && !hasRuntimeProvisioningInput ? void 0 : async (request) => {
|
|
11442
12625
|
const scope = await getSkillsScopeForRequest(request);
|
|
11443
|
-
if (!hasRuntimeProvisioningInput) return scope.pi
|
|
12626
|
+
if (!hasRuntimeProvisioningInput) return scope.pi.additionalSkillPaths;
|
|
11444
12627
|
const binding = await getBindingForRequest(request);
|
|
11445
12628
|
return [
|
|
11446
12629
|
...binding.runtimeProvisioning?.skillPaths ?? [],
|
|
11447
|
-
...scope.pi
|
|
12630
|
+
...scope.pi.additionalSkillPaths ?? []
|
|
11448
12631
|
];
|
|
11449
12632
|
},
|
|
11450
|
-
getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi
|
|
11451
|
-
getNoSkills: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi
|
|
12633
|
+
getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi.packages,
|
|
12634
|
+
getNoSkills: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi.noSkills
|
|
11452
12635
|
});
|
|
11453
12636
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
11454
12637
|
app.post("/api/v1/agent/reload", async (request, reply) => {
|
|
@@ -11507,10 +12690,17 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11507
12690
|
export {
|
|
11508
12691
|
FileHandleStore,
|
|
11509
12692
|
PI_PACKAGE_RESOURCE_FILTERS,
|
|
12693
|
+
REMOTE_WORKER_PROVIDER,
|
|
12694
|
+
REMOTE_WORKER_RUNTIME_CWD,
|
|
12695
|
+
RemoteWorkerClient,
|
|
12696
|
+
RemoteWorkerClientError,
|
|
11510
12697
|
UV_SETUP_COMMANDS,
|
|
11511
12698
|
VERCEL_PROVISIONING_CACHE_ROOT,
|
|
11512
12699
|
VERCEL_SANDBOX_WORKSPACE_ROOT,
|
|
11513
12700
|
UV_SETUP_COMMANDS as VERCEL_UV_SETUP_COMMANDS,
|
|
12701
|
+
WORKER_INTERNAL_TOKEN_HEADER,
|
|
12702
|
+
WORKER_REQUEST_ID_HEADER,
|
|
12703
|
+
WORKER_WORKSPACE_ID_HEADER,
|
|
11514
12704
|
applyCspHeaders,
|
|
11515
12705
|
autoDetectMode,
|
|
11516
12706
|
bakeSnapshotIfNeeded,
|
|
@@ -11518,21 +12708,28 @@ export {
|
|
|
11518
12708
|
buildPackageHash,
|
|
11519
12709
|
buildSnapshotRecipeHash,
|
|
11520
12710
|
compactPiPackages,
|
|
12711
|
+
constantTimeTokenEqual,
|
|
11521
12712
|
createAgentApp,
|
|
11522
12713
|
createBwrapSandbox,
|
|
11523
12714
|
createDirectSandbox,
|
|
11524
12715
|
createLogger,
|
|
11525
12716
|
createNodeWorkspace,
|
|
12717
|
+
createRemoteWorkerModeAdapter,
|
|
12718
|
+
createRemoteWorkerSandbox,
|
|
12719
|
+
createRemoteWorkerWorkspace,
|
|
11526
12720
|
createResourceSettingsManager,
|
|
11527
12721
|
createVercelDeploymentSnapshotProvider,
|
|
11528
12722
|
createVercelProvisioningAdapter,
|
|
11529
12723
|
createVercelSandboxWorkspace,
|
|
12724
|
+
decodeBytesFromWorker,
|
|
12725
|
+
encodeBytesForWorker,
|
|
11530
12726
|
fileRoutes,
|
|
11531
12727
|
getBoringAgentPathEntries,
|
|
11532
12728
|
getBoringAgentRuntimeEnv,
|
|
11533
12729
|
getBoringAgentRuntimePaths,
|
|
11534
12730
|
hasBwrap,
|
|
11535
12731
|
mergePiPackageSources,
|
|
12732
|
+
normalizeMeteringUsage,
|
|
11536
12733
|
piPackageSourceKey,
|
|
11537
12734
|
prepareDeploymentSnapshot,
|
|
11538
12735
|
prepareVercelDeploymentSnapshot,
|