@hachej/boring-agent 0.1.41 → 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/server/index.d.ts +2 -2
- package/dist/server/index.js +363 -246
- 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
|
@@ -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";
|
|
@@ -345,9 +345,8 @@ function buildBwrapArgs(workspaceRoot, options) {
|
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
// 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";
|
|
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";
|
|
351
350
|
|
|
352
351
|
// src/server/workspace/paths.ts
|
|
353
352
|
import { lstat, realpath, stat } from "fs/promises";
|
|
@@ -428,6 +427,11 @@ async function ensureWritableWorkspacePath(workspaceRoot, relPath) {
|
|
|
428
427
|
return absPath;
|
|
429
428
|
}
|
|
430
429
|
|
|
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
|
+
|
|
431
435
|
// src/server/workspace/ignore.ts
|
|
432
436
|
var DEFAULT_IGNORED_DIR_NAMES = [
|
|
433
437
|
"node_modules",
|
|
@@ -446,19 +450,136 @@ function isIgnoredDirName(name) {
|
|
|
446
450
|
return IGNORED_SET.has(name) || name.endsWith(".tsbuildinfo");
|
|
447
451
|
}
|
|
448
452
|
|
|
449
|
-
// src/server/
|
|
450
|
-
var
|
|
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");
|
|
451
535
|
function shouldIgnoreWatchPath(root, path4) {
|
|
452
536
|
const relPath = relative2(root, path4);
|
|
453
537
|
const parts = relPath.split(sep);
|
|
454
538
|
return parts.some((part) => isIgnoredDirName(part));
|
|
455
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;
|
|
574
|
+
}
|
|
456
575
|
function createNodeWatcher(root) {
|
|
457
576
|
const listeners = /* @__PURE__ */ new Set();
|
|
577
|
+
const suppressions = [];
|
|
458
578
|
let fsw = null;
|
|
459
579
|
let closed = false;
|
|
460
|
-
|
|
461
|
-
|
|
580
|
+
let readiness = null;
|
|
581
|
+
const startFsw = () => {
|
|
582
|
+
if (fsw || closed) return;
|
|
462
583
|
fsw = chokidar.watch(root, {
|
|
463
584
|
ignored: (path4) => shouldIgnoreWatchPath(root, path4),
|
|
464
585
|
ignoreInitial: true,
|
|
@@ -468,23 +589,43 @@ function createNodeWatcher(root) {
|
|
|
468
589
|
// servers. Consumers already reconcile by mtime after invalidation.
|
|
469
590
|
// No native renames from chokidar — `unlinkDir`/`addDir`/
|
|
470
591
|
// `unlink`/`add` are the primitives we get. We surface them as
|
|
471
|
-
// separate `unlink` + `write`/`mkdir` events
|
|
472
|
-
//
|
|
592
|
+
// separate `unlink` + `write`/`mkdir` events. Renames the host
|
|
593
|
+
// performs itself arrive via `emitRename` instead, which also
|
|
594
|
+
// mutes this echo.
|
|
473
595
|
});
|
|
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
|
-
|
|
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
|
+
});
|
|
480
609
|
});
|
|
481
|
-
return fsw;
|
|
482
610
|
};
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
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;
|
|
486
626
|
};
|
|
487
|
-
const
|
|
627
|
+
const rel = (abs) => toPosixRel(root, abs);
|
|
628
|
+
const fanout = (event) => {
|
|
488
629
|
if (closed) return;
|
|
489
630
|
if (event.path === "" || event.path.startsWith("..")) return;
|
|
490
631
|
for (const l of [...listeners]) {
|
|
@@ -494,26 +635,67 @@ function createNodeWatcher(root) {
|
|
|
494
635
|
}
|
|
495
636
|
}
|
|
496
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
|
+
};
|
|
497
657
|
return {
|
|
498
658
|
subscribe(listener) {
|
|
499
659
|
if (closed) return () => {
|
|
500
660
|
};
|
|
501
661
|
listeners.add(listener);
|
|
502
|
-
ensureFsw();
|
|
662
|
+
void ensureFsw();
|
|
503
663
|
return () => {
|
|
504
664
|
listeners.delete(listener);
|
|
505
665
|
};
|
|
506
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
|
+
},
|
|
507
685
|
close() {
|
|
508
686
|
if (closed) return;
|
|
509
687
|
closed = true;
|
|
510
688
|
listeners.clear();
|
|
689
|
+
suppressions.length = 0;
|
|
511
690
|
fsw?.close().catch(() => {
|
|
512
691
|
});
|
|
513
692
|
fsw = null;
|
|
514
693
|
}
|
|
515
694
|
};
|
|
516
695
|
}
|
|
696
|
+
|
|
697
|
+
// src/server/workspace/createNodeWorkspace.ts
|
|
698
|
+
var EPERM_CODE = "EPERM";
|
|
517
699
|
var nodeWorkspaceHostRoots = /* @__PURE__ */ new WeakMap();
|
|
518
700
|
function getNodeWorkspaceHostRoot(workspace) {
|
|
519
701
|
return nodeWorkspaceHostRoots.get(workspace);
|
|
@@ -594,7 +776,7 @@ function createNodeWorkspace(root, opts = {}) {
|
|
|
594
776
|
},
|
|
595
777
|
async readdir(relPath) {
|
|
596
778
|
const absPath = await ensureExistingWorkspacePath(root, relPath);
|
|
597
|
-
const entries = await
|
|
779
|
+
const entries = await readdir2(absPath, { withFileTypes: true });
|
|
598
780
|
return entries.map((entry) => ({
|
|
599
781
|
name: entry.name,
|
|
600
782
|
kind: entry.isDirectory() ? "dir" : "file"
|
|
@@ -632,6 +814,7 @@ function createNodeWorkspace(root, opts = {}) {
|
|
|
632
814
|
const fromAbsPath = await ensureExistingWorkspacePath(root, fromRelPath);
|
|
633
815
|
const toAbsPath = await ensureWritableWorkspacePath(root, toRelPath2);
|
|
634
816
|
await rename(fromAbsPath, toAbsPath);
|
|
817
|
+
cachedWatcher?.emitRename(toPosixRel(root, fromAbsPath), toPosixRel(root, toAbsPath));
|
|
635
818
|
}
|
|
636
819
|
};
|
|
637
820
|
nodeWorkspaceHostRoots.set(workspace, root);
|
|
@@ -757,8 +940,8 @@ async function buildGlobalToolMounts(workspaceRoot) {
|
|
|
757
940
|
if (globalRoot === workspaceRoot) return [];
|
|
758
941
|
const args = [];
|
|
759
942
|
const mountIfChildLacks = async (runtimeRelPath) => {
|
|
760
|
-
const parentRuntimePath =
|
|
761
|
-
const childRuntimePath =
|
|
943
|
+
const parentRuntimePath = join3(globalRoot, runtimeRelPath);
|
|
944
|
+
const childRuntimePath = join3(workspaceRoot, runtimeRelPath);
|
|
762
945
|
if (!await dirExists(parentRuntimePath)) return false;
|
|
763
946
|
if (await pathExists(childRuntimePath)) return false;
|
|
764
947
|
args.push("--ro-bind", parentRuntimePath, `${SANDBOX_HOME2}/${runtimeRelPath}`);
|
|
@@ -1701,86 +1884,6 @@ function createAuthMiddleware(opts = {}) {
|
|
|
1701
1884
|
};
|
|
1702
1885
|
}
|
|
1703
1886
|
|
|
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);
|
|
1780
|
-
}
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
1887
|
// src/server/http/routes/fileRecords.ts
|
|
1785
1888
|
import { extname } from "path/posix";
|
|
1786
1889
|
var MAX_RECORD_FILE_BYTES = 2 * 1024 * 1024;
|
|
@@ -1987,7 +2090,7 @@ function buildFileRecordsResult(args) {
|
|
|
1987
2090
|
}
|
|
1988
2091
|
|
|
1989
2092
|
// src/server/http/routes/file.ts
|
|
1990
|
-
var
|
|
2093
|
+
var log2 = createLogger("boring/workspace-settings");
|
|
1991
2094
|
var BORING_SETTINGS_PATH = ".boring/settings";
|
|
1992
2095
|
var DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR = "assets/images";
|
|
1993
2096
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
@@ -2062,7 +2165,7 @@ function parseWorkspaceSettings(raw) {
|
|
|
2062
2165
|
}
|
|
2063
2166
|
};
|
|
2064
2167
|
} catch (err) {
|
|
2065
|
-
|
|
2168
|
+
log2.warn("failed to parse .boring/settings \u2014 falling back to defaults", {
|
|
2066
2169
|
error: err instanceof Error ? err.message : String(err)
|
|
2067
2170
|
});
|
|
2068
2171
|
return defaultWorkspaceSettings();
|
|
@@ -2410,8 +2513,8 @@ function fileRoutes(app, opts, done) {
|
|
|
2410
2513
|
// src/server/workspace/provisionRuntime.ts
|
|
2411
2514
|
import { createHash as createHash3 } from "crypto";
|
|
2412
2515
|
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
|
|
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";
|
|
2415
2518
|
import { fileURLToPath } from "url";
|
|
2416
2519
|
import { execFile } from "child_process";
|
|
2417
2520
|
import { promisify } from "util";
|
|
@@ -2447,11 +2550,11 @@ async function commandExists(cmd) {
|
|
|
2447
2550
|
async function hashPath(path4, hash) {
|
|
2448
2551
|
const info = await stat4(path4);
|
|
2449
2552
|
if (info.isDirectory()) {
|
|
2450
|
-
const entries = (await
|
|
2553
|
+
const entries = (await readdir3(path4)).filter((entry) => entry !== "__pycache__" && entry !== "build" && !entry.endsWith(".egg-info")).sort();
|
|
2451
2554
|
for (const entry of entries) {
|
|
2452
2555
|
hash.update(`dir:${entry}
|
|
2453
2556
|
`);
|
|
2454
|
-
await hashPath(
|
|
2557
|
+
await hashPath(join4(path4, entry), hash);
|
|
2455
2558
|
}
|
|
2456
2559
|
return;
|
|
2457
2560
|
}
|
|
@@ -2484,16 +2587,16 @@ async function fingerprint(contributions) {
|
|
|
2484
2587
|
const packageRoot = toPath(spec.packageRoot);
|
|
2485
2588
|
hash.update(`node-package:${spec.id}:${spec.packageName}:${packageRoot}
|
|
2486
2589
|
`);
|
|
2487
|
-
const packageJson =
|
|
2488
|
-
const distDir =
|
|
2489
|
-
const docsDir =
|
|
2490
|
-
const skillsDir =
|
|
2491
|
-
const referencesDir =
|
|
2492
|
-
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");
|
|
2493
2596
|
if (await exists(packageJson)) await hashPath(packageJson, hash);
|
|
2494
2597
|
if (await exists(distDir)) await hashPath(distDir, hash);
|
|
2495
|
-
const templatesDir =
|
|
2496
|
-
const publicDir =
|
|
2598
|
+
const templatesDir = join4(packageRoot, "templates");
|
|
2599
|
+
const publicDir = join4(packageRoot, "public");
|
|
2497
2600
|
if (await exists(templatesDir)) await hashPath(templatesDir, hash);
|
|
2498
2601
|
if (await exists(publicDir)) await hashPath(publicDir, hash);
|
|
2499
2602
|
if (await exists(docsDir)) await hashPath(docsDir, hash);
|
|
@@ -2544,7 +2647,7 @@ function nodePackageTarget(workspaceRoot, packageName) {
|
|
|
2544
2647
|
if (parts.length === 0 || parts.some((part) => part === "." || part === "..")) {
|
|
2545
2648
|
throw new Error(`Invalid node package name: ${packageName}`);
|
|
2546
2649
|
}
|
|
2547
|
-
return
|
|
2650
|
+
return join4(workspaceRoot, "node_modules", ...parts);
|
|
2548
2651
|
}
|
|
2549
2652
|
async function copyIfExists(source, target) {
|
|
2550
2653
|
if (!await exists(source)) return false;
|
|
@@ -2562,27 +2665,27 @@ async function ensureNodePackages(workspaceRoot, specs) {
|
|
|
2562
2665
|
const packageRoot = toPath(spec.packageRoot);
|
|
2563
2666
|
const target = nodePackageTarget(workspaceRoot, spec.packageName);
|
|
2564
2667
|
await mkdir4(target, { recursive: true });
|
|
2565
|
-
const copiedPackageJson = await copyIfExists(
|
|
2668
|
+
const copiedPackageJson = await copyIfExists(join4(packageRoot, "package.json"), join4(target, "package.json"));
|
|
2566
2669
|
if (!copiedPackageJson) {
|
|
2567
2670
|
throw new Error(`Node package provisioning source is missing package.json: ${packageRoot}`);
|
|
2568
2671
|
}
|
|
2569
|
-
await copyIfExists(
|
|
2570
|
-
await copyIfExists(
|
|
2571
|
-
await copyIfExists(
|
|
2572
|
-
if (!await exists(
|
|
2573
|
-
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"));
|
|
2574
2677
|
}
|
|
2575
|
-
if (!await copyIfExists(
|
|
2576
|
-
await copyIfExists(
|
|
2678
|
+
if (!await copyIfExists(join4(packageRoot, "docs"), join4(target, "docs"))) {
|
|
2679
|
+
await copyIfExists(join4(packageRoot, "src", "server", "docs"), join4(target, "docs"));
|
|
2577
2680
|
}
|
|
2578
|
-
await copyIfExists(
|
|
2579
|
-
await copyIfExists(
|
|
2580
|
-
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"));
|
|
2581
2684
|
}
|
|
2582
2685
|
}
|
|
2583
2686
|
async function ensurePython(workspaceRoot, specs) {
|
|
2584
2687
|
if (specs.length === 0) return;
|
|
2585
|
-
const venvPython =
|
|
2688
|
+
const venvPython = join4(workspaceRoot, ".boring-agent", "venv", "bin", "python");
|
|
2586
2689
|
const uv = await commandExists("uv");
|
|
2587
2690
|
if (!await exists(venvPython)) {
|
|
2588
2691
|
if (uv) await run("uv", ["venv", ".boring-agent/venv"], workspaceRoot);
|
|
@@ -2615,17 +2718,17 @@ function assertEnvKey(key) {
|
|
|
2615
2718
|
}
|
|
2616
2719
|
async function isRuntimeMaterialized(workspaceRoot, contributions) {
|
|
2617
2720
|
const hasPython = contributions.some(({ provisioning }) => (provisioning.python ?? []).length > 0);
|
|
2618
|
-
if (hasPython && !await exists(
|
|
2721
|
+
if (hasPython && !await exists(join4(workspaceRoot, ".boring-agent", "venv", "bin", "python"))) return false;
|
|
2619
2722
|
for (const { provisioning } of contributions) {
|
|
2620
2723
|
for (const spec of provisioning.nodePackages ?? []) {
|
|
2621
|
-
if (!await exists(
|
|
2724
|
+
if (!await exists(join4(nodePackageTarget(workspaceRoot, spec.packageName), "package.json"))) return false;
|
|
2622
2725
|
}
|
|
2623
2726
|
}
|
|
2624
2727
|
return true;
|
|
2625
2728
|
}
|
|
2626
2729
|
async function writeShims(workspaceRoot, env) {
|
|
2627
|
-
const shimDir =
|
|
2628
|
-
const venvBin =
|
|
2730
|
+
const shimDir = join4(workspaceRoot, ".boring-agent", "bin");
|
|
2731
|
+
const venvBin = join4(workspaceRoot, ".boring-agent", "venv", "bin");
|
|
2629
2732
|
await mkdir4(shimDir, { recursive: true });
|
|
2630
2733
|
const exports = Object.entries(env).map(([key, value]) => {
|
|
2631
2734
|
assertEnvKey(key);
|
|
@@ -2639,21 +2742,21 @@ export BORING_AGENT_WORKSPACE_ROOT="$WORKSPACE_ROOT"
|
|
|
2639
2742
|
${exports}
|
|
2640
2743
|
VENV_BIN="$WORKSPACE_ROOT/.boring-agent/venv/bin"
|
|
2641
2744
|
`;
|
|
2642
|
-
await writeExecutable(
|
|
2745
|
+
await writeExecutable(join4(shimDir, "python"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2643
2746
|
`);
|
|
2644
|
-
await writeExecutable(
|
|
2747
|
+
await writeExecutable(join4(shimDir, "python3"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2645
2748
|
`);
|
|
2646
|
-
await writeExecutable(
|
|
2749
|
+
await writeExecutable(join4(shimDir, "pip"), `${base}exec "$VENV_BIN/python" -m pip "$@"
|
|
2647
2750
|
`);
|
|
2648
|
-
await writeExecutable(
|
|
2751
|
+
await writeExecutable(join4(shimDir, "pip3"), `${base}exec "$VENV_BIN/python" -m pip "$@"
|
|
2649
2752
|
`);
|
|
2650
2753
|
if (await exists(venvBin)) {
|
|
2651
|
-
for (const entry of await
|
|
2754
|
+
for (const entry of await readdir3(venvBin)) {
|
|
2652
2755
|
if (["python", "python3", "pip", "pip3"].includes(entry)) continue;
|
|
2653
|
-
const full =
|
|
2756
|
+
const full = join4(venvBin, entry);
|
|
2654
2757
|
const info = await stat4(full).catch(() => null);
|
|
2655
2758
|
if (!info?.isFile()) continue;
|
|
2656
|
-
await writeExecutable(
|
|
2759
|
+
await writeExecutable(join4(shimDir, entry), `${base}TARGET="$VENV_BIN"/${bashSingleQuote(entry)}
|
|
2657
2760
|
SHEBANG="$(head -n 1 "$TARGET" 2>/dev/null || true)"
|
|
2658
2761
|
case "$SHEBANG" in
|
|
2659
2762
|
*python*) exec "$VENV_BIN/python" "$TARGET" "$@" ;;
|
|
@@ -2676,8 +2779,8 @@ async function provisionRuntimeWorkspace({
|
|
|
2676
2779
|
await mkdir4(workspaceRoot, { recursive: true });
|
|
2677
2780
|
const env = collectEnv(active);
|
|
2678
2781
|
const hash = await fingerprint(active);
|
|
2679
|
-
const markerPath =
|
|
2680
|
-
const binDir =
|
|
2782
|
+
const markerPath = join4(workspaceRoot, ".boring-agent", "provisioning.json");
|
|
2783
|
+
const binDir = join4(workspaceRoot, ".boring-agent", "bin");
|
|
2681
2784
|
if (!force && await exists(markerPath)) {
|
|
2682
2785
|
try {
|
|
2683
2786
|
const marker = JSON.parse(await readFile4(markerPath, "utf8"));
|
|
@@ -3115,7 +3218,7 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
3115
3218
|
// src/server/workspace/provisioning/packArtifact.ts
|
|
3116
3219
|
import { execFile as execFile2 } from "child_process";
|
|
3117
3220
|
import { mkdir as mkdir5, mkdtemp, rename as rename4 } from "fs/promises";
|
|
3118
|
-
import { dirname as dirname7, isAbsolute as isAbsolute5, join as
|
|
3221
|
+
import { dirname as dirname7, isAbsolute as isAbsolute5, join as join5 } from "path";
|
|
3119
3222
|
import { tmpdir } from "os";
|
|
3120
3223
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3121
3224
|
import { promisify as promisify2 } from "util";
|
|
@@ -3160,7 +3263,7 @@ async function packProvisioningArtifact(request) {
|
|
|
3160
3263
|
], { maxBuffer: 1024 * 1024 * 20 });
|
|
3161
3264
|
const packedName = stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
3162
3265
|
if (!packedName) throw new Error(`pnpm pack produced no artifact for ${sourcePath}`);
|
|
3163
|
-
const packedPath = isAbsolute5(packedName) ? packedName :
|
|
3266
|
+
const packedPath = isAbsolute5(packedName) ? packedName : join5(dirname7(request.outputPath), packedName);
|
|
3164
3267
|
await rename4(packedPath, request.outputPath);
|
|
3165
3268
|
return;
|
|
3166
3269
|
}
|
|
@@ -3182,8 +3285,8 @@ async function resolveArtifactInstallSource(args) {
|
|
|
3182
3285
|
const name = provisioningArtifactName(kind, id, fingerprint2);
|
|
3183
3286
|
const workspaceRel = `.boring-agent/tmp/${name}`;
|
|
3184
3287
|
if (!await args.workspaceFs.exists(workspaceRel)) {
|
|
3185
|
-
const artifactDir = await mkdtemp(
|
|
3186
|
-
const outputPath =
|
|
3288
|
+
const artifactDir = await mkdtemp(join5(tmpdir(), "boring-agent-artifact-"));
|
|
3289
|
+
const outputPath = join5(artifactDir, name);
|
|
3187
3290
|
try {
|
|
3188
3291
|
await args.prepareArtifact({ kind, id, fingerprint: fingerprint2, source: args.source, outputPath });
|
|
3189
3292
|
await args.workspaceFs.copyFromHost(outputPath, workspaceRel);
|
|
@@ -3290,7 +3393,7 @@ function createPythonRuntimeFingerprint(input) {
|
|
|
3290
3393
|
}
|
|
3291
3394
|
|
|
3292
3395
|
// src/server/workspace/provisioning/node.ts
|
|
3293
|
-
import { join as
|
|
3396
|
+
import { join as join6, relative as relative6, sep as sep3 } from "path";
|
|
3294
3397
|
var NODE_RUNTIME_REL = ".boring-agent/node";
|
|
3295
3398
|
var NODE_PACKAGE_JSON_REL = `${NODE_RUNTIME_REL}/package.json`;
|
|
3296
3399
|
var NODE_FINGERPRINT_REL = `${NODE_RUNTIME_REL}/.fingerprint`;
|
|
@@ -3328,9 +3431,9 @@ function nodeInstallSource(spec) {
|
|
|
3328
3431
|
}
|
|
3329
3432
|
function expectedNodeOutputs(paths, packages) {
|
|
3330
3433
|
return [
|
|
3331
|
-
|
|
3434
|
+
join6(paths.node, "package-lock.json"),
|
|
3332
3435
|
...packages.flatMap(
|
|
3333
|
-
(pkg) => (pkg.expectedBins ?? []).map((bin) =>
|
|
3436
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join6(paths.nodeBin, bin))
|
|
3334
3437
|
)
|
|
3335
3438
|
];
|
|
3336
3439
|
}
|
|
@@ -3421,7 +3524,7 @@ async function ensureNodeRuntime(options) {
|
|
|
3421
3524
|
}
|
|
3422
3525
|
|
|
3423
3526
|
// src/server/workspace/provisioning/python.ts
|
|
3424
|
-
import { dirname as dirname9, join as
|
|
3527
|
+
import { dirname as dirname9, join as join7, relative as relative7, sep as sep4 } from "path";
|
|
3425
3528
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3426
3529
|
var VENV_REL = ".boring-agent/venv";
|
|
3427
3530
|
var VENV_FINGERPRINT_REL = `${VENV_REL}/.fingerprint`;
|
|
@@ -3440,7 +3543,7 @@ async function ensureUv(options) {
|
|
|
3440
3543
|
const uvVersion = await commandOutput2(options.adapter, explicitUvBin, ["--version"]) || "uv unknown";
|
|
3441
3544
|
try {
|
|
3442
3545
|
await options.adapter.exec("mkdir", ["-p", options.runtimeLayout.uvBin], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3443
|
-
await options.adapter.exec("ln", ["-sf", explicitUvBin,
|
|
3546
|
+
await options.adapter.exec("ln", ["-sf", explicitUvBin, join7(options.runtimeLayout.uvBin, "uv")], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3444
3547
|
} catch {
|
|
3445
3548
|
}
|
|
3446
3549
|
return { uvBin: explicitUvBin, uvVersion, installedWorkspaceUv: false };
|
|
@@ -3467,7 +3570,7 @@ async function ensureUv(options) {
|
|
|
3467
3570
|
await options.adapter.workspaceFs.mkdir(".boring-agent/sdk/uv/bin");
|
|
3468
3571
|
await options.adapter.workspaceFs.rm(UV_BIN_REL);
|
|
3469
3572
|
await options.adapter.workspaceFs.copyFromHost(options.uvStandaloneSource, UV_BIN_REL);
|
|
3470
|
-
const uvBin =
|
|
3573
|
+
const uvBin = join7(options.runtimeLayout.uvBin, "uv");
|
|
3471
3574
|
await options.adapter.exec("chmod", ["+x", uvBin], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3472
3575
|
return {
|
|
3473
3576
|
uvBin,
|
|
@@ -3500,7 +3603,7 @@ function expectedPythonOutputs(paths, packages) {
|
|
|
3500
3603
|
return [
|
|
3501
3604
|
paths.venvPython,
|
|
3502
3605
|
...packages.flatMap(
|
|
3503
|
-
(pkg) => (pkg.expectedBins ?? []).map((bin) =>
|
|
3606
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join7(paths.venvBin, bin))
|
|
3504
3607
|
)
|
|
3505
3608
|
];
|
|
3506
3609
|
}
|
|
@@ -3613,7 +3716,7 @@ async function ensurePythonRuntime(options) {
|
|
|
3613
3716
|
|
|
3614
3717
|
// src/server/workspace/provisioning/skills.ts
|
|
3615
3718
|
import { stat as stat5 } from "fs/promises";
|
|
3616
|
-
import { join as
|
|
3719
|
+
import { join as join8 } from "path";
|
|
3617
3720
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3618
3721
|
var GENERATED_SKILLS_REL = ".boring-agent/skills";
|
|
3619
3722
|
var USER_SKILLS_REL = ".agents/skills";
|
|
@@ -3626,7 +3729,7 @@ function assertSafeSegment(kind, value) {
|
|
|
3626
3729
|
}
|
|
3627
3730
|
}
|
|
3628
3731
|
function getProvisionedSkillPaths(paths) {
|
|
3629
|
-
return [paths.skills,
|
|
3732
|
+
return [paths.skills, join8(paths.workspaceRoot, USER_SKILLS_REL)];
|
|
3630
3733
|
}
|
|
3631
3734
|
async function mirrorPluginSkills(options) {
|
|
3632
3735
|
await options.adapter.workspaceFs.rm(GENERATED_SKILLS_REL);
|
|
@@ -3659,7 +3762,7 @@ async function mirrorPluginSkills(options) {
|
|
|
3659
3762
|
// src/server/workspace/provisioning/workspaceFiles.ts
|
|
3660
3763
|
import { basename, join as joinPath } from "path";
|
|
3661
3764
|
import { posix as posix2 } from "path";
|
|
3662
|
-
import { readdir as
|
|
3765
|
+
import { readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
3663
3766
|
import { fileURLToPath as fileURLToPath5, pathToFileURL } from "url";
|
|
3664
3767
|
function sourceToPath3(source) {
|
|
3665
3768
|
return source instanceof URL ? fileURLToPath5(source) : source;
|
|
@@ -3701,7 +3804,7 @@ async function collectTemplateWorkItems(template) {
|
|
|
3701
3804
|
kind: "directory"
|
|
3702
3805
|
});
|
|
3703
3806
|
}
|
|
3704
|
-
const entries = await
|
|
3807
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
3705
3808
|
for (const entry of entries) {
|
|
3706
3809
|
const childPath = joinPath(dir, entry.name);
|
|
3707
3810
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
@@ -4300,8 +4403,8 @@ var localModeAdapter = {
|
|
|
4300
4403
|
};
|
|
4301
4404
|
|
|
4302
4405
|
// src/server/runtime/modes/vercel-sandbox.ts
|
|
4303
|
-
import { readFile as readFile8, readdir as
|
|
4304
|
-
import { join as
|
|
4406
|
+
import { readFile as readFile8, readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
4407
|
+
import { join as join9 } from "path";
|
|
4305
4408
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
4306
4409
|
import { Sandbox as VercelSandbox } from "@vercel/sandbox";
|
|
4307
4410
|
|
|
@@ -4447,20 +4550,20 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
|
4447
4550
|
|
|
4448
4551
|
// src/server/sandbox/vercel-sandbox/packageTemplate.ts
|
|
4449
4552
|
import { createHash as createHash5 } from "crypto";
|
|
4450
|
-
import { readFile as readFile7, readdir as
|
|
4553
|
+
import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
|
|
4451
4554
|
import path3 from "path";
|
|
4452
4555
|
import { Readable } from "stream";
|
|
4453
4556
|
import { createGzip } from "zlib";
|
|
4454
4557
|
import { put } from "@vercel/blob";
|
|
4455
4558
|
var urlCache = /* @__PURE__ */ new Map();
|
|
4456
4559
|
var LOG_PREFIX = "[template-tarball]";
|
|
4457
|
-
function
|
|
4560
|
+
function log3(msg, meta = {}) {
|
|
4458
4561
|
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
|
|
4459
4562
|
process.stderr.write(`${LOG_PREFIX} ${msg}${metaStr}
|
|
4460
4563
|
`);
|
|
4461
4564
|
}
|
|
4462
4565
|
async function collectFiles(dir, base = "") {
|
|
4463
|
-
const entries = await
|
|
4566
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
4464
4567
|
const files = [];
|
|
4465
4568
|
for (const entry of entries) {
|
|
4466
4569
|
const fullPath = path3.join(dir, entry.name);
|
|
@@ -4537,11 +4640,11 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
4537
4640
|
const hash = computeTemplateHash(files);
|
|
4538
4641
|
const cached = urlCache.get(hash);
|
|
4539
4642
|
if (cached) {
|
|
4540
|
-
|
|
4643
|
+
log3("cache hit", { hash, fileCount: files.length });
|
|
4541
4644
|
return { url: cached, hash };
|
|
4542
4645
|
}
|
|
4543
4646
|
const tarball = await buildTarGz(files);
|
|
4544
|
-
|
|
4647
|
+
log3("tarball built", {
|
|
4545
4648
|
hash,
|
|
4546
4649
|
fileCount: files.length,
|
|
4547
4650
|
sizeBytes: tarball.length,
|
|
@@ -4550,7 +4653,7 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
4550
4653
|
const upload = opts.uploadFn ?? defaultUpload;
|
|
4551
4654
|
const url = await upload(hash, tarball);
|
|
4552
4655
|
urlCache.set(hash, url);
|
|
4553
|
-
|
|
4656
|
+
log3("uploaded", { hash, url, totalMs: Date.now() - startMs });
|
|
4554
4657
|
return { url, hash };
|
|
4555
4658
|
}
|
|
4556
4659
|
|
|
@@ -4716,10 +4819,10 @@ async function copyHostPathToVercelWorkspace(options) {
|
|
|
4716
4819
|
const sourceStat = await stat8(sourcePath);
|
|
4717
4820
|
if (sourceStat.isDirectory()) {
|
|
4718
4821
|
await options.workspace.mkdir(options.targetRel, { recursive: true });
|
|
4719
|
-
for (const entry of await
|
|
4822
|
+
for (const entry of await readdir6(sourcePath, { withFileTypes: true })) {
|
|
4720
4823
|
await copyHostPathToVercelWorkspace({
|
|
4721
4824
|
workspace: options.workspace,
|
|
4722
|
-
source:
|
|
4825
|
+
source: join9(sourcePath, entry.name),
|
|
4723
4826
|
targetRel: `${options.targetRel}/${entry.name}`
|
|
4724
4827
|
});
|
|
4725
4828
|
}
|
|
@@ -5147,7 +5250,7 @@ function getRuntimeBundleStorageRoot(bundle) {
|
|
|
5147
5250
|
|
|
5148
5251
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
5149
5252
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
5150
|
-
import { extname as extname3, join as
|
|
5253
|
+
import { extname as extname3, join as join11 } from "path";
|
|
5151
5254
|
import {
|
|
5152
5255
|
createAgentSession,
|
|
5153
5256
|
SessionManager,
|
|
@@ -5427,7 +5530,7 @@ function createPiAgentSessionAdapter(session, options = {}) {
|
|
|
5427
5530
|
// src/server/harness/pi-coding-agent/sessions.ts
|
|
5428
5531
|
import { randomUUID } from "crypto";
|
|
5429
5532
|
import {
|
|
5430
|
-
readdir as
|
|
5533
|
+
readdir as readdir7,
|
|
5431
5534
|
readFile as readFile9,
|
|
5432
5535
|
stat as fsStat,
|
|
5433
5536
|
rm as rm4,
|
|
@@ -5438,7 +5541,7 @@ import {
|
|
|
5438
5541
|
open as open2
|
|
5439
5542
|
} from "fs/promises";
|
|
5440
5543
|
import { closeSync, openSync, readFileSync, readSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5441
|
-
import { join as
|
|
5544
|
+
import { join as join10, basename as basename2, resolve as resolve7 } from "path";
|
|
5442
5545
|
import { homedir as homedir3 } from "os";
|
|
5443
5546
|
import {
|
|
5444
5547
|
parseSessionEntries,
|
|
@@ -5446,7 +5549,7 @@ import {
|
|
|
5446
5549
|
} from "@mariozechner/pi-coding-agent";
|
|
5447
5550
|
function defaultSessionDir(cwd) {
|
|
5448
5551
|
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
5449
|
-
return
|
|
5552
|
+
return join10(homedir3(), ".pi", "agent", "sessions", safePath);
|
|
5450
5553
|
}
|
|
5451
5554
|
var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
5452
5555
|
var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
|
|
@@ -5456,7 +5559,7 @@ function sessionDirForNamespace(namespace) {
|
|
|
5456
5559
|
if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
|
|
5457
5560
|
throw new Error("session namespace must contain only letters, numbers, underscores, and dashes");
|
|
5458
5561
|
}
|
|
5459
|
-
return
|
|
5562
|
+
return join10(homedir3(), ".pi", "agent", "sessions", safeNamespace);
|
|
5460
5563
|
}
|
|
5461
5564
|
function normalizeListOptions(options) {
|
|
5462
5565
|
return {
|
|
@@ -5501,9 +5604,9 @@ var PiSessionStore = class {
|
|
|
5501
5604
|
}
|
|
5502
5605
|
}
|
|
5503
5606
|
async listUncached(_ctx, options) {
|
|
5504
|
-
const files = await
|
|
5607
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5505
5608
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
5506
|
-
const filepaths = jsonlFiles.map((f) =>
|
|
5609
|
+
const filepaths = jsonlFiles.map((f) => join10(this.sessionDir, f));
|
|
5507
5610
|
const fileStats = await Promise.all(filepaths.map(async (filepath) => {
|
|
5508
5611
|
try {
|
|
5509
5612
|
return { filepath, stat: await fsStat(filepath) };
|
|
@@ -5547,7 +5650,7 @@ var PiSessionStore = class {
|
|
|
5547
5650
|
};
|
|
5548
5651
|
lines.push(JSON.stringify(infoEntry));
|
|
5549
5652
|
}
|
|
5550
|
-
const filepath =
|
|
5653
|
+
const filepath = join10(this.sessionDir, `${id}.jsonl`);
|
|
5551
5654
|
await writeFile6(filepath, lines.join("\n") + "\n", "utf-8");
|
|
5552
5655
|
return {
|
|
5553
5656
|
id,
|
|
@@ -5629,7 +5732,7 @@ var PiSessionStore = class {
|
|
|
5629
5732
|
loadPiSessionFileSync(sessionId) {
|
|
5630
5733
|
if (!SAFE_ID.test(sessionId)) return null;
|
|
5631
5734
|
try {
|
|
5632
|
-
const direct =
|
|
5735
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5633
5736
|
let filepath = direct;
|
|
5634
5737
|
let content;
|
|
5635
5738
|
try {
|
|
@@ -5639,7 +5742,7 @@ var PiSessionStore = class {
|
|
|
5639
5742
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
5640
5743
|
);
|
|
5641
5744
|
if (files.length === 0) return null;
|
|
5642
|
-
filepath =
|
|
5745
|
+
filepath = join10(this.sessionDir, files[0]);
|
|
5643
5746
|
content = readFileSync(filepath, "utf-8");
|
|
5644
5747
|
}
|
|
5645
5748
|
const entries = safeParseEntries(content);
|
|
@@ -5695,18 +5798,18 @@ var PiSessionStore = class {
|
|
|
5695
5798
|
if (!SAFE_ID.test(sessionId)) {
|
|
5696
5799
|
throw new Error(`Session not found: ${sessionId}`);
|
|
5697
5800
|
}
|
|
5698
|
-
const direct =
|
|
5801
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5699
5802
|
try {
|
|
5700
5803
|
await fsStat(direct);
|
|
5701
5804
|
return direct;
|
|
5702
5805
|
} catch {
|
|
5703
5806
|
}
|
|
5704
|
-
const files = await
|
|
5807
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5705
5808
|
const match = files.find(
|
|
5706
5809
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
5707
5810
|
);
|
|
5708
5811
|
if (!match) throw new Error(`Session not found: ${sessionId}`);
|
|
5709
|
-
const matchedPath =
|
|
5812
|
+
const matchedPath = join10(this.sessionDir, match);
|
|
5710
5813
|
if (!isTimestampNamedPiSessionFile(matchedPath, sessionId)) return matchedPath;
|
|
5711
5814
|
const existingWrapper = await this.findWrapperReferencingNativeSession(matchedPath);
|
|
5712
5815
|
if (existingWrapper) {
|
|
@@ -5871,7 +5974,7 @@ var PiSessionStore = class {
|
|
|
5871
5974
|
try {
|
|
5872
5975
|
const files = readdirSync(this.sessionDir).filter((file) => file.endsWith(".jsonl"));
|
|
5873
5976
|
for (const file of files) {
|
|
5874
|
-
const filepath =
|
|
5977
|
+
const filepath = join10(this.sessionDir, file);
|
|
5875
5978
|
if (resolve7(filepath) === resolvedNativePath) continue;
|
|
5876
5979
|
try {
|
|
5877
5980
|
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries(readJsonlPrefixSync(filepath)));
|
|
@@ -5886,10 +5989,10 @@ var PiSessionStore = class {
|
|
|
5886
5989
|
}
|
|
5887
5990
|
async findWrapperReferencingNativeSession(nativePath) {
|
|
5888
5991
|
const resolvedNativePath = resolve7(nativePath);
|
|
5889
|
-
const files = await
|
|
5992
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
5890
5993
|
for (const file of files) {
|
|
5891
5994
|
if (!file.endsWith(".jsonl")) continue;
|
|
5892
|
-
const filepath =
|
|
5995
|
+
const filepath = join10(this.sessionDir, file);
|
|
5893
5996
|
if (resolve7(filepath) === resolvedNativePath) continue;
|
|
5894
5997
|
try {
|
|
5895
5998
|
const linkedPiFile = extractPiSessionFilePath(parseJsonlPrefixEntries(await readJsonlPrefix(filepath)));
|
|
@@ -5900,7 +6003,7 @@ var PiSessionStore = class {
|
|
|
5900
6003
|
return null;
|
|
5901
6004
|
}
|
|
5902
6005
|
ensureWrapperForNativeSessionSync(sessionId, nativePath, entries) {
|
|
5903
|
-
const wrapperPath =
|
|
6006
|
+
const wrapperPath = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5904
6007
|
if (resolve7(wrapperPath) === resolve7(nativePath)) return wrapperPath;
|
|
5905
6008
|
try {
|
|
5906
6009
|
readFileSync(wrapperPath, "utf-8");
|
|
@@ -5920,7 +6023,7 @@ var PiSessionStore = class {
|
|
|
5920
6023
|
return wrapperPath;
|
|
5921
6024
|
}
|
|
5922
6025
|
async ensureWrapperForNativeSession(sessionId, nativePath) {
|
|
5923
|
-
const wrapperPath =
|
|
6026
|
+
const wrapperPath = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
5924
6027
|
if (resolve7(wrapperPath) === resolve7(nativePath)) return wrapperPath;
|
|
5925
6028
|
try {
|
|
5926
6029
|
await fsStat(wrapperPath);
|
|
@@ -6299,6 +6402,10 @@ var PYTHON_RUNTIME_GUIDELINE = [
|
|
|
6299
6402
|
function composeSystemPromptAppend(hostAppend) {
|
|
6300
6403
|
return [WORKSPACE_PATHS_GUIDELINE, PYTHON_RUNTIME_GUIDELINE, hostAppend?.trim()].filter(Boolean).join("\n\n");
|
|
6301
6404
|
}
|
|
6405
|
+
function withPiHarnessDefaults(pi) {
|
|
6406
|
+
const { noContextFiles = true, noSkills = true, ...rest } = pi ?? {};
|
|
6407
|
+
return { ...rest, noContextFiles, noSkills };
|
|
6408
|
+
}
|
|
6302
6409
|
function buildDynamicPromptExtension(source) {
|
|
6303
6410
|
return (pi) => {
|
|
6304
6411
|
pi.on("before_agent_start", async (event) => {
|
|
@@ -6354,8 +6461,8 @@ function mergeInjectedProjectPackages(settingsJson, piPackages) {
|
|
|
6354
6461
|
}
|
|
6355
6462
|
function createResourceSettingsManager(cwd, agentDir, piPackages) {
|
|
6356
6463
|
if (piPackages.length === 0) return SettingsManager2.create(cwd, agentDir);
|
|
6357
|
-
const globalSettingsPath =
|
|
6358
|
-
const projectSettingsPath =
|
|
6464
|
+
const globalSettingsPath = join11(agentDir, "settings.json");
|
|
6465
|
+
const projectSettingsPath = join11(cwd, ".pi", "settings.json");
|
|
6359
6466
|
let globalSettingsOverrideJson;
|
|
6360
6467
|
let projectSettingsOverrideJson;
|
|
6361
6468
|
const storage = {
|
|
@@ -6385,7 +6492,7 @@ async function applyRequestedSessionOptions(handle, input) {
|
|
|
6385
6492
|
handle.piSession.setThinkingLevel(input.thinkingLevel);
|
|
6386
6493
|
}
|
|
6387
6494
|
}
|
|
6388
|
-
var
|
|
6495
|
+
var log4 = createLogger("pi-harness");
|
|
6389
6496
|
function deriveSourcePlugin(sourceInfo) {
|
|
6390
6497
|
if (!sourceInfo) return void 0;
|
|
6391
6498
|
const path4 = typeof sourceInfo.path === "string" ? sourceInfo.path : "";
|
|
@@ -6410,6 +6517,7 @@ function normalizeSlashCommandInfo(command) {
|
|
|
6410
6517
|
};
|
|
6411
6518
|
}
|
|
6412
6519
|
function createPiCodingAgentHarness(opts) {
|
|
6520
|
+
const pi = withPiHarnessDefaults(opts.pi);
|
|
6413
6521
|
const sessionStore = new PiSessionStore(opts.runtimeCwd ?? opts.cwd, {
|
|
6414
6522
|
sessionNamespace: opts.sessionNamespace,
|
|
6415
6523
|
sessionDir: opts.sessionDir,
|
|
@@ -6420,22 +6528,22 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6420
6528
|
const effectivePackages = [];
|
|
6421
6529
|
const effectiveExtensionPaths = [];
|
|
6422
6530
|
const refreshEffectiveResources = () => {
|
|
6423
|
-
const dynamic =
|
|
6531
|
+
const dynamic = pi.getHotReloadableResources?.() ?? {};
|
|
6424
6532
|
effectiveSkillPaths.splice(
|
|
6425
6533
|
0,
|
|
6426
6534
|
effectiveSkillPaths.length,
|
|
6427
|
-
...
|
|
6535
|
+
...pi.additionalSkillPaths ?? [],
|
|
6428
6536
|
...dynamic.additionalSkillPaths ?? []
|
|
6429
6537
|
);
|
|
6430
6538
|
effectivePackages.splice(
|
|
6431
6539
|
0,
|
|
6432
6540
|
effectivePackages.length,
|
|
6433
|
-
...mergePiPackageSources(
|
|
6541
|
+
...mergePiPackageSources(pi.packages ?? [], dynamic.packages ?? [])
|
|
6434
6542
|
);
|
|
6435
6543
|
effectiveExtensionPaths.splice(
|
|
6436
6544
|
0,
|
|
6437
6545
|
effectiveExtensionPaths.length,
|
|
6438
|
-
...
|
|
6546
|
+
...pi.extensionPaths ?? [],
|
|
6439
6547
|
...dynamic.extensionPaths ?? []
|
|
6440
6548
|
);
|
|
6441
6549
|
};
|
|
@@ -6492,7 +6600,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6492
6600
|
const extensionFactories = [
|
|
6493
6601
|
toolErrorResultExtension,
|
|
6494
6602
|
...dynamicPromptExtension ? [dynamicPromptExtension] : [],
|
|
6495
|
-
...
|
|
6603
|
+
...pi.extensionFactories ?? []
|
|
6496
6604
|
];
|
|
6497
6605
|
const settingsManager = createResourceSettingsManager(
|
|
6498
6606
|
opts.cwd,
|
|
@@ -6506,8 +6614,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6506
6614
|
appendSystemPromptOverride: (base) => [...base, composedSystemPromptAppend],
|
|
6507
6615
|
...effectiveExtensionPaths.length ? { additionalExtensionPaths: effectiveExtensionPaths } : {},
|
|
6508
6616
|
...extensionFactories.length ? { extensionFactories } : {},
|
|
6509
|
-
...
|
|
6510
|
-
...
|
|
6617
|
+
...pi.noContextFiles ? { noContextFiles: true } : {},
|
|
6618
|
+
...pi.noSkills ? { noSkills: true } : {},
|
|
6511
6619
|
...effectiveSkillPaths.length ? { additionalSkillPaths: effectiveSkillPaths } : {},
|
|
6512
6620
|
// skillsOverride REPLACES Pi's resolved skill set, which includes
|
|
6513
6621
|
// skills contributed by host-declared pi packages (e.g.
|
|
@@ -6516,7 +6624,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
6516
6624
|
// Passing additionalSkillPaths is not, by itself, a request to throw
|
|
6517
6625
|
// away package skills — those should keep flowing through Pi's loader
|
|
6518
6626
|
// and merge with the additional paths.
|
|
6519
|
-
...
|
|
6627
|
+
...pi.noSkills ? {
|
|
6520
6628
|
skillsOverride: () => loadSkills({
|
|
6521
6629
|
cwd: opts.cwd,
|
|
6522
6630
|
agentDir,
|
|
@@ -6647,12 +6755,12 @@ ${entry.message}`;
|
|
|
6647
6755
|
}
|
|
6648
6756
|
|
|
6649
6757
|
// src/server/harness/pi-coding-agent/pluginLoader.ts
|
|
6650
|
-
import { readdir as
|
|
6651
|
-
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";
|
|
6652
6760
|
import { homedir as homedir4 } from "os";
|
|
6653
6761
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6654
6762
|
var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs"]);
|
|
6655
|
-
var GLOBAL_DIR =
|
|
6763
|
+
var GLOBAL_DIR = join12(homedir4(), ".pi", "agent", "extensions");
|
|
6656
6764
|
var LOCAL_DIR = ".pi/extensions";
|
|
6657
6765
|
var EXTENSIONS_JSON = ".pi/extensions.json";
|
|
6658
6766
|
async function dirExists2(path4) {
|
|
@@ -6673,8 +6781,8 @@ async function fileExists(path4) {
|
|
|
6673
6781
|
}
|
|
6674
6782
|
async function discoverFromDir(dir, source) {
|
|
6675
6783
|
if (!await dirExists2(dir)) return [];
|
|
6676
|
-
const entries = await
|
|
6677
|
-
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 }));
|
|
6678
6786
|
}
|
|
6679
6787
|
function extractTools(mod) {
|
|
6680
6788
|
const tools = [];
|
|
@@ -6704,17 +6812,17 @@ async function loadModule(filePath, importFn) {
|
|
|
6704
6812
|
return extractTools(mod);
|
|
6705
6813
|
}
|
|
6706
6814
|
async function discoverNpmPlugins(cwd) {
|
|
6707
|
-
const nodeModulesDir =
|
|
6815
|
+
const nodeModulesDir = join12(cwd, "node_modules");
|
|
6708
6816
|
if (!await dirExists2(nodeModulesDir)) return [];
|
|
6709
6817
|
try {
|
|
6710
|
-
const entries = await
|
|
6711
|
-
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));
|
|
6712
6820
|
} catch {
|
|
6713
6821
|
return [];
|
|
6714
6822
|
}
|
|
6715
6823
|
}
|
|
6716
6824
|
async function loadNpmPlugin(pkgDir, importFn) {
|
|
6717
|
-
const pkgJsonPath =
|
|
6825
|
+
const pkgJsonPath = join12(pkgDir, "package.json");
|
|
6718
6826
|
if (!await fileExists(pkgJsonPath)) return [];
|
|
6719
6827
|
const pkgJson = JSON.parse(await readFile10(pkgJsonPath, "utf-8"));
|
|
6720
6828
|
const main = pkgJson.main ?? "index.js";
|
|
@@ -6730,7 +6838,7 @@ async function loadNpmPlugin(pkgDir, importFn) {
|
|
|
6730
6838
|
return loadModule(resolvedMain, importFn);
|
|
6731
6839
|
}
|
|
6732
6840
|
async function loadExtensionsJson(cwd) {
|
|
6733
|
-
const configPath =
|
|
6841
|
+
const configPath = join12(cwd, EXTENSIONS_JSON);
|
|
6734
6842
|
if (!await fileExists(configPath)) return null;
|
|
6735
6843
|
try {
|
|
6736
6844
|
const raw = await readFile10(configPath, "utf-8");
|
|
@@ -6748,7 +6856,7 @@ async function loadPlugins(options) {
|
|
|
6748
6856
|
const globals = await discoverFromDir(GLOBAL_DIR, "global");
|
|
6749
6857
|
candidates.push(...globals);
|
|
6750
6858
|
}
|
|
6751
|
-
const localDir =
|
|
6859
|
+
const localDir = join12(options.cwd, LOCAL_DIR);
|
|
6752
6860
|
const locals = await discoverFromDir(localDir, "local");
|
|
6753
6861
|
candidates.push(...locals);
|
|
6754
6862
|
const npmDirs = await discoverNpmPlugins(options.cwd);
|
|
@@ -6758,7 +6866,7 @@ async function loadPlugins(options) {
|
|
|
6758
6866
|
const config = await loadExtensionsJson(options.cwd);
|
|
6759
6867
|
if (config?.npm) {
|
|
6760
6868
|
for (const pkg of config.npm) {
|
|
6761
|
-
const pkgDir =
|
|
6869
|
+
const pkgDir = join12(options.cwd, "node_modules", pkg);
|
|
6762
6870
|
if (await dirExists2(pkgDir)) {
|
|
6763
6871
|
const already = candidates.some((c) => c.path === pkgDir);
|
|
6764
6872
|
if (!already) {
|
|
@@ -6807,8 +6915,8 @@ import {
|
|
|
6807
6915
|
|
|
6808
6916
|
// src/server/tools/operations/bound.ts
|
|
6809
6917
|
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
|
|
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";
|
|
6812
6920
|
function toPosixPath(value) {
|
|
6813
6921
|
return value.split("\\").join("/");
|
|
6814
6922
|
}
|
|
@@ -6861,7 +6969,7 @@ function shouldSkipDir(relativePath, ignore) {
|
|
|
6861
6969
|
}
|
|
6862
6970
|
async function walkMatches(root, current, pattern, ignore, limit, out) {
|
|
6863
6971
|
if (out.length >= limit) return;
|
|
6864
|
-
const entries = await
|
|
6972
|
+
const entries = await readdir9(current, { withFileTypes: true });
|
|
6865
6973
|
for (const entry of entries) {
|
|
6866
6974
|
if (out.length >= limit) return;
|
|
6867
6975
|
const absolutePath = resolve9(current, entry.name);
|
|
@@ -6939,7 +7047,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
6939
7047
|
const normalized = toPosixPath(absolutePath);
|
|
6940
7048
|
if (normalized === runtimeRoot) return workspaceRoot;
|
|
6941
7049
|
if (normalized.startsWith(`${runtimeRoot}/`)) {
|
|
6942
|
-
return
|
|
7050
|
+
return join13(workspaceRoot, ...normalized.slice(runtimeRoot.length + 1).split("/"));
|
|
6943
7051
|
}
|
|
6944
7052
|
return absolutePath;
|
|
6945
7053
|
};
|
|
@@ -7043,7 +7151,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7043
7151
|
async readdir(absolutePath) {
|
|
7044
7152
|
const storagePath = toStoragePath(absolutePath);
|
|
7045
7153
|
await assertWithinWorkspace(workspaceRoot, storagePath);
|
|
7046
|
-
return await
|
|
7154
|
+
return await readdir9(storagePath);
|
|
7047
7155
|
}
|
|
7048
7156
|
};
|
|
7049
7157
|
return { read, write, edit, find, grep, ls };
|
|
@@ -7877,11 +7985,20 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7877
7985
|
const ensureBroadcaster = async (request) => {
|
|
7878
7986
|
const workspace = opts.getWorkspace ? await opts.getWorkspace(request) : opts.workspace;
|
|
7879
7987
|
if (!workspace) throw new Error("fs event route requires workspace or getWorkspace");
|
|
7880
|
-
if (typeof workspace.watch !== "function")
|
|
7988
|
+
if (typeof workspace.watch !== "function") {
|
|
7989
|
+
return { unsupported: { reason: "watch_not_implemented" } };
|
|
7990
|
+
}
|
|
7881
7991
|
const workspaceId = request.workspaceContext?.workspaceId ?? "default";
|
|
7882
7992
|
const existing = broadcasters.get(workspaceId);
|
|
7883
7993
|
if (existing) return { workspaceId, entry: existing };
|
|
7884
|
-
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);
|
|
7885
8002
|
const entry = { broadcaster, subscribers: 0 };
|
|
7886
8003
|
broadcasters.set(workspaceId, entry);
|
|
7887
8004
|
return { workspaceId, entry };
|
|
@@ -7901,8 +8018,8 @@ function fsEventsRoutes(app, opts, done) {
|
|
|
7901
8018
|
const resolved = await ensureBroadcaster(request);
|
|
7902
8019
|
reply.hijack();
|
|
7903
8020
|
setupSse(request, reply.raw);
|
|
7904
|
-
if (
|
|
7905
|
-
writeSse(reply.raw, "unsupported",
|
|
8021
|
+
if ("unsupported" in resolved) {
|
|
8022
|
+
writeSse(reply.raw, "unsupported", resolved.unsupported);
|
|
7906
8023
|
reply.raw.end();
|
|
7907
8024
|
return;
|
|
7908
8025
|
}
|
|
@@ -8128,8 +8245,8 @@ function skillsRoutes(app, opts, done) {
|
|
|
8128
8245
|
const workspaceRoot = opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(request) : opts.workspaceRoot;
|
|
8129
8246
|
const additionalSkillPaths = opts.getAdditionalSkillPaths ? await opts.getAdditionalSkillPaths(request) : opts.additionalSkillPaths;
|
|
8130
8247
|
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 ?? [],
|
|
8248
|
+
const noSkills = (opts.getNoSkills ? await opts.getNoSkills(request) : opts.noSkills) ?? withPiHarnessDefaults().noSkills;
|
|
8249
|
+
const cacheKey = JSON.stringify([workspaceRoot, additionalSkillPaths ?? [], piPackages ?? [], noSkills]);
|
|
8133
8250
|
const now = Date.now();
|
|
8134
8251
|
for (const [key, entry] of cached) {
|
|
8135
8252
|
if (entry.expiresAt <= now) cached.delete(key);
|
|
@@ -8158,7 +8275,7 @@ function skillsRoutes(app, opts, done) {
|
|
|
8158
8275
|
cwd: workspaceRoot,
|
|
8159
8276
|
agentDir,
|
|
8160
8277
|
skillPaths: [...packageSkillPaths, ...additionalSkillPaths ?? []],
|
|
8161
|
-
includeDefaults:
|
|
8278
|
+
includeDefaults: !noSkills
|
|
8162
8279
|
});
|
|
8163
8280
|
const skills = result.skills.map((s) => ({
|
|
8164
8281
|
name: s.name,
|
|
@@ -10571,7 +10688,7 @@ async function createAgentApp(opts = {}) {
|
|
|
10571
10688
|
}
|
|
10572
10689
|
const getRuntimeProvisioning = opts.getRuntimeProvisioning ?? (() => opts.runtimeProvisioning);
|
|
10573
10690
|
const runtimePi = {
|
|
10574
|
-
...opts.pi,
|
|
10691
|
+
...withPiHarnessDefaults(opts.pi),
|
|
10575
10692
|
additionalSkillPaths: [
|
|
10576
10693
|
...getRuntimeProvisioning()?.skillPaths ?? [],
|
|
10577
10694
|
...opts.pi?.additionalSkillPaths ?? []
|
|
@@ -10599,11 +10716,7 @@ async function createAgentApp(opts = {}) {
|
|
|
10599
10716
|
];
|
|
10600
10717
|
const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
|
|
10601
10718
|
...input,
|
|
10602
|
-
pi:
|
|
10603
|
-
noContextFiles: true,
|
|
10604
|
-
noSkills: true,
|
|
10605
|
-
...runtimePi
|
|
10606
|
-
}
|
|
10719
|
+
pi: runtimePi
|
|
10607
10720
|
}));
|
|
10608
10721
|
const harness = await harnessFactory({
|
|
10609
10722
|
tools,
|
|
@@ -10739,7 +10852,7 @@ function mergeTools(options) {
|
|
|
10739
10852
|
|
|
10740
10853
|
// src/server/tools/upload/index.ts
|
|
10741
10854
|
import { readFile as readFile12 } from "fs/promises";
|
|
10742
|
-
import { extname as extname5, join as
|
|
10855
|
+
import { extname as extname5, join as join14 } from "path";
|
|
10743
10856
|
var DEFAULT_UPLOAD_DIR = "assets/images";
|
|
10744
10857
|
var MAX_UPLOAD_BYTES2 = 10 * 1024 * 1024;
|
|
10745
10858
|
function contentTypeFromExt(path4) {
|
|
@@ -10808,7 +10921,7 @@ function buildUploadAgentTools(bundle) {
|
|
|
10808
10921
|
const rawDir = typeof input.directory === "string" ? input.directory.trim() : "";
|
|
10809
10922
|
const dir = rawDir ? rawDir.replace(/^\.\/+/, "").replace(/\/+$/, "") : DEFAULT_UPLOAD_DIR;
|
|
10810
10923
|
try {
|
|
10811
|
-
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));
|
|
10812
10925
|
if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES2) {
|
|
10813
10926
|
return {
|
|
10814
10927
|
content: [{ type: "text", text: `file must be between 1 byte and ${MAX_UPLOAD_BYTES2} bytes` }],
|
|
@@ -10971,10 +11084,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
10971
11084
|
runtimeBindings.delete(keys[i]);
|
|
10972
11085
|
}
|
|
10973
11086
|
}
|
|
11087
|
+
async function resolveScopePi(workspaceId, root, request) {
|
|
11088
|
+
return withPiHarnessDefaults(opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot: root, request }) : opts.pi);
|
|
11089
|
+
}
|
|
10974
11090
|
async function resolveRuntimeScope(workspaceId, request) {
|
|
10975
11091
|
const root = request && opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(workspaceId, request) : workspaceRoot;
|
|
10976
11092
|
const scopedTemplatePath = opts.getTemplatePath ? await opts.getTemplatePath({ workspaceId, workspaceRoot: root, request }) : templatePath;
|
|
10977
|
-
const pi =
|
|
11093
|
+
const pi = await resolveScopePi(workspaceId, root, request);
|
|
10978
11094
|
const sessionNamespace = normalizeSessionNamespace(opts.getSessionNamespace ? await opts.getSessionNamespace({ workspaceId, workspaceRoot: root, request }) : opts.sessionNamespace);
|
|
10979
11095
|
return {
|
|
10980
11096
|
root,
|
|
@@ -10986,29 +11102,29 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
10986
11102
|
workspaceId,
|
|
10987
11103
|
root,
|
|
10988
11104
|
scopedTemplatePath ?? null,
|
|
10989
|
-
pi
|
|
11105
|
+
pi,
|
|
10990
11106
|
sessionNamespace ?? null
|
|
10991
11107
|
])
|
|
10992
11108
|
};
|
|
10993
11109
|
}
|
|
10994
11110
|
async function resolveSkillScope(workspaceId, request) {
|
|
10995
11111
|
const root = request && opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(workspaceId, request) : workspaceRoot;
|
|
10996
|
-
const pi =
|
|
10997
|
-
const hot = pi
|
|
11112
|
+
const pi = await resolveScopePi(workspaceId, root, request);
|
|
11113
|
+
const hot = pi.getHotReloadableResources?.();
|
|
10998
11114
|
return {
|
|
10999
11115
|
root,
|
|
11000
11116
|
pi: hot ? {
|
|
11001
11117
|
...pi,
|
|
11002
11118
|
additionalSkillPaths: [
|
|
11003
|
-
...pi
|
|
11119
|
+
...pi.additionalSkillPaths ?? [],
|
|
11004
11120
|
...hot.additionalSkillPaths ?? []
|
|
11005
11121
|
],
|
|
11006
11122
|
packages: [
|
|
11007
|
-
...pi
|
|
11123
|
+
...pi.packages ?? [],
|
|
11008
11124
|
...hot.packages ?? []
|
|
11009
11125
|
],
|
|
11010
11126
|
extensionPaths: [
|
|
11011
|
-
...pi
|
|
11127
|
+
...pi.extensionPaths ?? [],
|
|
11012
11128
|
...hot.extensionPaths ?? []
|
|
11013
11129
|
]
|
|
11014
11130
|
} : pi
|
|
@@ -11185,14 +11301,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11185
11301
|
const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
|
|
11186
11302
|
...input,
|
|
11187
11303
|
pi: {
|
|
11188
|
-
|
|
11189
|
-
noSkills: true,
|
|
11304
|
+
// scope.pi is already defaulted at the resolveScopePi chokepoint.
|
|
11190
11305
|
...scope.pi,
|
|
11191
11306
|
additionalSkillPaths: [
|
|
11192
|
-
...scope.pi
|
|
11307
|
+
...scope.pi.additionalSkillPaths ?? []
|
|
11193
11308
|
],
|
|
11194
11309
|
getHotReloadableResources: () => {
|
|
11195
|
-
const hot = scope.pi
|
|
11310
|
+
const hot = scope.pi.getHotReloadableResources?.() ?? {};
|
|
11196
11311
|
return {
|
|
11197
11312
|
...hot,
|
|
11198
11313
|
additionalSkillPaths: [
|
|
@@ -11436,19 +11551,21 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
11436
11551
|
...opts.pi?.additionalSkillPaths ?? []
|
|
11437
11552
|
],
|
|
11438
11553
|
piPackages: opts.pi?.packages,
|
|
11554
|
+
// Undefined is fine: skillsRoutes resolves it through the canonical
|
|
11555
|
+
// harness policy (withPiHarnessDefaults), same as the factory above.
|
|
11439
11556
|
noSkills: opts.pi?.noSkills,
|
|
11440
11557
|
getWorkspaceRoot: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).root,
|
|
11441
11558
|
getAdditionalSkillPaths: staticBinding && !hasRuntimeProvisioningInput ? void 0 : async (request) => {
|
|
11442
11559
|
const scope = await getSkillsScopeForRequest(request);
|
|
11443
|
-
if (!hasRuntimeProvisioningInput) return scope.pi
|
|
11560
|
+
if (!hasRuntimeProvisioningInput) return scope.pi.additionalSkillPaths;
|
|
11444
11561
|
const binding = await getBindingForRequest(request);
|
|
11445
11562
|
return [
|
|
11446
11563
|
...binding.runtimeProvisioning?.skillPaths ?? [],
|
|
11447
|
-
...scope.pi
|
|
11564
|
+
...scope.pi.additionalSkillPaths ?? []
|
|
11448
11565
|
];
|
|
11449
11566
|
},
|
|
11450
|
-
getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi
|
|
11451
|
-
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
|
|
11452
11569
|
});
|
|
11453
11570
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
11454
11571
|
app.post("/api/v1/agent/reload", async (request, reply) => {
|