@hachej/boring-agent 0.1.40 → 0.1.42

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