@hachej/boring-agent 0.1.40 → 0.1.42
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/{agentPluginEvents-CKrZLW3g.d.ts → agentPluginEvents-ZoOjcb5J.d.ts} +21 -0
- package/dist/front/index.d.ts +3 -1
- package/dist/front/index.js +9 -3
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +429 -257
- package/dist/shared/index.d.ts +2 -2
- package/docs/ACCESSIBILITY.md +11 -13
- package/docs/API.md +86 -59
- package/docs/CSP.md +10 -9
- package/docs/ERROR_CODES.md +1 -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/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
|
@@ -87,17 +87,18 @@ function getBoringAgentRuntimeEnv(paths, adapterCacheRoot = paths.cache) {
|
|
|
87
87
|
|
|
88
88
|
// src/server/sandbox/workspacePythonEnv.ts
|
|
89
89
|
function withWorkspacePythonEnv(opts) {
|
|
90
|
-
const { workspaceRoot, env, sandboxRoot } = opts;
|
|
90
|
+
const { workspaceRoot, env, sandboxRoot, preserveHostHome } = opts;
|
|
91
91
|
const runtimeRoot = sandboxRoot ?? workspaceRoot;
|
|
92
92
|
const paths = getBoringAgentRuntimePaths(runtimeRoot);
|
|
93
93
|
const baseEnv = env ?? getEnvSnapshot();
|
|
94
94
|
const pathParts = getBoringAgentPathEntries(paths);
|
|
95
95
|
const existingPath = baseEnv.PATH;
|
|
96
96
|
if (existingPath) pathParts.push(existingPath);
|
|
97
|
+
const home = preserveHostHome ? baseEnv.HOME : runtimeRoot;
|
|
97
98
|
return {
|
|
98
99
|
...baseEnv,
|
|
99
100
|
PATH: pathParts.join(":"),
|
|
100
|
-
HOME:
|
|
101
|
+
HOME: home,
|
|
101
102
|
VIRTUAL_ENV: paths.venv,
|
|
102
103
|
PYTHONHOME: void 0,
|
|
103
104
|
BORING_AGENT_WORKSPACE_ROOT: runtimeRoot
|
|
@@ -168,7 +169,7 @@ function createDirectSandbox(opts = {}) {
|
|
|
168
169
|
return await new Promise((resolve11, reject) => {
|
|
169
170
|
const child = spawn(cmd, {
|
|
170
171
|
cwd,
|
|
171
|
-
env: withWorkspacePythonEnv({ workspaceRoot, env: opts2?.env }),
|
|
172
|
+
env: withWorkspacePythonEnv({ workspaceRoot, env: opts2?.env, preserveHostHome: true }),
|
|
172
173
|
shell: true,
|
|
173
174
|
windowsHide: true,
|
|
174
175
|
detached: process.platform !== "win32"
|
|
@@ -256,7 +257,7 @@ function createDirectSandbox(opts = {}) {
|
|
|
256
257
|
import { access, stat as stat3 } from "fs/promises";
|
|
257
258
|
import { constants } from "fs";
|
|
258
259
|
import { spawn as spawn2 } from "child_process";
|
|
259
|
-
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";
|
|
260
261
|
|
|
261
262
|
// src/server/sandbox/bwrap/buildBwrapArgs.ts
|
|
262
263
|
import { isAbsolute } from "path";
|
|
@@ -344,9 +345,8 @@ function buildBwrapArgs(workspaceRoot, options) {
|
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
// src/server/workspace/createNodeWorkspace.ts
|
|
347
|
-
import { lstat as lstat2, mkdir, readdir, readFile, rename, rm, stat as stat2, unlink, writeFile } from "fs/promises";
|
|
348
|
-
import { dirname as dirname3,
|
|
349
|
-
import chokidar from "chokidar";
|
|
348
|
+
import { lstat as lstat2, mkdir, readdir as readdir2, readFile, rename, rm, stat as stat2, unlink, writeFile } from "fs/promises";
|
|
349
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
350
350
|
|
|
351
351
|
// src/server/workspace/paths.ts
|
|
352
352
|
import { lstat, realpath, stat } from "fs/promises";
|
|
@@ -427,9 +427,13 @@ async function ensureWritableWorkspacePath(workspaceRoot, relPath) {
|
|
|
427
427
|
return absPath;
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
// src/server/workspace/
|
|
431
|
-
|
|
432
|
-
|
|
430
|
+
// src/server/workspace/nodeWatcher.ts
|
|
431
|
+
import { readdir } from "fs/promises";
|
|
432
|
+
import { join as join2, relative as relative2, sep } from "path";
|
|
433
|
+
import chokidar from "chokidar";
|
|
434
|
+
|
|
435
|
+
// src/server/workspace/ignore.ts
|
|
436
|
+
var DEFAULT_IGNORED_DIR_NAMES = [
|
|
433
437
|
"node_modules",
|
|
434
438
|
".git",
|
|
435
439
|
".DS_Store",
|
|
@@ -441,19 +445,141 @@ var DEFAULT_WATCH_IGNORES = [
|
|
|
441
445
|
".turbo",
|
|
442
446
|
"test-results"
|
|
443
447
|
];
|
|
448
|
+
var IGNORED_SET = new Set(DEFAULT_IGNORED_DIR_NAMES);
|
|
449
|
+
function isIgnoredDirName(name) {
|
|
450
|
+
return IGNORED_SET.has(name) || name.endsWith(".tsbuildinfo");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/server/logging.ts
|
|
454
|
+
var SENSITIVE_KEYS = new Set([
|
|
455
|
+
"apiKey",
|
|
456
|
+
"api_key",
|
|
457
|
+
"token",
|
|
458
|
+
"secret",
|
|
459
|
+
"password",
|
|
460
|
+
"authorization",
|
|
461
|
+
"cookie",
|
|
462
|
+
"oidcToken",
|
|
463
|
+
"accessToken",
|
|
464
|
+
"refreshToken",
|
|
465
|
+
"ANTHROPIC_API_KEY",
|
|
466
|
+
"OPENAI_API_KEY",
|
|
467
|
+
"VERCEL_OIDC_TOKEN",
|
|
468
|
+
"VERCEL_TEAM_ID"
|
|
469
|
+
].map((key) => key.toLowerCase()));
|
|
470
|
+
function isSensitiveKey(key) {
|
|
471
|
+
return SENSITIVE_KEYS.has(key.toLowerCase());
|
|
472
|
+
}
|
|
473
|
+
function redactValue(key, value, seen) {
|
|
474
|
+
if (key && isSensitiveKey(key) && value != null) return "***";
|
|
475
|
+
if (value == null || typeof value !== "object") return value;
|
|
476
|
+
if (value instanceof Date) return value;
|
|
477
|
+
if (seen.has(value)) return "[Circular]";
|
|
478
|
+
seen.add(value);
|
|
479
|
+
if (Array.isArray(value)) {
|
|
480
|
+
const out2 = value.map((item) => redactValue(void 0, item, seen));
|
|
481
|
+
seen.delete(value);
|
|
482
|
+
return out2;
|
|
483
|
+
}
|
|
484
|
+
const out = {};
|
|
485
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
486
|
+
out[childKey] = redactValue(childKey, childValue, seen);
|
|
487
|
+
}
|
|
488
|
+
seen.delete(value);
|
|
489
|
+
return out;
|
|
490
|
+
}
|
|
491
|
+
function redact(fields) {
|
|
492
|
+
const out = {};
|
|
493
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
494
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
495
|
+
out[k] = redactValue(k, v, seen);
|
|
496
|
+
}
|
|
497
|
+
return out;
|
|
498
|
+
}
|
|
499
|
+
var verbose = typeof process !== "undefined" && process.env?.BORING_AGENT_VERBOSE === "1";
|
|
500
|
+
function createLogger(prefix) {
|
|
501
|
+
function emit(level, msg, fields) {
|
|
502
|
+
const entry = {
|
|
503
|
+
level,
|
|
504
|
+
prefix,
|
|
505
|
+
msg,
|
|
506
|
+
...fields ? redact(fields) : {},
|
|
507
|
+
t: (/* @__PURE__ */ new Date()).toISOString()
|
|
508
|
+
};
|
|
509
|
+
if (level === "error") {
|
|
510
|
+
console.error(JSON.stringify(entry));
|
|
511
|
+
} else if (level === "warn") {
|
|
512
|
+
console.warn(JSON.stringify(entry));
|
|
513
|
+
} else {
|
|
514
|
+
console.log(JSON.stringify(entry));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
debug(msg, fields) {
|
|
519
|
+
if (verbose) emit("debug", msg, fields);
|
|
520
|
+
},
|
|
521
|
+
info(msg, fields) {
|
|
522
|
+
emit("info", msg, fields);
|
|
523
|
+
},
|
|
524
|
+
warn(msg, fields) {
|
|
525
|
+
emit("warn", msg, fields);
|
|
526
|
+
},
|
|
527
|
+
error(msg, fields) {
|
|
528
|
+
emit("error", msg, fields);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/server/workspace/nodeWatcher.ts
|
|
534
|
+
var log = createLogger("workspace-watch");
|
|
444
535
|
function shouldIgnoreWatchPath(root, path4) {
|
|
445
536
|
const relPath = relative2(root, path4);
|
|
446
537
|
const parts = relPath.split(sep);
|
|
447
|
-
return parts.some(
|
|
448
|
-
|
|
449
|
-
|
|
538
|
+
return parts.some((part) => isIgnoredDirName(part));
|
|
539
|
+
}
|
|
540
|
+
function toPosixRel(root, absPath) {
|
|
541
|
+
return relative2(root, absPath).split(sep).join("/");
|
|
542
|
+
}
|
|
543
|
+
var RENAME_ECHO_IDLE_MS = 1e3;
|
|
544
|
+
var RENAME_ECHO_MAX_MS = 3e4;
|
|
545
|
+
var RENAME_ECHO_FROM_KINDS = /* @__PURE__ */ new Set(["unlink", "unlinkDir"]);
|
|
546
|
+
var RENAME_ECHO_TO_KINDS = /* @__PURE__ */ new Set(["add", "addDir"]);
|
|
547
|
+
var DEFAULT_MAX_WATCHED_ENTRIES = 5e4;
|
|
548
|
+
function maxWatchedEntries() {
|
|
549
|
+
const raw = getEnv("BORING_MAX_WATCHED_ENTRIES");
|
|
550
|
+
if (!raw) return DEFAULT_MAX_WATCHED_ENTRIES;
|
|
551
|
+
const n = Number.parseInt(raw, 10);
|
|
552
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_WATCHED_ENTRIES;
|
|
553
|
+
}
|
|
554
|
+
async function countWatchableEntries(root, cap) {
|
|
555
|
+
let count = 0;
|
|
556
|
+
const stack = [root];
|
|
557
|
+
while (stack.length > 0) {
|
|
558
|
+
const dir = stack.pop();
|
|
559
|
+
let entries;
|
|
560
|
+
try {
|
|
561
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
562
|
+
} catch {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
for (const entry of entries) {
|
|
566
|
+
count += 1;
|
|
567
|
+
if (count > cap) return count;
|
|
568
|
+
if (entry.isDirectory() && !isIgnoredDirName(entry.name)) {
|
|
569
|
+
stack.push(join2(dir, entry.name));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return count;
|
|
450
574
|
}
|
|
451
575
|
function createNodeWatcher(root) {
|
|
452
576
|
const listeners = /* @__PURE__ */ new Set();
|
|
577
|
+
const suppressions = [];
|
|
453
578
|
let fsw = null;
|
|
454
579
|
let closed = false;
|
|
455
|
-
|
|
456
|
-
|
|
580
|
+
let readiness = null;
|
|
581
|
+
const startFsw = () => {
|
|
582
|
+
if (fsw || closed) return;
|
|
457
583
|
fsw = chokidar.watch(root, {
|
|
458
584
|
ignored: (path4) => shouldIgnoreWatchPath(root, path4),
|
|
459
585
|
ignoreInitial: true,
|
|
@@ -463,23 +589,43 @@ function createNodeWatcher(root) {
|
|
|
463
589
|
// servers. Consumers already reconcile by mtime after invalidation.
|
|
464
590
|
// No native renames from chokidar — `unlinkDir`/`addDir`/
|
|
465
591
|
// `unlink`/`add` are the primitives we get. We surface them as
|
|
466
|
-
// separate `unlink` + `write`/`mkdir` events
|
|
467
|
-
//
|
|
592
|
+
// separate `unlink` + `write`/`mkdir` events. Renames the host
|
|
593
|
+
// performs itself arrive via `emitRename` instead, which also
|
|
594
|
+
// mutes this echo.
|
|
468
595
|
});
|
|
469
|
-
fsw.on("add", (p, s) => emit({ op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
470
|
-
fsw.on("change", (p, s) => emit({ op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
471
|
-
fsw.on("addDir", (p) => emit({ op: "mkdir", path: rel(p) }));
|
|
472
|
-
fsw.on("unlink", (p) => emit({ op: "unlink", path: rel(p) }));
|
|
473
|
-
fsw.on("unlinkDir", (p) => emit({ op: "unlink", path: rel(p) }));
|
|
474
|
-
|
|
596
|
+
fsw.on("add", (p, s) => emit("add", { op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
597
|
+
fsw.on("change", (p, s) => emit("change", { op: "write", path: rel(p), mtimeMs: s?.mtimeMs }));
|
|
598
|
+
fsw.on("addDir", (p) => emit("addDir", { op: "mkdir", path: rel(p) }));
|
|
599
|
+
fsw.on("unlink", (p) => emit("unlink", { op: "unlink", path: rel(p) }));
|
|
600
|
+
fsw.on("unlinkDir", (p) => emit("unlinkDir", { op: "unlink", path: rel(p) }));
|
|
601
|
+
let loggedError = false;
|
|
602
|
+
fsw.on("error", (err) => {
|
|
603
|
+
if (loggedError) return;
|
|
604
|
+
loggedError = true;
|
|
605
|
+
log.error("file watcher error \u2014 live file events may be incomplete", {
|
|
606
|
+
root,
|
|
607
|
+
error: err instanceof Error ? err.message : String(err)
|
|
608
|
+
});
|
|
475
609
|
});
|
|
476
|
-
return fsw;
|
|
477
610
|
};
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
611
|
+
const ensureFsw = () => {
|
|
612
|
+
if (readiness) return readiness;
|
|
613
|
+
readiness = (async () => {
|
|
614
|
+
const cap = maxWatchedEntries();
|
|
615
|
+
const count = await countWatchableEntries(root, cap);
|
|
616
|
+
if (closed) return { ok: false, reason: "closed" };
|
|
617
|
+
if (count > cap) {
|
|
618
|
+
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.`;
|
|
619
|
+
log.error(message, { root, cap });
|
|
620
|
+
return { ok: false, reason: "workspace_too_large", message };
|
|
621
|
+
}
|
|
622
|
+
startFsw();
|
|
623
|
+
return { ok: true };
|
|
624
|
+
})();
|
|
625
|
+
return readiness;
|
|
481
626
|
};
|
|
482
|
-
const
|
|
627
|
+
const rel = (abs) => toPosixRel(root, abs);
|
|
628
|
+
const fanout = (event) => {
|
|
483
629
|
if (closed) return;
|
|
484
630
|
if (event.path === "" || event.path.startsWith("..")) return;
|
|
485
631
|
for (const l of [...listeners]) {
|
|
@@ -489,26 +635,67 @@ function createNodeWatcher(root) {
|
|
|
489
635
|
}
|
|
490
636
|
}
|
|
491
637
|
};
|
|
638
|
+
const isSuppressedEcho = (kind, path4) => {
|
|
639
|
+
const now = Date.now();
|
|
640
|
+
for (let i = suppressions.length - 1; i >= 0; i--) {
|
|
641
|
+
const s = suppressions[i];
|
|
642
|
+
if (now > s.idleDeadline || now > s.hardDeadline) {
|
|
643
|
+
suppressions.splice(i, 1);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (s.kinds.has(kind) && (path4 === s.prefix || path4.startsWith(`${s.prefix}/`))) {
|
|
647
|
+
s.idleDeadline = Math.min(now + RENAME_ECHO_IDLE_MS, s.hardDeadline);
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return false;
|
|
652
|
+
};
|
|
653
|
+
const emit = (kind, event) => {
|
|
654
|
+
if (isSuppressedEcho(kind, event.path)) return;
|
|
655
|
+
fanout(event);
|
|
656
|
+
};
|
|
492
657
|
return {
|
|
493
658
|
subscribe(listener) {
|
|
494
659
|
if (closed) return () => {
|
|
495
660
|
};
|
|
496
661
|
listeners.add(listener);
|
|
497
|
-
ensureFsw();
|
|
662
|
+
void ensureFsw();
|
|
498
663
|
return () => {
|
|
499
664
|
listeners.delete(listener);
|
|
500
665
|
};
|
|
501
666
|
},
|
|
667
|
+
whenReady() {
|
|
668
|
+
return ensureFsw();
|
|
669
|
+
},
|
|
670
|
+
emitRename(fromRel, toRel) {
|
|
671
|
+
if (closed) return;
|
|
672
|
+
if (fsw) {
|
|
673
|
+
const now = Date.now();
|
|
674
|
+
const window = {
|
|
675
|
+
idleDeadline: now + RENAME_ECHO_IDLE_MS,
|
|
676
|
+
hardDeadline: now + RENAME_ECHO_MAX_MS
|
|
677
|
+
};
|
|
678
|
+
suppressions.push(
|
|
679
|
+
{ prefix: fromRel, kinds: RENAME_ECHO_FROM_KINDS, ...window },
|
|
680
|
+
{ prefix: toRel, kinds: RENAME_ECHO_TO_KINDS, ...window }
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
fanout({ op: "rename", path: toRel, oldPath: fromRel });
|
|
684
|
+
},
|
|
502
685
|
close() {
|
|
503
686
|
if (closed) return;
|
|
504
687
|
closed = true;
|
|
505
688
|
listeners.clear();
|
|
689
|
+
suppressions.length = 0;
|
|
506
690
|
fsw?.close().catch(() => {
|
|
507
691
|
});
|
|
508
692
|
fsw = null;
|
|
509
693
|
}
|
|
510
694
|
};
|
|
511
695
|
}
|
|
696
|
+
|
|
697
|
+
// src/server/workspace/createNodeWorkspace.ts
|
|
698
|
+
var EPERM_CODE = "EPERM";
|
|
512
699
|
var nodeWorkspaceHostRoots = /* @__PURE__ */ new WeakMap();
|
|
513
700
|
function getNodeWorkspaceHostRoot(workspace) {
|
|
514
701
|
return nodeWorkspaceHostRoots.get(workspace);
|
|
@@ -589,7 +776,7 @@ function createNodeWorkspace(root, opts = {}) {
|
|
|
589
776
|
},
|
|
590
777
|
async readdir(relPath) {
|
|
591
778
|
const absPath = await ensureExistingWorkspacePath(root, relPath);
|
|
592
|
-
const entries = await
|
|
779
|
+
const entries = await readdir2(absPath, { withFileTypes: true });
|
|
593
780
|
return entries.map((entry) => ({
|
|
594
781
|
name: entry.name,
|
|
595
782
|
kind: entry.isDirectory() ? "dir" : "file"
|
|
@@ -627,6 +814,7 @@ function createNodeWorkspace(root, opts = {}) {
|
|
|
627
814
|
const fromAbsPath = await ensureExistingWorkspacePath(root, fromRelPath);
|
|
628
815
|
const toAbsPath = await ensureWritableWorkspacePath(root, toRelPath2);
|
|
629
816
|
await rename(fromAbsPath, toAbsPath);
|
|
817
|
+
cachedWatcher?.emitRename(toPosixRel(root, fromAbsPath), toPosixRel(root, toAbsPath));
|
|
630
818
|
}
|
|
631
819
|
};
|
|
632
820
|
nodeWorkspaceHostRoots.set(workspace, root);
|
|
@@ -752,8 +940,8 @@ async function buildGlobalToolMounts(workspaceRoot) {
|
|
|
752
940
|
if (globalRoot === workspaceRoot) return [];
|
|
753
941
|
const args = [];
|
|
754
942
|
const mountIfChildLacks = async (runtimeRelPath) => {
|
|
755
|
-
const parentRuntimePath =
|
|
756
|
-
const childRuntimePath =
|
|
943
|
+
const parentRuntimePath = join3(globalRoot, runtimeRelPath);
|
|
944
|
+
const childRuntimePath = join3(workspaceRoot, runtimeRelPath);
|
|
757
945
|
if (!await dirExists(parentRuntimePath)) return false;
|
|
758
946
|
if (await pathExists(childRuntimePath)) return false;
|
|
759
947
|
args.push("--ro-bind", parentRuntimePath, `${SANDBOX_HOME2}/${runtimeRelPath}`);
|
|
@@ -1696,86 +1884,6 @@ function createAuthMiddleware(opts = {}) {
|
|
|
1696
1884
|
};
|
|
1697
1885
|
}
|
|
1698
1886
|
|
|
1699
|
-
// src/server/logging.ts
|
|
1700
|
-
var SENSITIVE_KEYS = new Set([
|
|
1701
|
-
"apiKey",
|
|
1702
|
-
"api_key",
|
|
1703
|
-
"token",
|
|
1704
|
-
"secret",
|
|
1705
|
-
"password",
|
|
1706
|
-
"authorization",
|
|
1707
|
-
"cookie",
|
|
1708
|
-
"oidcToken",
|
|
1709
|
-
"accessToken",
|
|
1710
|
-
"refreshToken",
|
|
1711
|
-
"ANTHROPIC_API_KEY",
|
|
1712
|
-
"OPENAI_API_KEY",
|
|
1713
|
-
"VERCEL_OIDC_TOKEN",
|
|
1714
|
-
"VERCEL_TEAM_ID"
|
|
1715
|
-
].map((key) => key.toLowerCase()));
|
|
1716
|
-
function isSensitiveKey(key) {
|
|
1717
|
-
return SENSITIVE_KEYS.has(key.toLowerCase());
|
|
1718
|
-
}
|
|
1719
|
-
function redactValue(key, value, seen) {
|
|
1720
|
-
if (key && isSensitiveKey(key) && value != null) return "***";
|
|
1721
|
-
if (value == null || typeof value !== "object") return value;
|
|
1722
|
-
if (value instanceof Date) return value;
|
|
1723
|
-
if (seen.has(value)) return "[Circular]";
|
|
1724
|
-
seen.add(value);
|
|
1725
|
-
if (Array.isArray(value)) {
|
|
1726
|
-
const out2 = value.map((item) => redactValue(void 0, item, seen));
|
|
1727
|
-
seen.delete(value);
|
|
1728
|
-
return out2;
|
|
1729
|
-
}
|
|
1730
|
-
const out = {};
|
|
1731
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
1732
|
-
out[childKey] = redactValue(childKey, childValue, seen);
|
|
1733
|
-
}
|
|
1734
|
-
seen.delete(value);
|
|
1735
|
-
return out;
|
|
1736
|
-
}
|
|
1737
|
-
function redact(fields) {
|
|
1738
|
-
const out = {};
|
|
1739
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
1740
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
1741
|
-
out[k] = redactValue(k, v, seen);
|
|
1742
|
-
}
|
|
1743
|
-
return out;
|
|
1744
|
-
}
|
|
1745
|
-
var verbose = typeof process !== "undefined" && process.env?.BORING_AGENT_VERBOSE === "1";
|
|
1746
|
-
function createLogger(prefix) {
|
|
1747
|
-
function emit(level, msg, fields) {
|
|
1748
|
-
const entry = {
|
|
1749
|
-
level,
|
|
1750
|
-
prefix,
|
|
1751
|
-
msg,
|
|
1752
|
-
...fields ? redact(fields) : {},
|
|
1753
|
-
t: (/* @__PURE__ */ new Date()).toISOString()
|
|
1754
|
-
};
|
|
1755
|
-
if (level === "error") {
|
|
1756
|
-
console.error(JSON.stringify(entry));
|
|
1757
|
-
} else if (level === "warn") {
|
|
1758
|
-
console.warn(JSON.stringify(entry));
|
|
1759
|
-
} else {
|
|
1760
|
-
console.log(JSON.stringify(entry));
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
return {
|
|
1764
|
-
debug(msg, fields) {
|
|
1765
|
-
if (verbose) emit("debug", msg, fields);
|
|
1766
|
-
},
|
|
1767
|
-
info(msg, fields) {
|
|
1768
|
-
emit("info", msg, fields);
|
|
1769
|
-
},
|
|
1770
|
-
warn(msg, fields) {
|
|
1771
|
-
emit("warn", msg, fields);
|
|
1772
|
-
},
|
|
1773
|
-
error(msg, fields) {
|
|
1774
|
-
emit("error", msg, fields);
|
|
1775
|
-
}
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
1887
|
// src/server/http/routes/fileRecords.ts
|
|
1780
1888
|
import { extname } from "path/posix";
|
|
1781
1889
|
var MAX_RECORD_FILE_BYTES = 2 * 1024 * 1024;
|
|
@@ -1982,7 +2090,7 @@ function buildFileRecordsResult(args) {
|
|
|
1982
2090
|
}
|
|
1983
2091
|
|
|
1984
2092
|
// src/server/http/routes/file.ts
|
|
1985
|
-
var
|
|
2093
|
+
var log2 = createLogger("boring/workspace-settings");
|
|
1986
2094
|
var BORING_SETTINGS_PATH = ".boring/settings";
|
|
1987
2095
|
var DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR = "assets/images";
|
|
1988
2096
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
@@ -2057,7 +2165,7 @@ function parseWorkspaceSettings(raw) {
|
|
|
2057
2165
|
}
|
|
2058
2166
|
};
|
|
2059
2167
|
} catch (err) {
|
|
2060
|
-
|
|
2168
|
+
log2.warn("failed to parse .boring/settings \u2014 falling back to defaults", {
|
|
2061
2169
|
error: err instanceof Error ? err.message : String(err)
|
|
2062
2170
|
});
|
|
2063
2171
|
return defaultWorkspaceSettings();
|
|
@@ -2405,8 +2513,8 @@ function fileRoutes(app, opts, done) {
|
|
|
2405
2513
|
// src/server/workspace/provisionRuntime.ts
|
|
2406
2514
|
import { createHash as createHash3 } from "crypto";
|
|
2407
2515
|
import { constants as constants2 } from "fs";
|
|
2408
|
-
import { access as access2, chmod as chmod3, cp, mkdir as mkdir4, readdir as
|
|
2409
|
-
import { dirname as dirname6, isAbsolute as isAbsolute4, join as
|
|
2516
|
+
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";
|
|
2517
|
+
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join4, relative as relative5, resolve as resolve5 } from "path";
|
|
2410
2518
|
import { fileURLToPath } from "url";
|
|
2411
2519
|
import { execFile } from "child_process";
|
|
2412
2520
|
import { promisify } from "util";
|
|
@@ -2442,11 +2550,11 @@ async function commandExists(cmd) {
|
|
|
2442
2550
|
async function hashPath(path4, hash) {
|
|
2443
2551
|
const info = await stat4(path4);
|
|
2444
2552
|
if (info.isDirectory()) {
|
|
2445
|
-
const entries = (await
|
|
2553
|
+
const entries = (await readdir3(path4)).filter((entry) => entry !== "__pycache__" && entry !== "build" && !entry.endsWith(".egg-info")).sort();
|
|
2446
2554
|
for (const entry of entries) {
|
|
2447
2555
|
hash.update(`dir:${entry}
|
|
2448
2556
|
`);
|
|
2449
|
-
await hashPath(
|
|
2557
|
+
await hashPath(join4(path4, entry), hash);
|
|
2450
2558
|
}
|
|
2451
2559
|
return;
|
|
2452
2560
|
}
|
|
@@ -2479,16 +2587,16 @@ async function fingerprint(contributions) {
|
|
|
2479
2587
|
const packageRoot = toPath(spec.packageRoot);
|
|
2480
2588
|
hash.update(`node-package:${spec.id}:${spec.packageName}:${packageRoot}
|
|
2481
2589
|
`);
|
|
2482
|
-
const packageJson =
|
|
2483
|
-
const distDir =
|
|
2484
|
-
const docsDir =
|
|
2485
|
-
const skillsDir =
|
|
2486
|
-
const referencesDir =
|
|
2487
|
-
const sourceDocsDir =
|
|
2590
|
+
const packageJson = join4(packageRoot, "package.json");
|
|
2591
|
+
const distDir = join4(packageRoot, "dist");
|
|
2592
|
+
const docsDir = join4(packageRoot, "docs");
|
|
2593
|
+
const skillsDir = join4(packageRoot, "skills");
|
|
2594
|
+
const referencesDir = join4(packageRoot, "references");
|
|
2595
|
+
const sourceDocsDir = join4(packageRoot, "src", "server", "docs");
|
|
2488
2596
|
if (await exists(packageJson)) await hashPath(packageJson, hash);
|
|
2489
2597
|
if (await exists(distDir)) await hashPath(distDir, hash);
|
|
2490
|
-
const templatesDir =
|
|
2491
|
-
const publicDir =
|
|
2598
|
+
const templatesDir = join4(packageRoot, "templates");
|
|
2599
|
+
const publicDir = join4(packageRoot, "public");
|
|
2492
2600
|
if (await exists(templatesDir)) await hashPath(templatesDir, hash);
|
|
2493
2601
|
if (await exists(publicDir)) await hashPath(publicDir, hash);
|
|
2494
2602
|
if (await exists(docsDir)) await hashPath(docsDir, hash);
|
|
@@ -2539,7 +2647,7 @@ function nodePackageTarget(workspaceRoot, packageName) {
|
|
|
2539
2647
|
if (parts.length === 0 || parts.some((part) => part === "." || part === "..")) {
|
|
2540
2648
|
throw new Error(`Invalid node package name: ${packageName}`);
|
|
2541
2649
|
}
|
|
2542
|
-
return
|
|
2650
|
+
return join4(workspaceRoot, "node_modules", ...parts);
|
|
2543
2651
|
}
|
|
2544
2652
|
async function copyIfExists(source, target) {
|
|
2545
2653
|
if (!await exists(source)) return false;
|
|
@@ -2557,27 +2665,27 @@ async function ensureNodePackages(workspaceRoot, specs) {
|
|
|
2557
2665
|
const packageRoot = toPath(spec.packageRoot);
|
|
2558
2666
|
const target = nodePackageTarget(workspaceRoot, spec.packageName);
|
|
2559
2667
|
await mkdir4(target, { recursive: true });
|
|
2560
|
-
const copiedPackageJson = await copyIfExists(
|
|
2668
|
+
const copiedPackageJson = await copyIfExists(join4(packageRoot, "package.json"), join4(target, "package.json"));
|
|
2561
2669
|
if (!copiedPackageJson) {
|
|
2562
2670
|
throw new Error(`Node package provisioning source is missing package.json: ${packageRoot}`);
|
|
2563
2671
|
}
|
|
2564
|
-
await copyIfExists(
|
|
2565
|
-
await copyIfExists(
|
|
2566
|
-
await copyIfExists(
|
|
2567
|
-
if (!await exists(
|
|
2568
|
-
await copyIfExists(
|
|
2672
|
+
await copyIfExists(join4(packageRoot, "dist"), join4(target, "dist"));
|
|
2673
|
+
await copyIfExists(join4(packageRoot, "templates"), join4(target, "templates"));
|
|
2674
|
+
await copyIfExists(join4(packageRoot, "public"), join4(target, "public"));
|
|
2675
|
+
if (!await exists(join4(target, "dist", "docs", "plugins.md"))) {
|
|
2676
|
+
await copyIfExists(join4(packageRoot, "src", "server", "docs"), join4(target, "dist", "docs"));
|
|
2569
2677
|
}
|
|
2570
|
-
if (!await copyIfExists(
|
|
2571
|
-
await copyIfExists(
|
|
2678
|
+
if (!await copyIfExists(join4(packageRoot, "docs"), join4(target, "docs"))) {
|
|
2679
|
+
await copyIfExists(join4(packageRoot, "src", "server", "docs"), join4(target, "docs"));
|
|
2572
2680
|
}
|
|
2573
|
-
await copyIfExists(
|
|
2574
|
-
await copyIfExists(
|
|
2575
|
-
await copyIfExists(
|
|
2681
|
+
await copyIfExists(join4(packageRoot, "skills"), join4(target, "skills"));
|
|
2682
|
+
await copyIfExists(join4(packageRoot, "references"), join4(target, "references"));
|
|
2683
|
+
await copyIfExists(join4(packageRoot, "src", "globals.css"), join4(target, "src", "globals.css"));
|
|
2576
2684
|
}
|
|
2577
2685
|
}
|
|
2578
2686
|
async function ensurePython(workspaceRoot, specs) {
|
|
2579
2687
|
if (specs.length === 0) return;
|
|
2580
|
-
const venvPython =
|
|
2688
|
+
const venvPython = join4(workspaceRoot, ".boring-agent", "venv", "bin", "python");
|
|
2581
2689
|
const uv = await commandExists("uv");
|
|
2582
2690
|
if (!await exists(venvPython)) {
|
|
2583
2691
|
if (uv) await run("uv", ["venv", ".boring-agent/venv"], workspaceRoot);
|
|
@@ -2610,17 +2718,17 @@ function assertEnvKey(key) {
|
|
|
2610
2718
|
}
|
|
2611
2719
|
async function isRuntimeMaterialized(workspaceRoot, contributions) {
|
|
2612
2720
|
const hasPython = contributions.some(({ provisioning }) => (provisioning.python ?? []).length > 0);
|
|
2613
|
-
if (hasPython && !await exists(
|
|
2721
|
+
if (hasPython && !await exists(join4(workspaceRoot, ".boring-agent", "venv", "bin", "python"))) return false;
|
|
2614
2722
|
for (const { provisioning } of contributions) {
|
|
2615
2723
|
for (const spec of provisioning.nodePackages ?? []) {
|
|
2616
|
-
if (!await exists(
|
|
2724
|
+
if (!await exists(join4(nodePackageTarget(workspaceRoot, spec.packageName), "package.json"))) return false;
|
|
2617
2725
|
}
|
|
2618
2726
|
}
|
|
2619
2727
|
return true;
|
|
2620
2728
|
}
|
|
2621
2729
|
async function writeShims(workspaceRoot, env) {
|
|
2622
|
-
const shimDir =
|
|
2623
|
-
const venvBin =
|
|
2730
|
+
const shimDir = join4(workspaceRoot, ".boring-agent", "bin");
|
|
2731
|
+
const venvBin = join4(workspaceRoot, ".boring-agent", "venv", "bin");
|
|
2624
2732
|
await mkdir4(shimDir, { recursive: true });
|
|
2625
2733
|
const exports = Object.entries(env).map(([key, value]) => {
|
|
2626
2734
|
assertEnvKey(key);
|
|
@@ -2634,21 +2742,21 @@ export BORING_AGENT_WORKSPACE_ROOT="$WORKSPACE_ROOT"
|
|
|
2634
2742
|
${exports}
|
|
2635
2743
|
VENV_BIN="$WORKSPACE_ROOT/.boring-agent/venv/bin"
|
|
2636
2744
|
`;
|
|
2637
|
-
await writeExecutable(
|
|
2745
|
+
await writeExecutable(join4(shimDir, "python"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2638
2746
|
`);
|
|
2639
|
-
await writeExecutable(
|
|
2747
|
+
await writeExecutable(join4(shimDir, "python3"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2640
2748
|
`);
|
|
2641
|
-
await writeExecutable(
|
|
2749
|
+
await writeExecutable(join4(shimDir, "pip"), `${base}exec "$VENV_BIN/python" -m pip "$@"
|
|
2642
2750
|
`);
|
|
2643
|
-
await writeExecutable(
|
|
2751
|
+
await writeExecutable(join4(shimDir, "pip3"), `${base}exec "$VENV_BIN/python" -m pip "$@"
|
|
2644
2752
|
`);
|
|
2645
2753
|
if (await exists(venvBin)) {
|
|
2646
|
-
for (const entry of await
|
|
2754
|
+
for (const entry of await readdir3(venvBin)) {
|
|
2647
2755
|
if (["python", "python3", "pip", "pip3"].includes(entry)) continue;
|
|
2648
|
-
const full =
|
|
2756
|
+
const full = join4(venvBin, entry);
|
|
2649
2757
|
const info = await stat4(full).catch(() => null);
|
|
2650
2758
|
if (!info?.isFile()) continue;
|
|
2651
|
-
await writeExecutable(
|
|
2759
|
+
await writeExecutable(join4(shimDir, entry), `${base}TARGET="$VENV_BIN"/${bashSingleQuote(entry)}
|
|
2652
2760
|
SHEBANG="$(head -n 1 "$TARGET" 2>/dev/null || true)"
|
|
2653
2761
|
case "$SHEBANG" in
|
|
2654
2762
|
*python*) exec "$VENV_BIN/python" "$TARGET" "$@" ;;
|
|
@@ -2671,8 +2779,8 @@ async function provisionRuntimeWorkspace({
|
|
|
2671
2779
|
await mkdir4(workspaceRoot, { recursive: true });
|
|
2672
2780
|
const env = collectEnv(active);
|
|
2673
2781
|
const hash = await fingerprint(active);
|
|
2674
|
-
const markerPath =
|
|
2675
|
-
const binDir =
|
|
2782
|
+
const markerPath = join4(workspaceRoot, ".boring-agent", "provisioning.json");
|
|
2783
|
+
const binDir = join4(workspaceRoot, ".boring-agent", "bin");
|
|
2676
2784
|
if (!force && await exists(markerPath)) {
|
|
2677
2785
|
try {
|
|
2678
2786
|
const marker = JSON.parse(await readFile4(markerPath, "utf8"));
|
|
@@ -3110,7 +3218,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3110
3218
|
// src/server/workspace/provisioning/packArtifact.ts
|
|
3111
3219
|
import { execFile as execFile2 } from "child_process";
|
|
3112
3220
|
import { mkdir as mkdir5, mkdtemp, rename as rename4 } from "fs/promises";
|
|
3113
|
-
import { dirname as dirname7, isAbsolute as isAbsolute5, join as
|
|
3221
|
+
import { dirname as dirname7, isAbsolute as isAbsolute5, join as join5 } from "path";
|
|
3114
3222
|
import { tmpdir } from "os";
|
|
3115
3223
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3116
3224
|
import { promisify as promisify2 } from "util";
|
|
@@ -3155,7 +3263,7 @@ async function packProvisioningArtifact(request) {
|
|
|
3155
3263
|
], { maxBuffer: 1024 * 1024 * 20 });
|
|
3156
3264
|
const packedName = stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
3157
3265
|
if (!packedName) throw new Error(`pnpm pack produced no artifact for ${sourcePath}`);
|
|
3158
|
-
const packedPath = isAbsolute5(packedName) ? packedName :
|
|
3266
|
+
const packedPath = isAbsolute5(packedName) ? packedName : join5(dirname7(request.outputPath), packedName);
|
|
3159
3267
|
await rename4(packedPath, request.outputPath);
|
|
3160
3268
|
return;
|
|
3161
3269
|
}
|
|
@@ -3177,8 +3285,8 @@ async function resolveArtifactInstallSource(args) {
|
|
|
3177
3285
|
const name = provisioningArtifactName(kind, id, fingerprint2);
|
|
3178
3286
|
const workspaceRel = `.boring-agent/tmp/${name}`;
|
|
3179
3287
|
if (!await args.workspaceFs.exists(workspaceRel)) {
|
|
3180
|
-
const artifactDir = await mkdtemp(
|
|
3181
|
-
const outputPath =
|
|
3288
|
+
const artifactDir = await mkdtemp(join5(tmpdir(), "boring-agent-artifact-"));
|
|
3289
|
+
const outputPath = join5(artifactDir, name);
|
|
3182
3290
|
try {
|
|
3183
3291
|
await args.prepareArtifact({ kind, id, fingerprint: fingerprint2, source: args.source, outputPath });
|
|
3184
3292
|
await args.workspaceFs.copyFromHost(outputPath, workspaceRel);
|
|
@@ -3285,7 +3393,7 @@ function createPythonRuntimeFingerprint(input) {
|
|
|
3285
3393
|
}
|
|
3286
3394
|
|
|
3287
3395
|
// src/server/workspace/provisioning/node.ts
|
|
3288
|
-
import { join as
|
|
3396
|
+
import { join as join6, relative as relative6, sep as sep3 } from "path";
|
|
3289
3397
|
var NODE_RUNTIME_REL = ".boring-agent/node";
|
|
3290
3398
|
var NODE_PACKAGE_JSON_REL = `${NODE_RUNTIME_REL}/package.json`;
|
|
3291
3399
|
var NODE_FINGERPRINT_REL = `${NODE_RUNTIME_REL}/.fingerprint`;
|
|
@@ -3323,9 +3431,9 @@ function nodeInstallSource(spec) {
|
|
|
3323
3431
|
}
|
|
3324
3432
|
function expectedNodeOutputs(paths, packages) {
|
|
3325
3433
|
return [
|
|
3326
|
-
|
|
3434
|
+
join6(paths.node, "package-lock.json"),
|
|
3327
3435
|
...packages.flatMap(
|
|
3328
|
-
(pkg) => (pkg.expectedBins ?? []).map((bin) =>
|
|
3436
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join6(paths.nodeBin, bin))
|
|
3329
3437
|
)
|
|
3330
3438
|
];
|
|
3331
3439
|
}
|
|
@@ -3416,7 +3524,7 @@ async function ensureNodeRuntime(options) {
|
|
|
3416
3524
|
}
|
|
3417
3525
|
|
|
3418
3526
|
// src/server/workspace/provisioning/python.ts
|
|
3419
|
-
import { dirname as dirname9, join as
|
|
3527
|
+
import { dirname as dirname9, join as join7, relative as relative7, sep as sep4 } from "path";
|
|
3420
3528
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3421
3529
|
var VENV_REL = ".boring-agent/venv";
|
|
3422
3530
|
var VENV_FINGERPRINT_REL = `${VENV_REL}/.fingerprint`;
|
|
@@ -3435,7 +3543,7 @@ async function ensureUv(options) {
|
|
|
3435
3543
|
const uvVersion = await commandOutput2(options.adapter, explicitUvBin, ["--version"]) || "uv unknown";
|
|
3436
3544
|
try {
|
|
3437
3545
|
await options.adapter.exec("mkdir", ["-p", options.runtimeLayout.uvBin], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3438
|
-
await options.adapter.exec("ln", ["-sf", explicitUvBin,
|
|
3546
|
+
await options.adapter.exec("ln", ["-sf", explicitUvBin, join7(options.runtimeLayout.uvBin, "uv")], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3439
3547
|
} catch {
|
|
3440
3548
|
}
|
|
3441
3549
|
return { uvBin: explicitUvBin, uvVersion, installedWorkspaceUv: false };
|
|
@@ -3462,7 +3570,7 @@ async function ensureUv(options) {
|
|
|
3462
3570
|
await options.adapter.workspaceFs.mkdir(".boring-agent/sdk/uv/bin");
|
|
3463
3571
|
await options.adapter.workspaceFs.rm(UV_BIN_REL);
|
|
3464
3572
|
await options.adapter.workspaceFs.copyFromHost(options.uvStandaloneSource, UV_BIN_REL);
|
|
3465
|
-
const uvBin =
|
|
3573
|
+
const uvBin = join7(options.runtimeLayout.uvBin, "uv");
|
|
3466
3574
|
await options.adapter.exec("chmod", ["+x", uvBin], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3467
3575
|
return {
|
|
3468
3576
|
uvBin,
|
|
@@ -3495,7 +3603,7 @@ function expectedPythonOutputs(paths, packages) {
|
|
|
3495
3603
|
return [
|
|
3496
3604
|
paths.venvPython,
|
|
3497
3605
|
...packages.flatMap(
|
|
3498
|
-
(pkg) => (pkg.expectedBins ?? []).map((bin) =>
|
|
3606
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join7(paths.venvBin, bin))
|
|
3499
3607
|
)
|
|
3500
3608
|
];
|
|
3501
3609
|
}
|
|
@@ -3608,7 +3716,7 @@ async function ensurePythonRuntime(options) {
|
|
|
3608
3716
|
|
|
3609
3717
|
// src/server/workspace/provisioning/skills.ts
|
|
3610
3718
|
import { stat as stat5 } from "fs/promises";
|
|
3611
|
-
import { join as
|
|
3719
|
+
import { join as join8 } from "path";
|
|
3612
3720
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3613
3721
|
var GENERATED_SKILLS_REL = ".boring-agent/skills";
|
|
3614
3722
|
var USER_SKILLS_REL = ".agents/skills";
|
|
@@ -3621,7 +3729,7 @@ function assertSafeSegment(kind, value) {
|
|
|
3621
3729
|
}
|
|
3622
3730
|
}
|
|
3623
3731
|
function getProvisionedSkillPaths(paths) {
|
|
3624
|
-
return [paths.skills,
|
|
3732
|
+
return [paths.skills, join8(paths.workspaceRoot, USER_SKILLS_REL)];
|
|
3625
3733
|
}
|
|
3626
3734
|
async function mirrorPluginSkills(options) {
|
|
3627
3735
|
await options.adapter.workspaceFs.rm(GENERATED_SKILLS_REL);
|
|
@@ -3654,7 +3762,7 @@ async function mirrorPluginSkills(options) {
|
|
|
3654
3762
|
// src/server/workspace/provisioning/workspaceFiles.ts
|
|
3655
3763
|
import { basename, join as joinPath } from "path";
|
|
3656
3764
|
import { posix as posix2 } from "path";
|
|
3657
|
-
import { readdir as
|
|
3765
|
+
import { readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
3658
3766
|
import { fileURLToPath as fileURLToPath5, pathToFileURL } from "url";
|
|
3659
3767
|
function sourceToPath3(source) {
|
|
3660
3768
|
return source instanceof URL ? fileURLToPath5(source) : source;
|
|
@@ -3696,7 +3804,7 @@ async function collectTemplateWorkItems(template) {
|
|
|
3696
3804
|
kind: "directory"
|
|
3697
3805
|
});
|
|
3698
3806
|
}
|
|
3699
|
-
const entries = await
|
|
3807
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
3700
3808
|
for (const entry of entries) {
|
|
3701
3809
|
const childPath = joinPath(dir, entry.name);
|
|
3702
3810
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
@@ -4295,8 +4403,8 @@ var localModeAdapter = {
|
|
|
4295
4403
|
};
|
|
4296
4404
|
|
|
4297
4405
|
// src/server/runtime/modes/vercel-sandbox.ts
|
|
4298
|
-
import { readFile as readFile8, readdir as
|
|
4299
|
-
import { join as
|
|
4406
|
+
import { readFile as readFile8, readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
4407
|
+
import { join as join9 } from "path";
|
|
4300
4408
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
4301
4409
|
import { Sandbox as VercelSandbox } from "@vercel/sandbox";
|
|
4302
4410
|
|
|
@@ -4442,20 +4550,20 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
|
4442
4550
|
|
|
4443
4551
|
// src/server/sandbox/vercel-sandbox/packageTemplate.ts
|
|
4444
4552
|
import { createHash as createHash5 } from "crypto";
|
|
4445
|
-
import { readFile as readFile7, readdir as
|
|
4553
|
+
import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
|
|
4446
4554
|
import path3 from "path";
|
|
4447
4555
|
import { Readable } from "stream";
|
|
4448
4556
|
import { createGzip } from "zlib";
|
|
4449
4557
|
import { put } from "@vercel/blob";
|
|
4450
4558
|
var urlCache = /* @__PURE__ */ new Map();
|
|
4451
4559
|
var LOG_PREFIX = "[template-tarball]";
|
|
4452
|
-
function
|
|
4560
|
+
function log3(msg, meta = {}) {
|
|
4453
4561
|
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
|
|
4454
4562
|
process.stderr.write(`${LOG_PREFIX} ${msg}${metaStr}
|
|
4455
4563
|
`);
|
|
4456
4564
|
}
|
|
4457
4565
|
async function collectFiles(dir, base = "") {
|
|
4458
|
-
const entries = await
|
|
4566
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
4459
4567
|
const files = [];
|
|
4460
4568
|
for (const entry of entries) {
|
|
4461
4569
|
const fullPath = path3.join(dir, entry.name);
|
|
@@ -4532,11 +4640,11 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
4532
4640
|
const hash = computeTemplateHash(files);
|
|
4533
4641
|
const cached = urlCache.get(hash);
|
|
4534
4642
|
if (cached) {
|
|
4535
|
-
|
|
4643
|
+
log3("cache hit", { hash, fileCount: files.length });
|
|
4536
4644
|
return { url: cached, hash };
|
|
4537
4645
|
}
|
|
4538
4646
|
const tarball = await buildTarGz(files);
|
|
4539
|
-
|
|
4647
|
+
log3("tarball built", {
|
|
4540
4648
|
hash,
|
|
4541
4649
|
fileCount: files.length,
|
|
4542
4650
|
sizeBytes: tarball.length,
|
|
@@ -4545,7 +4653,7 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
4545
4653
|
const upload = opts.uploadFn ?? defaultUpload;
|
|
4546
4654
|
const url = await upload(hash, tarball);
|
|
4547
4655
|
urlCache.set(hash, url);
|
|
4548
|
-
|
|
4656
|
+
log3("uploaded", { hash, url, totalMs: Date.now() - startMs });
|
|
4549
4657
|
return { url, hash };
|
|
4550
4658
|
}
|
|
4551
4659
|
|
|
@@ -4711,10 +4819,10 @@ async function copyHostPathToVercelWorkspace(options) {
|
|
|
4711
4819
|
const sourceStat = await stat8(sourcePath);
|
|
4712
4820
|
if (sourceStat.isDirectory()) {
|
|
4713
4821
|
await options.workspace.mkdir(options.targetRel, { recursive: true });
|
|
4714
|
-
for (const entry of await
|
|
4822
|
+
for (const entry of await readdir6(sourcePath, { withFileTypes: true })) {
|
|
4715
4823
|
await copyHostPathToVercelWorkspace({
|
|
4716
4824
|
workspace: options.workspace,
|
|
4717
|
-
source:
|
|
4825
|
+
source: join9(sourcePath, entry.name),
|
|
4718
4826
|
targetRel: `${options.targetRel}/${entry.name}`
|
|
4719
4827
|
});
|
|
4720
4828
|
}
|
|
@@ -5142,7 +5250,7 @@ function getRuntimeBundleStorageRoot(bundle) {
|
|
|
5142
5250
|
|
|
5143
5251
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
5144
5252
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
5145
|
-
import { extname as extname3, join as
|
|
5253
|
+
import { extname as extname3, join as join11 } from "path";
|
|
5146
5254
|
import {
|
|
5147
5255
|
createAgentSession,
|
|
5148
5256
|
SessionManager,
|
|
@@ -5422,7 +5530,7 @@ function createPiAgentSessionAdapter(session, options = {}) {
|
|
|
5422
5530
|
// src/server/harness/pi-coding-agent/sessions.ts
|
|
5423
5531
|
import { randomUUID } from "crypto";
|
|
5424
5532
|
import {
|
|
5425
|
-
readdir as
|
|
5533
|
+
readdir as readdir7,
|
|
5426
5534
|
readFile as readFile9,
|
|
5427
5535
|
stat as fsStat,
|
|
5428
5536
|
rm as rm4,
|
|
@@ -5433,7 +5541,7 @@ import {
|
|
|
5433
5541
|
open as open2
|
|
5434
5542
|
} from "fs/promises";
|
|
5435
5543
|
import { closeSync, openSync, readFileSync, readSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5436
|
-
import { join as
|
|
5544
|
+
import { join as join10, basename as basename2, resolve as resolve7 } from "path";
|
|
5437
5545
|
import { homedir as homedir3 } from "os";
|
|
5438
5546
|
import {
|
|
5439
5547
|
parseSessionEntries,
|
|
@@ -5441,7 +5549,7 @@ import {
|
|
|
5441
5549
|
} from "@mariozechner/pi-coding-agent";
|
|
5442
5550
|
function defaultSessionDir(cwd) {
|
|
5443
5551
|
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
5444
|
-
return
|
|
5552
|
+
return join10(homedir3(), ".pi", "agent", "sessions", safePath);
|
|
5445
5553
|
}
|
|
5446
5554
|
var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
5447
5555
|
var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
|
|
@@ -5451,7 +5559,7 @@ function sessionDirForNamespace(namespace) {
|
|
|
5451
5559
|
if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
|
|
5452
5560
|
throw new Error("session namespace must contain only letters, numbers, underscores, and dashes");
|
|
5453
5561
|
}
|
|
5454
|
-
return
|
|
5562
|
+
return join10(homedir3(), ".pi", "agent", "sessions", safeNamespace);
|
|
5455
5563
|
}
|
|
5456
5564
|
function normalizeListOptions(options) {
|
|
5457
5565
|
return {
|
|
@@ -5496,9 +5604,9 @@ var PiSessionStore = class {
|
|
|
5496
5604
|
}
|
|
5497
5605
|
}
|
|
5498
5606
|
async listUncached(_ctx, options) {
|
|
5499
|
-
const files = await
|
|
5607
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5500
5608
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
5501
|
-
const filepaths = jsonlFiles.map((f) =>
|
|
5609
|
+
const filepaths = jsonlFiles.map((f) => join10(this.sessionDir, f));
|
|
5502
5610
|
const fileStats = await Promise.all(filepaths.map(async (filepath) => {
|
|
5503
5611
|
try {
|
|
5504
5612
|
return { filepath, stat: await fsStat(filepath) };
|
|
@@ -5542,7 +5650,7 @@ var PiSessionStore = class {
|
|
|
5542
5650
|
};
|
|
5543
5651
|
lines.push(JSON.stringify(infoEntry));
|
|
5544
5652
|
}
|
|
5545
|
-
const filepath =
|
|
5653
|
+
const filepath = join10(this.sessionDir, `${id}.jsonl`);
|
|
5546
5654
|
await writeFile6(filepath, lines.join("\n") + "\n", "utf-8");
|
|
5547
5655
|
return {
|
|
5548
5656
|
id,
|
|
@@ -5624,7 +5732,7 @@ var PiSessionStore = class {
|
|
|
5624
5732
|
loadPiSessionFileSync(sessionId) {
|
|
5625
5733
|
if (!SAFE_ID.test(sessionId)) return null;
|
|
5626
5734
|
try {
|
|
5627
|
-
const direct =
|
|
5735
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5628
5736
|
let filepath = direct;
|
|
5629
5737
|
let content;
|
|
5630
5738
|
try {
|
|
@@ -5634,7 +5742,7 @@ var PiSessionStore = class {
|
|
|
5634
5742
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
5635
5743
|
);
|
|
5636
5744
|
if (files.length === 0) return null;
|
|
5637
|
-
filepath =
|
|
5745
|
+
filepath = join10(this.sessionDir, files[0]);
|
|
5638
5746
|
content = readFileSync(filepath, "utf-8");
|
|
5639
5747
|
}
|
|
5640
5748
|
const entries = safeParseEntries(content);
|
|
@@ -5690,18 +5798,18 @@ var PiSessionStore = class {
|
|
|
5690
5798
|
if (!SAFE_ID.test(sessionId)) {
|
|
5691
5799
|
throw new Error(`Session not found: ${sessionId}`);
|
|
5692
5800
|
}
|
|
5693
|
-
const direct =
|
|
5801
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5694
5802
|
try {
|
|
5695
5803
|
await fsStat(direct);
|
|
5696
5804
|
return direct;
|
|
5697
5805
|
} catch {
|
|
5698
5806
|
}
|
|
5699
|
-
const files = await
|
|
5807
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5700
5808
|
const match = files.find(
|
|
5701
5809
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
5702
5810
|
);
|
|
5703
5811
|
if (!match) throw new Error(`Session not found: ${sessionId}`);
|
|
5704
|
-
const matchedPath =
|
|
5812
|
+
const matchedPath = join10(this.sessionDir, match);
|
|
5705
5813
|
if (!isTimestampNamedPiSessionFile(matchedPath, sessionId)) return matchedPath;
|
|
5706
5814
|
const existingWrapper = await this.findWrapperReferencingNativeSession(matchedPath);
|
|
5707
5815
|
if (existingWrapper) {
|
|
@@ -5866,7 +5974,7 @@ var PiSessionStore = class {
|
|
|
5866
5974
|
try {
|
|
5867
5975
|
const files = readdirSync(this.sessionDir).filter((file) => file.endsWith(".jsonl"));
|
|
5868
5976
|
for (const file of files) {
|
|
5869
|
-
const filepath =
|
|
5977
|
+
const filepath = join10(this.sessionDir, file);
|
|
5870
5978
|
if (resolve7(filepath) === resolvedNativePath) continue;
|
|
5871
5979
|
try {
|
|
5872
5980
|
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries(readJsonlPrefixSync(filepath)));
|
|
@@ -5881,10 +5989,10 @@ var PiSessionStore = class {
|
|
|
5881
5989
|
}
|
|
5882
5990
|
async findWrapperReferencingNativeSession(nativePath) {
|
|
5883
5991
|
const resolvedNativePath = resolve7(nativePath);
|
|
5884
|
-
const files = await
|
|
5992
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5885
5993
|
for (const file of files) {
|
|
5886
5994
|
if (!file.endsWith(".jsonl")) continue;
|
|
5887
|
-
const filepath =
|
|
5995
|
+
const filepath = join10(this.sessionDir, file);
|
|
5888
5996
|
if (resolve7(filepath) === resolvedNativePath) continue;
|
|
5889
5997
|
try {
|
|
5890
5998
|
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries(await readJsonlPrefix(filepath)));
|
|
@@ -5895,7 +6003,7 @@ var PiSessionStore = class {
|
|
|
5895
6003
|
return null;
|
|
5896
6004
|
}
|
|
5897
6005
|
ensureWrapperForNativeSessionSync(sessionId, nativePath, entries) {
|
|
5898
|
-
const wrapperPath =
|
|
6006
|
+
const wrapperPath = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5899
6007
|
if (resolve7(wrapperPath) === resolve7(nativePath)) return wrapperPath;
|
|
5900
6008
|
try {
|
|
5901
6009
|
readFileSync(wrapperPath, "utf-8");
|
|
@@ -5915,7 +6023,7 @@ var PiSessionStore = class {
|
|
|
5915
6023
|
return wrapperPath;
|
|
5916
6024
|
}
|
|
5917
6025
|
async ensureWrapperForNativeSession(sessionId, nativePath) {
|
|
5918
|
-
const wrapperPath =
|
|
6026
|
+
const wrapperPath = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5919
6027
|
if (resolve7(wrapperPath) === resolve7(nativePath)) return wrapperPath;
|
|
5920
6028
|
try {
|
|
5921
6029
|
await fsStat(wrapperPath);
|
|
@@ -6294,6 +6402,10 @@ var PYTHON_RUNTIME_GUIDELINE = [
|
|
|
6294
6402
|
function composeSystemPromptAppend(hostAppend) {
|
|
6295
6403
|
return [WORKSPACE_PATHS_GUIDELINE, PYTHON_RUNTIME_GUIDELINE, hostAppend?.trim()].filter(Boolean).join("\n\n");
|
|
6296
6404
|
}
|
|
6405
|
+
function withPiHarnessDefaults(pi) {
|
|
6406
|
+
const { noContextFiles = true, noSkills = true, ...rest } = pi ?? {};
|
|
6407
|
+
return { ...rest, noContextFiles, noSkills };
|
|
6408
|
+
}
|
|
6297
6409
|
function buildDynamicPromptExtension(source) {
|
|
6298
6410
|
return (pi) => {
|
|
6299
6411
|
pi.on("before_agent_start", async (event) => {
|
|
@@ -6349,8 +6461,8 @@ function mergeInjectedProjectPackages(settingsJson, piPackages) {
|
|
|
6349
6461
|
}
|
|
6350
6462
|
function createResourceSettingsManager(cwd, agentDir, piPackages) {
|
|
6351
6463
|
if (piPackages.length === 0) return SettingsManager2.create(cwd, agentDir);
|
|
6352
|
-
const globalSettingsPath =
|
|
6353
|
-
const projectSettingsPath =
|
|
6464
|
+
const globalSettingsPath = join11(agentDir, "settings.json");
|
|
6465
|
+
const projectSettingsPath = join11(cwd, ".pi", "settings.json");
|
|
6354
6466
|
let globalSettingsOverrideJson;
|
|
6355
6467
|
let projectSettingsOverrideJson;
|
|
6356
6468
|
const storage = {
|
|
@@ -6380,7 +6492,7 @@ async function applyRequestedSessionOptions(handle, input) {
|
|
|
6380
6492
|
handle.piSession.setThinkingLevel(input.thinkingLevel);
|
|
6381
6493
|
}
|
|
6382
6494
|
}
|
|
6383
|
-
var
|
|
6495
|
+
var log4 = createLogger("pi-harness");
|
|
6384
6496
|
function deriveSourcePlugin(sourceInfo) {
|
|
6385
6497
|
if (!sourceInfo) return void 0;
|
|
6386
6498
|
const path4 = typeof sourceInfo.path === "string" ? sourceInfo.path : "";
|
|
@@ -6405,6 +6517,7 @@ function normalizeSlashCommandInfo(command) {
|
|
|
6405
6517
|
};
|
|
6406
6518
|
}
|
|
6407
6519
|
function createPiCodingAgentHarness(opts) {
|
|
6520
|
+
const pi = withPiHarnessDefaults(opts.pi);
|
|
6408
6521
|
const sessionStore = new PiSessionStore(opts.runtimeCwd ?? opts.cwd, {
|
|
6409
6522
|
sessionNamespace: opts.sessionNamespace,
|
|
6410
6523
|
sessionDir: opts.sessionDir,
|
|
@@ -6415,32 +6528,48 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6415
6528
|
const effectivePackages = [];
|
|
6416
6529
|
const effectiveExtensionPaths = [];
|
|
6417
6530
|
const refreshEffectiveResources = () => {
|
|
6418
|
-
const dynamic =
|
|
6531
|
+
const dynamic = pi.getHotReloadableResources?.() ?? {};
|
|
6419
6532
|
effectiveSkillPaths.splice(
|
|
6420
6533
|
0,
|
|
6421
6534
|
effectiveSkillPaths.length,
|
|
6422
|
-
...
|
|
6535
|
+
...pi.additionalSkillPaths ?? [],
|
|
6423
6536
|
...dynamic.additionalSkillPaths ?? []
|
|
6424
6537
|
);
|
|
6425
6538
|
effectivePackages.splice(
|
|
6426
6539
|
0,
|
|
6427
6540
|
effectivePackages.length,
|
|
6428
|
-
...mergePiPackageSources(
|
|
6541
|
+
...mergePiPackageSources(pi.packages ?? [], dynamic.packages ?? [])
|
|
6429
6542
|
);
|
|
6430
6543
|
effectiveExtensionPaths.splice(
|
|
6431
6544
|
0,
|
|
6432
6545
|
effectiveExtensionPaths.length,
|
|
6433
|
-
...
|
|
6546
|
+
...pi.extensionPaths ?? [],
|
|
6434
6547
|
...dynamic.extensionPaths ?? []
|
|
6435
6548
|
);
|
|
6436
6549
|
};
|
|
6437
6550
|
refreshEffectiveResources();
|
|
6551
|
+
const piSessionCreations = /* @__PURE__ */ new Map();
|
|
6438
6552
|
async function getOrCreatePiSession(sessionId, input, ctx) {
|
|
6439
6553
|
const existing = piSessions.get(sessionId);
|
|
6440
6554
|
if (existing) {
|
|
6441
6555
|
await applyRequestedSessionOptions(existing, input);
|
|
6442
6556
|
return existing;
|
|
6443
6557
|
}
|
|
6558
|
+
const inFlight = piSessionCreations.get(sessionId);
|
|
6559
|
+
if (inFlight) {
|
|
6560
|
+
const handle = await inFlight;
|
|
6561
|
+
await applyRequestedSessionOptions(handle, input);
|
|
6562
|
+
return handle;
|
|
6563
|
+
}
|
|
6564
|
+
const creation = createPiSession(sessionId, input, ctx);
|
|
6565
|
+
piSessionCreations.set(sessionId, creation);
|
|
6566
|
+
try {
|
|
6567
|
+
return await creation;
|
|
6568
|
+
} finally {
|
|
6569
|
+
if (piSessionCreations.get(sessionId) === creation) piSessionCreations.delete(sessionId);
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
async function createPiSession(sessionId, input, ctx) {
|
|
6444
6573
|
const authStorage = AuthStorage.create();
|
|
6445
6574
|
const modelRegistry = ModelRegistry.create(authStorage);
|
|
6446
6575
|
registerConfiguredModelProviders(modelRegistry);
|
|
@@ -6471,7 +6600,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6471
6600
|
const extensionFactories = [
|
|
6472
6601
|
toolErrorResultExtension,
|
|
6473
6602
|
...dynamicPromptExtension ? [dynamicPromptExtension] : [],
|
|
6474
|
-
...
|
|
6603
|
+
...pi.extensionFactories ?? []
|
|
6475
6604
|
];
|
|
6476
6605
|
const settingsManager = createResourceSettingsManager(
|
|
6477
6606
|
opts.cwd,
|
|
@@ -6485,8 +6614,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6485
6614
|
appendSystemPromptOverride: (base) => [...base, composedSystemPromptAppend],
|
|
6486
6615
|
...effectiveExtensionPaths.length ? { additionalExtensionPaths: effectiveExtensionPaths } : {},
|
|
6487
6616
|
...extensionFactories.length ? { extensionFactories } : {},
|
|
6488
|
-
...
|
|
6489
|
-
...
|
|
6617
|
+
...pi.noContextFiles ? { noContextFiles: true } : {},
|
|
6618
|
+
...pi.noSkills ? { noSkills: true } : {},
|
|
6490
6619
|
...effectiveSkillPaths.length ? { additionalSkillPaths: effectiveSkillPaths } : {},
|
|
6491
6620
|
// skillsOverride REPLACES Pi's resolved skill set, which includes
|
|
6492
6621
|
// skills contributed by host-declared pi packages (e.g.
|
|
@@ -6495,7 +6624,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6495
6624
|
// Passing additionalSkillPaths is not, by itself, a request to throw
|
|
6496
6625
|
// away package skills — those should keep flowing through Pi's loader
|
|
6497
6626
|
// and merge with the additional paths.
|
|
6498
|
-
...
|
|
6627
|
+
...pi.noSkills ? {
|
|
6499
6628
|
skillsOverride: () => loadSkills({
|
|
6500
6629
|
cwd: opts.cwd,
|
|
6501
6630
|
agentDir,
|
|
@@ -6626,12 +6755,12 @@ ${entry.message}`;
|
|
|
6626
6755
|
}
|
|
6627
6756
|
|
|
6628
6757
|
// src/server/harness/pi-coding-agent/pluginLoader.ts
|
|
6629
|
-
import { readdir as
|
|
6630
|
-
import { join as
|
|
6758
|
+
import { readdir as readdir8, stat as stat9, readFile as readFile10 } from "fs/promises";
|
|
6759
|
+
import { join as join12, extname as extname4, resolve as resolve8, sep as sep6, relative as relative9 } from "path";
|
|
6631
6760
|
import { homedir as homedir4 } from "os";
|
|
6632
6761
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6633
6762
|
var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs"]);
|
|
6634
|
-
var GLOBAL_DIR =
|
|
6763
|
+
var GLOBAL_DIR = join12(homedir4(), ".pi", "agent", "extensions");
|
|
6635
6764
|
var LOCAL_DIR = ".pi/extensions";
|
|
6636
6765
|
var EXTENSIONS_JSON = ".pi/extensions.json";
|
|
6637
6766
|
async function dirExists2(path4) {
|
|
@@ -6652,8 +6781,8 @@ async function fileExists(path4) {
|
|
|
6652
6781
|
}
|
|
6653
6782
|
async function discoverFromDir(dir, source) {
|
|
6654
6783
|
if (!await dirExists2(dir)) return [];
|
|
6655
|
-
const entries = await
|
|
6656
|
-
return entries.filter((e) => VALID_EXTENSIONS.has(extname4(e))).map((e) => ({ path:
|
|
6784
|
+
const entries = await readdir8(dir);
|
|
6785
|
+
return entries.filter((e) => VALID_EXTENSIONS.has(extname4(e))).map((e) => ({ path: join12(dir, e), source }));
|
|
6657
6786
|
}
|
|
6658
6787
|
function extractTools(mod) {
|
|
6659
6788
|
const tools = [];
|
|
@@ -6683,17 +6812,17 @@ async function loadModule(filePath, importFn) {
|
|
|
6683
6812
|
return extractTools(mod);
|
|
6684
6813
|
}
|
|
6685
6814
|
async function discoverNpmPlugins(cwd) {
|
|
6686
|
-
const nodeModulesDir =
|
|
6815
|
+
const nodeModulesDir = join12(cwd, "node_modules");
|
|
6687
6816
|
if (!await dirExists2(nodeModulesDir)) return [];
|
|
6688
6817
|
try {
|
|
6689
|
-
const entries = await
|
|
6690
|
-
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) =>
|
|
6818
|
+
const entries = await readdir8(nodeModulesDir);
|
|
6819
|
+
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) => join12(nodeModulesDir, e));
|
|
6691
6820
|
} catch {
|
|
6692
6821
|
return [];
|
|
6693
6822
|
}
|
|
6694
6823
|
}
|
|
6695
6824
|
async function loadNpmPlugin(pkgDir, importFn) {
|
|
6696
|
-
const pkgJsonPath =
|
|
6825
|
+
const pkgJsonPath = join12(pkgDir, "package.json");
|
|
6697
6826
|
if (!await fileExists(pkgJsonPath)) return [];
|
|
6698
6827
|
const pkgJson = JSON.parse(await readFile10(pkgJsonPath, "utf-8"));
|
|
6699
6828
|
const main = pkgJson.main ?? "index.js";
|
|
@@ -6709,7 +6838,7 @@ async function loadNpmPlugin(pkgDir, importFn) {
|
|
|
6709
6838
|
return loadModule(resolvedMain, importFn);
|
|
6710
6839
|
}
|
|
6711
6840
|
async function loadExtensionsJson(cwd) {
|
|
6712
|
-
const configPath =
|
|
6841
|
+
const configPath = join12(cwd, EXTENSIONS_JSON);
|
|
6713
6842
|
if (!await fileExists(configPath)) return null;
|
|
6714
6843
|
try {
|
|
6715
6844
|
const raw = await readFile10(configPath, "utf-8");
|
|
@@ -6727,7 +6856,7 @@ async function loadPlugins(options) {
|
|
|
6727
6856
|
const globals = await discoverFromDir(GLOBAL_DIR, "global");
|
|
6728
6857
|
candidates.push(...globals);
|
|
6729
6858
|
}
|
|
6730
|
-
const localDir =
|
|
6859
|
+
const localDir = join12(options.cwd, LOCAL_DIR);
|
|
6731
6860
|
const locals = await discoverFromDir(localDir, "local");
|
|
6732
6861
|
candidates.push(...locals);
|
|
6733
6862
|
const npmDirs = await discoverNpmPlugins(options.cwd);
|
|
@@ -6737,7 +6866,7 @@ async function loadPlugins(options) {
|
|
|
6737
6866
|
const config = await loadExtensionsJson(options.cwd);
|
|
6738
6867
|
if (config?.npm) {
|
|
6739
6868
|
for (const pkg of config.npm) {
|
|
6740
|
-
const pkgDir =
|
|
6869
|
+
const pkgDir = join12(options.cwd, "node_modules", pkg);
|
|
6741
6870
|
if (await dirExists2(pkgDir)) {
|
|
6742
6871
|
const already = candidates.some((c) => c.path === pkgDir);
|
|
6743
6872
|
if (!already) {
|
|
@@ -6786,8 +6915,8 @@ import {
|
|
|
6786
6915
|
|
|
6787
6916
|
// src/server/tools/operations/bound.ts
|
|
6788
6917
|
import { constants as constants4 } from "fs";
|
|
6789
|
-
import { access as access4, lstat as lstat4, mkdir as mkdir12, readFile as readFile11, readdir as
|
|
6790
|
-
import { dirname as dirname11, isAbsolute as isAbsolute7, join as
|
|
6918
|
+
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";
|
|
6919
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join13, relative as relative10, resolve as resolve9 } from "path";
|
|
6791
6920
|
function toPosixPath(value) {
|
|
6792
6921
|
return value.split("\\").join("/");
|
|
6793
6922
|
}
|
|
@@ -6840,7 +6969,7 @@ function shouldSkipDir(relativePath, ignore) {
|
|
|
6840
6969
|
}
|
|
6841
6970
|
async function walkMatches(root, current, pattern, ignore, limit, out) {
|
|
6842
6971
|
if (out.length >= limit) return;
|
|
6843
|
-
const entries = await
|
|
6972
|
+
const entries = await readdir9(current, { withFileTypes: true });
|
|
6844
6973
|
for (const entry of entries) {
|
|
6845
6974
|
if (out.length >= limit) return;
|
|
6846
6975
|
const absolutePath = resolve9(current, entry.name);
|
|
@@ -6918,7 +7047,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
6918
7047
|
const normalized = toPosixPath(absolutePath);
|
|
6919
7048
|
if (normalized === runtimeRoot) return workspaceRoot;
|
|
6920
7049
|
if (normalized.startsWith(`${runtimeRoot}/`)) {
|
|
6921
|
-
return
|
|
7050
|
+
return join13(workspaceRoot, ...normalized.slice(runtimeRoot.length + 1).split("/"));
|
|
6922
7051
|
}
|
|
6923
7052
|
return absolutePath;
|
|
6924
7053
|
};
|
|
@@ -7022,7 +7151,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7022
7151
|
async readdir(absolutePath) {
|
|
7023
7152
|
const storagePath = toStoragePath(absolutePath);
|
|
7024
7153
|
await assertWithinWorkspace(workspaceRoot, storagePath);
|
|
7025
|
-
return await
|
|
7154
|
+
return await readdir9(storagePath);
|
|
7026
7155
|
}
|
|
7027
7156
|
};
|
|
7028
7157
|
return { read, write, edit, find, grep, ls };
|
|
@@ -7588,7 +7717,8 @@ function directSpawnHook(workspaceRoot, runtime) {
|
|
|
7588
7717
|
...context,
|
|
7589
7718
|
env: withWorkspacePythonEnv({
|
|
7590
7719
|
workspaceRoot,
|
|
7591
|
-
env: mergeRuntimeEnv(runtime, context.env)
|
|
7720
|
+
env: mergeRuntimeEnv(runtime, context.env),
|
|
7721
|
+
preserveHostHome: true
|
|
7592
7722
|
})
|
|
7593
7723
|
});
|
|
7594
7724
|
}
|
|
@@ -7855,11 +7985,20 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7855
7985
|
const ensureBroadcaster = async (request) => {
|
|
7856
7986
|
const workspace = opts.getWorkspace ? await opts.getWorkspace(request) : opts.workspace;
|
|
7857
7987
|
if (!workspace) throw new Error("fs event route requires workspace or getWorkspace");
|
|
7858
|
-
if (typeof workspace.watch !== "function")
|
|
7988
|
+
if (typeof workspace.watch !== "function") {
|
|
7989
|
+
return { unsupported: { reason: "watch_not_implemented" } };
|
|
7990
|
+
}
|
|
7859
7991
|
const workspaceId = request.workspaceContext?.workspaceId ?? "default";
|
|
7860
7992
|
const existing = broadcasters.get(workspaceId);
|
|
7861
7993
|
if (existing) return { workspaceId, entry: existing };
|
|
7862
|
-
const
|
|
7994
|
+
const watcher = workspace.watch();
|
|
7995
|
+
const readiness = await watcher.whenReady?.() ?? { ok: true };
|
|
7996
|
+
if (!readiness.ok) {
|
|
7997
|
+
return { unsupported: { reason: readiness.reason, ...readiness.message ? { message: readiness.message } : {} } };
|
|
7998
|
+
}
|
|
7999
|
+
const existingAfterWait = broadcasters.get(workspaceId);
|
|
8000
|
+
if (existingAfterWait) return { workspaceId, entry: existingAfterWait };
|
|
8001
|
+
const broadcaster = createFsEventBroadcaster(watcher);
|
|
7863
8002
|
const entry = { broadcaster, subscribers: 0 };
|
|
7864
8003
|
broadcasters.set(workspaceId, entry);
|
|
7865
8004
|
return { workspaceId, entry };
|
|
@@ -7879,8 +8018,8 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7879
8018
|
const resolved = await ensureBroadcaster(request);
|
|
7880
8019
|
reply.hijack();
|
|
7881
8020
|
setupSse(request, reply.raw);
|
|
7882
|
-
if (
|
|
7883
|
-
writeSse(reply.raw, "unsupported",
|
|
8021
|
+
if ("unsupported" in resolved) {
|
|
8022
|
+
writeSse(reply.raw, "unsupported", resolved.unsupported);
|
|
7884
8023
|
reply.raw.end();
|
|
7885
8024
|
return;
|
|
7886
8025
|
}
|
|
@@ -7990,7 +8129,7 @@ async function listTree(workspace, dir, recursive) {
|
|
|
7990
8129
|
if (entries.length >= MAX_ENTRIES) break;
|
|
7991
8130
|
const entryPath = joinPath2(batch.parentDir, e.name);
|
|
7992
8131
|
entries.push({ name: e.name, kind: e.kind, path: entryPath });
|
|
7993
|
-
if (recursive && e.kind === "dir" && batch.depth < MAX_DEPTH) {
|
|
8132
|
+
if (recursive && e.kind === "dir" && batch.depth < MAX_DEPTH && !isIgnoredDirName(e.name)) {
|
|
7994
8133
|
try {
|
|
7995
8134
|
const subEntries = await workspace.readdir(entryPath);
|
|
7996
8135
|
queue.push({ parentDir: entryPath, items: subEntries, depth: batch.depth + 1 });
|
|
@@ -8106,8 +8245,8 @@ function skillsRoutes(app, opts, done) {
|
|
|
8106
8245
|
const workspaceRoot = opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(request) : opts.workspaceRoot;
|
|
8107
8246
|
const additionalSkillPaths = opts.getAdditionalSkillPaths ? await opts.getAdditionalSkillPaths(request) : opts.additionalSkillPaths;
|
|
8108
8247
|
const piPackages = opts.getPiPackages ? await opts.getPiPackages(request) : opts.piPackages;
|
|
8109
|
-
const noSkills = opts.getNoSkills ? await opts.getNoSkills(request) : opts.noSkills;
|
|
8110
|
-
const cacheKey = JSON.stringify([workspaceRoot, additionalSkillPaths ?? [], piPackages ?? [],
|
|
8248
|
+
const noSkills = (opts.getNoSkills ? await opts.getNoSkills(request) : opts.noSkills) ?? withPiHarnessDefaults().noSkills;
|
|
8249
|
+
const cacheKey = JSON.stringify([workspaceRoot, additionalSkillPaths ?? [], piPackages ?? [], noSkills]);
|
|
8111
8250
|
const now = Date.now();
|
|
8112
8251
|
for (const [key, entry] of cached) {
|
|
8113
8252
|
if (entry.expiresAt <= now) cached.delete(key);
|
|
@@ -8136,7 +8275,7 @@ function skillsRoutes(app, opts, done) {
|
|
|
8136
8275
|
cwd: workspaceRoot,
|
|
8137
8276
|
agentDir,
|
|
8138
8277
|
skillPaths: [...packageSkillPaths, ...additionalSkillPaths ?? []],
|
|
8139
|
-
includeDefaults:
|
|
8278
|
+
includeDefaults: !noSkills
|
|
8140
8279
|
});
|
|
8141
8280
|
const skills = result.skills.map((s) => ({
|
|
8142
8281
|
name: s.name,
|
|
@@ -10089,6 +10228,11 @@ var HarnessPiChatService = class {
|
|
|
10089
10228
|
sessionStore;
|
|
10090
10229
|
workdir;
|
|
10091
10230
|
channels = /* @__PURE__ */ new Map();
|
|
10231
|
+
// Single-flight guard so concurrent cold callers (e.g. two browser tabs each
|
|
10232
|
+
// opening /events while the session is still being created) converge on one
|
|
10233
|
+
// LiveSessionChannel instead of racing through ensureChannel and orphaning
|
|
10234
|
+
// the loser's adapter subscription.
|
|
10235
|
+
channelCreations = /* @__PURE__ */ new Map();
|
|
10092
10236
|
messageMetadata = new PiChatMessageMetadataReconciler();
|
|
10093
10237
|
activePromptRuns = /* @__PURE__ */ new Map();
|
|
10094
10238
|
syntheticPromptFailures = /* @__PURE__ */ new Map();
|
|
@@ -10348,10 +10492,38 @@ var HarnessPiChatService = class {
|
|
|
10348
10492
|
async getChannel(ctx, sessionId) {
|
|
10349
10493
|
const existing = this.channels.get(sessionId);
|
|
10350
10494
|
if (existing) return existing;
|
|
10351
|
-
|
|
10352
|
-
return this.ensureChannel(ctx, sessionId, adapter);
|
|
10495
|
+
return this.createChannelOnce(sessionId, () => this.getAdapter(ctx, sessionId, ""));
|
|
10353
10496
|
}
|
|
10354
10497
|
async ensureChannel(ctx, sessionId, adapter) {
|
|
10498
|
+
const existing = this.channels.get(sessionId);
|
|
10499
|
+
if (existing) return existing;
|
|
10500
|
+
return this.createChannelOnce(sessionId, async () => adapter);
|
|
10501
|
+
}
|
|
10502
|
+
/**
|
|
10503
|
+
* Resolve (or create) the single LiveSessionChannel for a session, coalescing
|
|
10504
|
+
* concurrent cold callers onto one in-flight creation. Without this guard two
|
|
10505
|
+
* tabs opening /events on a not-yet-live session both fall through the channel
|
|
10506
|
+
* cache miss, build separate channels + adapter subscriptions, and the second
|
|
10507
|
+
* `this.channels.set` orphans the first — so the first tab's stream silently
|
|
10508
|
+
* stops receiving events.
|
|
10509
|
+
*/
|
|
10510
|
+
async createChannelOnce(sessionId, resolveAdapter) {
|
|
10511
|
+
const inFlight = this.channelCreations.get(sessionId);
|
|
10512
|
+
if (inFlight) return inFlight;
|
|
10513
|
+
const creation = (async () => {
|
|
10514
|
+
const existing = this.channels.get(sessionId);
|
|
10515
|
+
if (existing) return existing;
|
|
10516
|
+
const adapter = await resolveAdapter();
|
|
10517
|
+
return this.buildChannel(sessionId, adapter);
|
|
10518
|
+
})();
|
|
10519
|
+
this.channelCreations.set(sessionId, creation);
|
|
10520
|
+
try {
|
|
10521
|
+
return await creation;
|
|
10522
|
+
} finally {
|
|
10523
|
+
if (this.channelCreations.get(sessionId) === creation) this.channelCreations.delete(sessionId);
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10526
|
+
buildChannel(sessionId, adapter) {
|
|
10355
10527
|
const existing = this.channels.get(sessionId);
|
|
10356
10528
|
if (existing) return existing;
|
|
10357
10529
|
const buffer = new PiChatReplayBuffer();
|
|
@@ -10516,7 +10688,7 @@ async function createAgentApp(opts = {}) {
|
|
|
10516
10688
|
}
|
|
10517
10689
|
const getRuntimeProvisioning = opts.getRuntimeProvisioning ?? (() => opts.runtimeProvisioning);
|
|
10518
10690
|
const runtimePi = {
|
|
10519
|
-
...opts.pi,
|
|
10691
|
+
...withPiHarnessDefaults(opts.pi),
|
|
10520
10692
|
additionalSkillPaths: [
|
|
10521
10693
|
...getRuntimeProvisioning()?.skillPaths ?? [],
|
|
10522
10694
|
...opts.pi?.additionalSkillPaths ?? []
|
|
@@ -10544,11 +10716,7 @@ async function createAgentApp(opts = {}) {
|
|
|
10544
10716
|
];
|
|
10545
10717
|
const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
|
|
10546
10718
|
...input,
|
|
10547
|
-
pi:
|
|
10548
|
-
noContextFiles: true,
|
|
10549
|
-
noSkills: true,
|
|
10550
|
-
...runtimePi
|
|
10551
|
-
}
|
|
10719
|
+
pi: runtimePi
|
|
10552
10720
|
}));
|
|
10553
10721
|
const harness = await harnessFactory({
|
|
10554
10722
|
tools,
|
|
@@ -10684,7 +10852,7 @@ function mergeTools(options) {
|
|
|
10684
10852
|
|
|
10685
10853
|
// src/server/tools/upload/index.ts
|
|
10686
10854
|
import { readFile as readFile12 } from "fs/promises";
|
|
10687
|
-
import { extname as extname5, join as
|
|
10855
|
+
import { extname as extname5, join as join14 } from "path";
|
|
10688
10856
|
var DEFAULT_UPLOAD_DIR = "assets/images";
|
|
10689
10857
|
var MAX_UPLOAD_BYTES2 = 10 * 1024 * 1024;
|
|
10690
10858
|
function contentTypeFromExt(path4) {
|
|
@@ -10753,7 +10921,7 @@ function buildUploadAgentTools(bundle) {
|
|
|
10753
10921
|
const rawDir = typeof input.directory === "string" ? input.directory.trim() : "";
|
|
10754
10922
|
const dir = rawDir ? rawDir.replace(/^\.\/+/, "").replace(/\/+$/, "") : DEFAULT_UPLOAD_DIR;
|
|
10755
10923
|
try {
|
|
10756
|
-
const bytes = workspace.readBinaryFile ? Buffer.from(await workspace.readBinaryFile(filePath)) : await readFile12(
|
|
10924
|
+
const bytes = workspace.readBinaryFile ? Buffer.from(await workspace.readBinaryFile(filePath)) : await readFile12(join14(storageRoot, filePath));
|
|
10757
10925
|
if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES2) {
|
|
10758
10926
|
return {
|
|
10759
10927
|
content: [{ type: "text", text: `file must be between 1 byte and ${MAX_UPLOAD_BYTES2} bytes` }],
|
|
@@ -10916,10 +11084,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
10916
11084
|
runtimeBindings.delete(keys[i]);
|
|
10917
11085
|
}
|
|
10918
11086
|
}
|
|
11087
|
+
async function resolveScopePi(workspaceId, root, request) {
|
|
11088
|
+
return withPiHarnessDefaults(opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot: root, request }) : opts.pi);
|
|
11089
|
+
}
|
|
10919
11090
|
async function resolveRuntimeScope(workspaceId, request) {
|
|
10920
11091
|
const root = request && opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(workspaceId, request) : workspaceRoot;
|
|
10921
11092
|
const scopedTemplatePath = opts.getTemplatePath ? await opts.getTemplatePath({ workspaceId, workspaceRoot: root, request }) : templatePath;
|
|
10922
|
-
const pi =
|
|
11093
|
+
const pi = await resolveScopePi(workspaceId, root, request);
|
|
10923
11094
|
const sessionNamespace = normalizeSessionNamespace(opts.getSessionNamespace ? await opts.getSessionNamespace({ workspaceId, workspaceRoot: root, request }) : opts.sessionNamespace);
|
|
10924
11095
|
return {
|
|
10925
11096
|
root,
|
|
@@ -10931,29 +11102,29 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
10931
11102
|
workspaceId,
|
|
10932
11103
|
root,
|
|
10933
11104
|
scopedTemplatePath ?? null,
|
|
10934
|
-
pi
|
|
11105
|
+
pi,
|
|
10935
11106
|
sessionNamespace ?? null
|
|
10936
11107
|
])
|
|
10937
11108
|
};
|
|
10938
11109
|
}
|
|
10939
11110
|
async function resolveSkillScope(workspaceId, request) {
|
|
10940
11111
|
const root = request && opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(workspaceId, request) : workspaceRoot;
|
|
10941
|
-
const pi =
|
|
10942
|
-
const hot = pi
|
|
11112
|
+
const pi = await resolveScopePi(workspaceId, root, request);
|
|
11113
|
+
const hot = pi.getHotReloadableResources?.();
|
|
10943
11114
|
return {
|
|
10944
11115
|
root,
|
|
10945
11116
|
pi: hot ? {
|
|
10946
11117
|
...pi,
|
|
10947
11118
|
additionalSkillPaths: [
|
|
10948
|
-
...pi
|
|
11119
|
+
...pi.additionalSkillPaths ?? [],
|
|
10949
11120
|
...hot.additionalSkillPaths ?? []
|
|
10950
11121
|
],
|
|
10951
11122
|
packages: [
|
|
10952
|
-
...pi
|
|
11123
|
+
...pi.packages ?? [],
|
|
10953
11124
|
...hot.packages ?? []
|
|
10954
11125
|
],
|
|
10955
11126
|
extensionPaths: [
|
|
10956
|
-
...pi
|
|
11127
|
+
...pi.extensionPaths ?? [],
|
|
10957
11128
|
...hot.extensionPaths ?? []
|
|
10958
11129
|
]
|
|
10959
11130
|
} : pi
|
|
@@ -11130,14 +11301,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11130
11301
|
const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
|
|
11131
11302
|
...input,
|
|
11132
11303
|
pi: {
|
|
11133
|
-
|
|
11134
|
-
noSkills: true,
|
|
11304
|
+
// scope.pi is already defaulted at the resolveScopePi chokepoint.
|
|
11135
11305
|
...scope.pi,
|
|
11136
11306
|
additionalSkillPaths: [
|
|
11137
|
-
...scope.pi
|
|
11307
|
+
...scope.pi.additionalSkillPaths ?? []
|
|
11138
11308
|
],
|
|
11139
11309
|
getHotReloadableResources: () => {
|
|
11140
|
-
const hot = scope.pi
|
|
11310
|
+
const hot = scope.pi.getHotReloadableResources?.() ?? {};
|
|
11141
11311
|
return {
|
|
11142
11312
|
...hot,
|
|
11143
11313
|
additionalSkillPaths: [
|
|
@@ -11381,19 +11551,21 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11381
11551
|
...opts.pi?.additionalSkillPaths ?? []
|
|
11382
11552
|
],
|
|
11383
11553
|
piPackages: opts.pi?.packages,
|
|
11554
|
+
// Undefined is fine: skillsRoutes resolves it through the canonical
|
|
11555
|
+
// harness policy (withPiHarnessDefaults), same as the factory above.
|
|
11384
11556
|
noSkills: opts.pi?.noSkills,
|
|
11385
11557
|
getWorkspaceRoot: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).root,
|
|
11386
11558
|
getAdditionalSkillPaths: staticBinding && !hasRuntimeProvisioningInput ? void 0 : async (request) => {
|
|
11387
11559
|
const scope = await getSkillsScopeForRequest(request);
|
|
11388
|
-
if (!hasRuntimeProvisioningInput) return scope.pi
|
|
11560
|
+
if (!hasRuntimeProvisioningInput) return scope.pi.additionalSkillPaths;
|
|
11389
11561
|
const binding = await getBindingForRequest(request);
|
|
11390
11562
|
return [
|
|
11391
11563
|
...binding.runtimeProvisioning?.skillPaths ?? [],
|
|
11392
|
-
...scope.pi
|
|
11564
|
+
...scope.pi.additionalSkillPaths ?? []
|
|
11393
11565
|
];
|
|
11394
11566
|
},
|
|
11395
|
-
getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi
|
|
11396
|
-
getNoSkills: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi
|
|
11567
|
+
getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi.packages,
|
|
11568
|
+
getNoSkills: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi.noSkills
|
|
11397
11569
|
});
|
|
11398
11570
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
11399
11571
|
app.post("/api/v1/agent/reload", async (request, reply) => {
|