@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.
Files changed (33) hide show
  1. package/README.md +31 -252
  2. package/dist/{agentPluginEvents-CKrZLW3g.d.ts → agentPluginEvents-ZoOjcb5J.d.ts} +21 -0
  3. package/dist/server/index.d.ts +2 -2
  4. package/dist/server/index.js +363 -246
  5. package/dist/shared/index.d.ts +2 -2
  6. package/docs/ACCESSIBILITY.md +11 -13
  7. package/docs/API.md +86 -59
  8. package/docs/CSP.md +10 -9
  9. package/docs/ERROR_CODES.md +1 -1
  10. package/docs/KNOWN_LIMITATIONS.md +3 -3
  11. package/docs/MIGRATION.md +16 -16
  12. package/docs/PERFORMANCE.md +1 -1
  13. package/docs/PLUGINS.md +42 -12
  14. package/docs/README.md +89 -12
  15. package/docs/RISKS-MULTI-TAB.md +1 -1
  16. package/docs/STYLING.md +6 -4
  17. package/docs/UI-SHADCN.md +7 -7
  18. package/docs/runtime-provisioning.md +7 -3
  19. package/docs/runtime.md +68 -13
  20. package/docs/tools.md +37 -14
  21. package/package.json +2 -2
  22. /package/docs/plans/{AGENT_EVAL_FRAMEWORK.md → archive/AGENT_EVAL_FRAMEWORK.md} +0 -0
  23. /package/docs/plans/{agent-package-spec.md → archive/agent-package-spec.md} +0 -0
  24. /package/docs/plans/{harness-followup-capabilities.md → archive/harness-followup-capabilities.md} +0 -0
  25. /package/docs/plans/{harness-tool-ui-capabilities.md → archive/harness-tool-ui-capabilities.md} +0 -0
  26. /package/docs/plans/{pi-followup-history-projection.md → archive/pi-followup-history-projection.md} +0 -0
  27. /package/docs/plans/{pi-tools-migration.md → archive/pi-tools-migration.md} +0 -0
  28. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-codex-review.md +0 -0
  29. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-gpt-review.md +0 -0
  30. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-opus-review.md +0 -0
  31. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-xai-review.md +0 -0
  32. /package/docs/plans/{vercel-base-snapshot-template-plan.md → archive/vercel-base-snapshot-template-plan.md} +0 -0
  33. /package/docs/plans/{vercel-persistent-sandbox-adapter.md → archive/vercel-persistent-sandbox-adapter.md} +0 -0
@@ -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 join2, posix, relative as relative3, resolve as resolve4, sep as sep2 } from "path";
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, relative as relative2, resolve as resolve3, sep } from "path";
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/workspace/createNodeWorkspace.ts
450
- var EPERM_CODE = "EPERM";
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
- const ensureFsw = () => {
461
- if (fsw) return fsw;
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; renames are best
472
- // recovered at the consumer level if needed.
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
- fsw.on("error", () => {
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 rel = (abs) => {
484
- const r = relative2(root, abs);
485
- return r.split(sep).join("/");
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 emit = (event) => {
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 readdir(absPath, { withFileTypes: true });
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 = join2(globalRoot, runtimeRelPath);
761
- const childRuntimePath = join2(workspaceRoot, runtimeRelPath);
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 log = createLogger("boring/workspace-settings");
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
- log.warn("failed to parse .boring/settings \u2014 falling back to defaults", {
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 readdir2, readFile as readFile4, realpath as realpath2, stat as stat4, writeFile as writeFile4 } from "fs/promises";
2414
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join3, relative as relative5, resolve as resolve5 } from "path";
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 readdir2(path4)).filter((entry) => entry !== "__pycache__" && entry !== "build" && !entry.endsWith(".egg-info")).sort();
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(join3(path4, entry), hash);
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 = join3(packageRoot, "package.json");
2488
- const distDir = join3(packageRoot, "dist");
2489
- const docsDir = join3(packageRoot, "docs");
2490
- const skillsDir = join3(packageRoot, "skills");
2491
- const referencesDir = join3(packageRoot, "references");
2492
- const sourceDocsDir = join3(packageRoot, "src", "server", "docs");
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 = join3(packageRoot, "templates");
2496
- const publicDir = join3(packageRoot, "public");
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 join3(workspaceRoot, "node_modules", ...parts);
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(join3(packageRoot, "package.json"), join3(target, "package.json"));
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(join3(packageRoot, "dist"), join3(target, "dist"));
2570
- await copyIfExists(join3(packageRoot, "templates"), join3(target, "templates"));
2571
- await copyIfExists(join3(packageRoot, "public"), join3(target, "public"));
2572
- if (!await exists(join3(target, "dist", "docs", "plugins.md"))) {
2573
- await copyIfExists(join3(packageRoot, "src", "server", "docs"), join3(target, "dist", "docs"));
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(join3(packageRoot, "docs"), join3(target, "docs"))) {
2576
- await copyIfExists(join3(packageRoot, "src", "server", "docs"), join3(target, "docs"));
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(join3(packageRoot, "skills"), join3(target, "skills"));
2579
- await copyIfExists(join3(packageRoot, "references"), join3(target, "references"));
2580
- await copyIfExists(join3(packageRoot, "src", "globals.css"), join3(target, "src", "globals.css"));
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 = join3(workspaceRoot, ".boring-agent", "venv", "bin", "python");
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(join3(workspaceRoot, ".boring-agent", "venv", "bin", "python"))) return false;
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(join3(nodePackageTarget(workspaceRoot, spec.packageName), "package.json"))) return false;
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 = join3(workspaceRoot, ".boring-agent", "bin");
2628
- const venvBin = join3(workspaceRoot, ".boring-agent", "venv", "bin");
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(join3(shimDir, "python"), `${base}exec "$VENV_BIN/python" "$@"
2745
+ await writeExecutable(join4(shimDir, "python"), `${base}exec "$VENV_BIN/python" "$@"
2643
2746
  `);
2644
- await writeExecutable(join3(shimDir, "python3"), `${base}exec "$VENV_BIN/python" "$@"
2747
+ await writeExecutable(join4(shimDir, "python3"), `${base}exec "$VENV_BIN/python" "$@"
2645
2748
  `);
2646
- await writeExecutable(join3(shimDir, "pip"), `${base}exec "$VENV_BIN/python" -m pip "$@"
2749
+ await writeExecutable(join4(shimDir, "pip"), `${base}exec "$VENV_BIN/python" -m pip "$@"
2647
2750
  `);
2648
- await writeExecutable(join3(shimDir, "pip3"), `${base}exec "$VENV_BIN/python" -m pip "$@"
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 readdir2(venvBin)) {
2754
+ for (const entry of await readdir3(venvBin)) {
2652
2755
  if (["python", "python3", "pip", "pip3"].includes(entry)) continue;
2653
- const full = join3(venvBin, entry);
2756
+ const full = join4(venvBin, entry);
2654
2757
  const info = await stat4(full).catch(() => null);
2655
2758
  if (!info?.isFile()) continue;
2656
- await writeExecutable(join3(shimDir, entry), `${base}TARGET="$VENV_BIN"/${bashSingleQuote(entry)}
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 = join3(workspaceRoot, ".boring-agent", "provisioning.json");
2680
- const binDir = join3(workspaceRoot, ".boring-agent", "bin");
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 join4 } from "path";
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 : join4(dirname7(request.outputPath), 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(join4(tmpdir(), "boring-agent-artifact-"));
3186
- const outputPath = join4(artifactDir, name);
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 join5, relative as relative6, sep as sep3 } from "path";
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
- join5(paths.node, "package-lock.json"),
3434
+ join6(paths.node, "package-lock.json"),
3332
3435
  ...packages.flatMap(
3333
- (pkg) => (pkg.expectedBins ?? []).map((bin) => join5(paths.nodeBin, 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 join6, relative as relative7, sep as sep4 } from "path";
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, join6(options.runtimeLayout.uvBin, "uv")], { cwd: options.runtimeLayout.workspaceRoot });
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 = join6(options.runtimeLayout.uvBin, "uv");
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) => join6(paths.venvBin, 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 join7 } from "path";
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, join7(paths.workspaceRoot, USER_SKILLS_REL)];
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 readdir3, stat as stat6 } from "fs/promises";
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 readdir3(dir, { withFileTypes: true });
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 readdir5, stat as stat8 } from "fs/promises";
4304
- import { join as join8 } from "path";
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 readdir4 } from "fs/promises";
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 log2(msg, meta = {}) {
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 readdir4(dir, { withFileTypes: true });
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
- log2("cache hit", { hash, fileCount: files.length });
4643
+ log3("cache hit", { hash, fileCount: files.length });
4541
4644
  return { url: cached, hash };
4542
4645
  }
4543
4646
  const tarball = await buildTarGz(files);
4544
- log2("tarball built", {
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
- log2("uploaded", { hash, url, totalMs: Date.now() - startMs });
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 readdir5(sourcePath, { withFileTypes: true })) {
4822
+ for (const entry of await readdir6(sourcePath, { withFileTypes: true })) {
4720
4823
  await copyHostPathToVercelWorkspace({
4721
4824
  workspace: options.workspace,
4722
- source: join8(sourcePath, entry.name),
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 join10 } from "path";
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 readdir6,
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 join9, basename as basename2, resolve as resolve7 } from "path";
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 join9(homedir3(), ".pi", "agent", "sessions", safePath);
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 join9(homedir3(), ".pi", "agent", "sessions", safeNamespace);
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 readdir6(this.sessionDir).catch(() => []);
5607
+ const files = await readdir7(this.sessionDir).catch(() => []);
5505
5608
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
5506
- const filepaths = jsonlFiles.map((f) => join9(this.sessionDir, 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 = join9(this.sessionDir, `${id}.jsonl`);
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 = join9(this.sessionDir, `${sessionId}.jsonl`);
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 = join9(this.sessionDir, files[0]);
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 = join9(this.sessionDir, `${sessionId}.jsonl`);
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 readdir6(this.sessionDir).catch(() => []);
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 = join9(this.sessionDir, match);
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 = join9(this.sessionDir, file);
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 readdir6(this.sessionDir).catch(() => []);
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 = join9(this.sessionDir, file);
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 = join9(this.sessionDir, `${sessionId}.jsonl`);
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 = join9(this.sessionDir, `${sessionId}.jsonl`);
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 = join10(agentDir, "settings.json");
6358
- const projectSettingsPath = join10(cwd, ".pi", "settings.json");
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 log3 = createLogger("pi-harness");
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 = opts.pi?.getHotReloadableResources?.() ?? {};
6531
+ const dynamic = pi.getHotReloadableResources?.() ?? {};
6424
6532
  effectiveSkillPaths.splice(
6425
6533
  0,
6426
6534
  effectiveSkillPaths.length,
6427
- ...opts.pi?.additionalSkillPaths ?? [],
6535
+ ...pi.additionalSkillPaths ?? [],
6428
6536
  ...dynamic.additionalSkillPaths ?? []
6429
6537
  );
6430
6538
  effectivePackages.splice(
6431
6539
  0,
6432
6540
  effectivePackages.length,
6433
- ...mergePiPackageSources(opts.pi?.packages ?? [], dynamic.packages ?? [])
6541
+ ...mergePiPackageSources(pi.packages ?? [], dynamic.packages ?? [])
6434
6542
  );
6435
6543
  effectiveExtensionPaths.splice(
6436
6544
  0,
6437
6545
  effectiveExtensionPaths.length,
6438
- ...opts.pi?.extensionPaths ?? [],
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
- ...opts.pi?.extensionFactories ?? []
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
- ...opts.pi?.noContextFiles ? { noContextFiles: true } : {},
6510
- ...opts.pi?.noSkills ? { noSkills: true } : {},
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
- ...opts.pi?.noSkills ? {
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 readdir7, stat as stat9, readFile as readFile10 } from "fs/promises";
6651
- import { join as join11, extname as extname4, resolve as resolve8, sep as sep6, relative as relative9 } from "path";
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 = join11(homedir4(), ".pi", "agent", "extensions");
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 readdir7(dir);
6677
- return entries.filter((e) => VALID_EXTENSIONS.has(extname4(e))).map((e) => ({ path: join11(dir, e), source }));
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 = join11(cwd, "node_modules");
6815
+ const nodeModulesDir = join12(cwd, "node_modules");
6708
6816
  if (!await dirExists2(nodeModulesDir)) return [];
6709
6817
  try {
6710
- const entries = await readdir7(nodeModulesDir);
6711
- return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) => join11(nodeModulesDir, 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 = join11(pkgDir, "package.json");
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 = join11(cwd, EXTENSIONS_JSON);
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 = join11(options.cwd, LOCAL_DIR);
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 = join11(options.cwd, "node_modules", pkg);
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 readdir8, readlink, realpath as realpath4, stat as stat10, writeFile as writeFile7 } from "fs/promises";
6811
- import { dirname as dirname11, isAbsolute as isAbsolute7, join as join12, relative as relative10, resolve as resolve9 } from "path";
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 readdir8(current, { withFileTypes: true });
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 join12(workspaceRoot, ...normalized.slice(runtimeRoot.length + 1).split("/"));
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 readdir8(storagePath);
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") return null;
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 broadcaster = createFsEventBroadcaster(workspace.watch());
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 (!resolved) {
7905
- writeSse(reply.raw, "unsupported", { reason: "watch_not_implemented" });
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 ?? [], Boolean(noSkills)]);
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: false
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 join13 } from "path";
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(join13(storageRoot, filePath));
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 = opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot: root, request }) : opts.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 ?? null,
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 = opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot: root, request }) : opts.pi;
10997
- const hot = pi?.getHotReloadableResources?.();
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?.additionalSkillPaths ?? [],
11119
+ ...pi.additionalSkillPaths ?? [],
11004
11120
  ...hot.additionalSkillPaths ?? []
11005
11121
  ],
11006
11122
  packages: [
11007
- ...pi?.packages ?? [],
11123
+ ...pi.packages ?? [],
11008
11124
  ...hot.packages ?? []
11009
11125
  ],
11010
11126
  extensionPaths: [
11011
- ...pi?.extensionPaths ?? [],
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
- noContextFiles: true,
11189
- noSkills: true,
11304
+ // scope.pi is already defaulted at the resolveScopePi chokepoint.
11190
11305
  ...scope.pi,
11191
11306
  additionalSkillPaths: [
11192
- ...scope.pi?.additionalSkillPaths ?? []
11307
+ ...scope.pi.additionalSkillPaths ?? []
11193
11308
  ],
11194
11309
  getHotReloadableResources: () => {
11195
- const hot = scope.pi?.getHotReloadableResources?.() ?? {};
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?.additionalSkillPaths;
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?.additionalSkillPaths ?? []
11564
+ ...scope.pi.additionalSkillPaths ?? []
11448
11565
  ];
11449
11566
  },
11450
- getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi?.packages,
11451
- getNoSkills: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi?.noSkills
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) => {