@wrongstack/core 0.291.0 → 0.291.2

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 (52) hide show
  1. package/dist/boot.d.ts.map +1 -1
  2. package/dist/chronicle/index.js +20 -1
  3. package/dist/chronicle/index.js.map +2 -2
  4. package/dist/chronicle/tool-adapter.d.ts.map +1 -1
  5. package/dist/coordination/director.d.ts +18 -0
  6. package/dist/coordination/director.d.ts.map +1 -1
  7. package/dist/coordination/fleet-spawn.d.ts.map +1 -1
  8. package/dist/coordination/index.js +37 -15
  9. package/dist/coordination/index.js.map +2 -2
  10. package/dist/core/agent-response.d.ts.map +1 -1
  11. package/dist/defaults/index.js +660 -391
  12. package/dist/defaults/index.js.map +4 -4
  13. package/dist/execution/index.js +61 -12
  14. package/dist/execution/index.js.map +3 -3
  15. package/dist/execution/tool-executor.d.ts.map +1 -1
  16. package/dist/hq/index.js +17 -0
  17. package/dist/hq/index.js.map +2 -2
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1304 -882
  21. package/dist/index.js.map +4 -4
  22. package/dist/infrastructure/index.js +15 -10
  23. package/dist/infrastructure/index.js.map +2 -2
  24. package/dist/kernel/events/tool-events.d.ts +24 -0
  25. package/dist/kernel/events/tool-events.d.ts.map +1 -1
  26. package/dist/plugins/auto-review-plugin.d.ts +10 -0
  27. package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
  28. package/dist/security/index.js +148 -0
  29. package/dist/security/index.js.map +3 -3
  30. package/dist/security/permission-policy.d.ts +8 -1
  31. package/dist/security/permission-policy.d.ts.map +1 -1
  32. package/dist/storage/config-loader.d.ts +8 -0
  33. package/dist/storage/config-loader.d.ts.map +1 -1
  34. package/dist/storage/index.js +457 -379
  35. package/dist/storage/index.js.map +4 -4
  36. package/dist/storage/provider-config-watcher.d.ts +6 -0
  37. package/dist/storage/provider-config-watcher.d.ts.map +1 -1
  38. package/dist/types/permission.d.ts +38 -0
  39. package/dist/types/permission.d.ts.map +1 -1
  40. package/dist/utils/config-backup.d.ts +20 -0
  41. package/dist/utils/config-backup.d.ts.map +1 -0
  42. package/dist/utils/index.d.ts +2 -1
  43. package/dist/utils/index.d.ts.map +1 -1
  44. package/dist/utils/index.js +172 -106
  45. package/dist/utils/index.js.map +4 -4
  46. package/dist/utils/message-invariants.d.ts +12 -9
  47. package/dist/utils/message-invariants.d.ts.map +1 -1
  48. package/dist/utils/term.d.ts +6 -0
  49. package/dist/utils/term.d.ts.map +1 -1
  50. package/dist/utils/wstack-paths.d.ts +10 -0
  51. package/dist/utils/wstack-paths.d.ts.map +1 -1
  52. package/package.json +2 -2
@@ -2,14 +2,14 @@
2
2
  import { createHash as createHash4 } from "node:crypto";
3
3
  import { createReadStream } from "node:fs";
4
4
  import * as fsp4 from "node:fs/promises";
5
- import * as path7 from "node:path";
5
+ import * as path8 from "node:path";
6
6
  import { createInterface } from "node:readline";
7
7
 
8
8
  // src/utils/atomic-write.ts
9
9
  import { randomBytes as randomBytes2 } from "node:crypto";
10
- import * as fs2 from "node:fs/promises";
10
+ import * as fs3 from "node:fs/promises";
11
11
  import { watch as watchDir } from "node:fs";
12
- import * as path3 from "node:path";
12
+ import * as path4 from "node:path";
13
13
 
14
14
  // src/utils/child-env.ts
15
15
  var ALLOWED_KEYS = /* @__PURE__ */ new Set([
@@ -186,6 +186,37 @@ var color = {
186
186
  bgGreen: wrap("42", "49")
187
187
  };
188
188
 
189
+ // src/utils/config-backup.ts
190
+ import * as fs from "node:fs/promises";
191
+ import * as path from "node:path";
192
+ function configHistoryDir(globalRoot) {
193
+ return path.join(globalRoot, "config-history");
194
+ }
195
+ function configSlug(absolutePath, globalRoot) {
196
+ const rel = path.relative(globalRoot, absolutePath);
197
+ const normalized = rel.replace(/\\/g, "/").replace(/\.json$/i, "");
198
+ return normalized.replace(/\//g, "-");
199
+ }
200
+ async function backupConfigFile(filePath, paths) {
201
+ let currentContent;
202
+ try {
203
+ currentContent = await fs.readFile(filePath, "utf8");
204
+ if (!currentContent.trim()) return;
205
+ } catch {
206
+ return;
207
+ }
208
+ const now = /* @__PURE__ */ new Date();
209
+ const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
210
+ const slug = configSlug(filePath, paths.globalRoot);
211
+ const backupDir = configHistoryDir(paths.globalRoot);
212
+ const backupFile = path.join(backupDir, `${slug}-${ts}.json`);
213
+ try {
214
+ await fs.mkdir(backupDir, { recursive: true });
215
+ await fs.writeFile(backupFile, currentContent, { mode: 384, encoding: "utf8" });
216
+ } catch {
217
+ }
218
+ }
219
+
189
220
  // src/utils/deep-merge.ts
190
221
  var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
191
222
  "__proto__",
@@ -261,16 +292,6 @@ function expectDefined(value, label) {
261
292
  }
262
293
 
263
294
  // src/utils/message-invariants.ts
264
- function hasMeaningfulContent(content) {
265
- if (typeof content === "string") return content.trim().length > 0;
266
- return content.some((block) => {
267
- if (block.type === "text") return block.text.trim().length > 0;
268
- if (block.type === "thinking") {
269
- return block.thinking.trim().length > 0 || Boolean(block.signature);
270
- }
271
- return true;
272
- });
273
- }
274
295
  function repairToolUseAdjacency(messages) {
275
296
  const removedToolUses = [];
276
297
  const removedToolResults = [];
@@ -357,6 +378,21 @@ function mapContent(msg, fn) {
357
378
  }
358
379
  return { ...msg, content: next };
359
380
  }
381
+ function hasMeaningfulContent(content) {
382
+ if (typeof content === "string") return content.trim().length > 0;
383
+ for (const block of content) {
384
+ if (block.type === "text") {
385
+ if (block.text.trim().length > 0) return true;
386
+ continue;
387
+ }
388
+ if (block.type === "thinking") {
389
+ if (block.thinking.trim().length > 0 || block.signature) return true;
390
+ continue;
391
+ }
392
+ return true;
393
+ }
394
+ return false;
395
+ }
360
396
  function isEmptyMessage(msg) {
361
397
  return !hasMeaningfulContent(msg.content);
362
398
  }
@@ -413,14 +449,14 @@ function safeParse(input, maxBytes = 5e6) {
413
449
  }
414
450
 
415
451
  // src/utils/session-scoped-path.ts
416
- import * as path from "node:path";
452
+ import * as path2 from "node:path";
417
453
  function sessionScopedPath(dir, sessionId, suffix) {
418
454
  if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
419
455
  throw invalid(sessionId);
420
456
  }
421
- const resolved = path.resolve(dir, `${sessionId}${suffix}`);
422
- const rel = path.relative(path.resolve(dir), resolved);
423
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
457
+ const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
458
+ const rel = path2.relative(path2.resolve(dir), resolved);
459
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
424
460
  throw invalid(sessionId);
425
461
  }
426
462
  return resolved;
@@ -469,23 +505,23 @@ function ulid(seedTime = Date.now()) {
469
505
 
470
506
  // src/utils/wstack-paths.ts
471
507
  import { createHash } from "node:crypto";
472
- import * as fs from "node:fs";
508
+ import * as fs2 from "node:fs";
473
509
  import * as os from "node:os";
474
- import * as path2 from "node:path";
510
+ import * as path3 from "node:path";
475
511
  function canonicalProjectRoot(absRoot) {
476
- const checkoutRoot = path2.resolve(absRoot);
477
- const dotGit = path2.join(checkoutRoot, ".git");
512
+ const checkoutRoot = path3.resolve(absRoot);
513
+ const dotGit = path3.join(checkoutRoot, ".git");
478
514
  try {
479
- if (!fs.statSync(dotGit).isFile()) return checkoutRoot;
480
- const gitDirLine = fs.readFileSync(dotGit, "utf8").trim();
515
+ if (!fs2.statSync(dotGit).isFile()) return checkoutRoot;
516
+ const gitDirLine = fs2.readFileSync(dotGit, "utf8").trim();
481
517
  const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
482
518
  if (!match?.[1]) return checkoutRoot;
483
- const gitDir = path2.resolve(checkoutRoot, match[1].trim());
484
- const commonDirFile = path2.join(gitDir, "commondir");
485
- if (!fs.statSync(commonDirFile).isFile()) return checkoutRoot;
486
- const commonDir = path2.resolve(gitDir, fs.readFileSync(commonDirFile, "utf8").trim());
487
- if (path2.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
488
- return path2.dirname(commonDir);
519
+ const gitDir = path3.resolve(checkoutRoot, match[1].trim());
520
+ const commonDirFile = path3.join(gitDir, "commondir");
521
+ if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
522
+ const commonDir = path3.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
523
+ if (path3.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
524
+ return path3.dirname(commonDir);
489
525
  } catch {
490
526
  return checkoutRoot;
491
527
  }
@@ -495,7 +531,7 @@ function projectHash(absRoot) {
495
531
  }
496
532
  function projectSlug(absRoot) {
497
533
  const identityRoot = canonicalProjectRoot(absRoot);
498
- const base = slugify2(path2.basename(identityRoot));
534
+ const base = slugify2(path3.basename(identityRoot));
499
535
  const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
500
536
  return `${base}-${hash}`;
501
537
  }
@@ -504,66 +540,83 @@ function slugify2(name) {
504
540
  }
505
541
  function wstackGlobalRoot() {
506
542
  const fromEnv = process.env["WRONGSTACK_HOME"];
507
- if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
508
- return path2.join(os.homedir(), ".wrongstack");
543
+ if (fromEnv && fromEnv.trim().length > 0) return path3.resolve(fromEnv);
544
+ return path3.join(os.homedir(), ".wrongstack");
509
545
  }
510
546
  function resolveWstackPaths(opts) {
511
- const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
547
+ const globalRoot = opts.globalRoot ?? (opts.userHome ? path3.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
512
548
  const homeDir = opts.userHome ?? os.homedir();
513
549
  const hash = projectHash(opts.projectRoot);
514
550
  const slug = projectSlug(opts.projectRoot);
515
- const projectDir = path2.join(globalRoot, "projects", slug);
551
+ const projectDir = path3.join(globalRoot, "projects", slug);
516
552
  return {
517
553
  globalRoot,
518
554
  projectRoot: opts.projectRoot,
519
555
  homeDir,
520
556
  configDir: globalRoot,
521
- globalConfig: path2.join(globalRoot, "config.json"),
522
- profilesDir: path2.join(globalRoot, "profiles"),
557
+ globalConfig: path3.join(globalRoot, "config.json"),
558
+ profilesDir: path3.join(globalRoot, "profiles"),
523
559
  profileConfig: (name) => {
524
560
  const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
525
- return path2.join(globalRoot, "profiles", safe || "default", "config.json");
561
+ return path3.join(globalRoot, "profiles", safe || "default", "config.json");
562
+ },
563
+ profileStatuslineConfig: (name) => {
564
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
565
+ return path3.join(globalRoot, "profiles", safe || "default", "statusline.json");
566
+ },
567
+ profileModeConfig: (name) => {
568
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
569
+ return path3.join(globalRoot, "profiles", safe || "default", "mode.json");
570
+ },
571
+ profileProviderStatus: (name) => {
572
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
573
+ return path3.join(globalRoot, "profiles", safe || "default", "provider-status.json");
574
+ },
575
+ profileUpdateCache: (name) => {
576
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
577
+ return path3.join(globalRoot, "profiles", safe || "default", "update-cache.json");
526
578
  },
527
- secretsKey: path2.join(globalRoot, ".key"),
528
- globalMemory: path2.join(globalRoot, "memory.md"),
529
- globalSkills: path2.join(globalRoot, "skills"),
530
- globalClaudeSkills: path2.join(homeDir, ".claude", "skills"),
531
- globalDesignKits: path2.join(globalRoot, "design-kits"),
532
- globalPrompts: path2.join(globalRoot, "prompts"),
533
- globalInstructions: path2.join(globalRoot, "instructions"),
534
- promptUsage: path2.join(globalRoot, "prompt-usage.json"),
535
- cacheDir: path2.join(globalRoot, "cache"),
536
- modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
537
- modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
538
- historyFile: path2.join(globalRoot, "history"),
539
- logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
579
+ secretsKey: path3.join(globalRoot, ".key"),
580
+ globalMemory: path3.join(globalRoot, "memory.md"),
581
+ globalSkills: path3.join(globalRoot, "skills"),
582
+ globalClaudeSkills: path3.join(homeDir, ".claude", "skills"),
583
+ globalDesignKits: path3.join(globalRoot, "design-kits"),
584
+ globalPrompts: path3.join(globalRoot, "prompts"),
585
+ globalInstructions: path3.join(globalRoot, "instructions"),
586
+ promptUsage: path3.join(globalRoot, "prompt-usage.json"),
587
+ cacheDir: path3.join(globalRoot, "cache"),
588
+ modelsCache: path3.join(globalRoot, "cache", "models.dev.json"),
589
+ modelsOverlayCache: path3.join(globalRoot, "cache", "models-overlay.json"),
590
+ historyFile: path3.join(globalRoot, "history"),
591
+ logFile: path3.join(globalRoot, "logs", "wrongstack.log"),
540
592
  projectDir,
541
- projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
542
- projectMemory: path2.join(projectDir, "memory.md"),
543
- projectSessions: path2.join(projectDir, "sessions"),
544
- projectTrust: path2.join(projectDir, "trust.json"),
545
- projectMeta: path2.join(projectDir, "meta.json"),
546
- projectLocalConfig: path2.join(projectDir, "config.local.json"),
547
- inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
548
- inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
549
- inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
550
- inProjectClaudeSkills: path2.join(opts.projectRoot, ".claude", "skills"),
551
- inProjectPrompts: path2.join(opts.projectRoot, ".wrongstack", "prompts"),
552
- inProjectInstructions: path2.join(opts.projectRoot, ".wrongstack", "instructions"),
553
- inProjectDesignKits: path2.join(opts.projectRoot, ".wrongstack", "design-kits"),
554
- inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
593
+ projectCodebaseIndex: path3.join(projectDir, "codebase-index"),
594
+ projectMemory: path3.join(projectDir, "memory.md"),
595
+ projectSessions: path3.join(projectDir, "sessions"),
596
+ projectTrust: path3.join(projectDir, "trust.json"),
597
+ projectMeta: path3.join(projectDir, "meta.json"),
598
+ projectLocalConfig: path3.join(projectDir, "config.local.json"),
599
+ inProjectConfig: path3.join(opts.projectRoot, ".wrongstack", "config.json"),
600
+ inProjectAgentsFile: path3.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
601
+ inProjectSkills: path3.join(opts.projectRoot, ".wrongstack", "skills"),
602
+ inProjectClaudeSkills: path3.join(opts.projectRoot, ".claude", "skills"),
603
+ inProjectPrompts: path3.join(opts.projectRoot, ".wrongstack", "prompts"),
604
+ inProjectInstructions: path3.join(opts.projectRoot, ".wrongstack", "instructions"),
605
+ inProjectDesignKits: path3.join(opts.projectRoot, ".wrongstack", "design-kits"),
606
+ inProjectWorktrees: path3.join(opts.projectRoot, ".wrongstack", "worktrees"),
555
607
  projectHash: hash,
556
608
  projectSlug: slug,
557
- projectGoal: path2.join(projectDir, "goal.json"),
558
- projectInputHistory: path2.join(projectDir, "input-history.json"),
559
- projectSpecs: path2.join(projectDir, "specs"),
560
- projectTaskGraphs: path2.join(projectDir, "task-graphs"),
561
- projectSddSession: path2.join(projectDir, "sdd-session.json"),
562
- projectPlan: path2.join(projectDir, "plan.json"),
563
- projectAutophase: path2.join(projectDir, "autophase"),
564
- projectSddBoards: path2.join(projectDir, "sdd-boards"),
565
- syncConfig: path2.join(globalRoot, "sync.json"),
566
- projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
609
+ projectGoal: path3.join(projectDir, "goal.json"),
610
+ projectInputHistory: path3.join(projectDir, "input-history.json"),
611
+ projectSpecs: path3.join(projectDir, "specs"),
612
+ projectTaskGraphs: path3.join(projectDir, "task-graphs"),
613
+ projectSddSession: path3.join(projectDir, "sdd-session.json"),
614
+ projectPlan: path3.join(projectDir, "plan.json"),
615
+ projectAutophase: path3.join(projectDir, "autophase"),
616
+ projectSddBoards: path3.join(projectDir, "sdd-boards"),
617
+ syncConfig: path3.join(globalRoot, "sync.json"),
618
+ configHistoryDir: path3.join(globalRoot, "config-history"),
619
+ projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
567
620
  };
568
621
  }
569
622
 
@@ -701,17 +754,17 @@ var FsError = class extends WrongStackError {
701
754
 
702
755
  // src/utils/atomic-write.ts
703
756
  async function atomicWrite(targetPath, content, opts = {}) {
704
- const dir = path3.dirname(targetPath);
705
- await fs2.mkdir(dir, { recursive: true });
706
- const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes2(6).toString("hex")}.tmp`);
757
+ const dir = path4.dirname(targetPath);
758
+ await fs3.mkdir(dir, { recursive: true });
759
+ const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes2(6).toString("hex")}.tmp`);
707
760
  try {
708
761
  if (typeof content === "string") {
709
- await fs2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
762
+ await fs3.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
710
763
  } else {
711
- await fs2.writeFile(tmp, content, { flag: "wx" });
764
+ await fs3.writeFile(tmp, content, { flag: "wx" });
712
765
  }
713
766
  try {
714
- const fh = await fs2.open(tmp, "r+");
767
+ const fh = await fs3.open(tmp, "r+");
715
768
  try {
716
769
  await fh.sync();
717
770
  } finally {
@@ -721,63 +774,63 @@ async function atomicWrite(targetPath, content, opts = {}) {
721
774
  }
722
775
  let mode;
723
776
  try {
724
- const stat13 = await fs2.stat(targetPath);
777
+ const stat13 = await fs3.stat(targetPath);
725
778
  mode = stat13.mode & 511;
726
779
  } catch {
727
780
  mode = opts.mode;
728
781
  }
729
782
  if (mode !== void 0) {
730
- await fs2.chmod(tmp, mode);
783
+ await fs3.chmod(tmp, mode);
731
784
  }
732
785
  await renameWithRetry(tmp, targetPath);
733
786
  if (mode !== void 0 && process.platform === "win32") {
734
787
  try {
735
- await fs2.chmod(targetPath, mode);
788
+ await fs3.chmod(targetPath, mode);
736
789
  } catch {
737
790
  }
738
791
  }
739
792
  } catch (err) {
740
793
  try {
741
- await fs2.unlink(tmp);
794
+ await fs3.unlink(tmp);
742
795
  } catch {
743
796
  }
744
797
  throw err;
745
798
  }
746
799
  }
747
800
  async function ensureDir(dir) {
748
- await fs2.mkdir(dir, { recursive: true });
801
+ await fs3.mkdir(dir, { recursive: true });
749
802
  }
750
803
  async function withFileLock(targetPath, fn, opts = {}) {
751
- const dir = path3.dirname(targetPath);
752
- await fs2.mkdir(dir, { recursive: true });
753
- const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
804
+ const dir = path4.dirname(targetPath);
805
+ await fs3.mkdir(dir, { recursive: true });
806
+ const lockPath = path4.join(dir, `.${path4.basename(targetPath)}.lock`);
754
807
  const timeoutMs = opts.timeoutMs ?? 15e3;
755
808
  const staleMs = opts.staleMs ?? 3e4;
756
809
  const started = Date.now();
757
810
  let handle;
758
811
  for (; ; ) {
759
812
  try {
760
- handle = await fs2.open(lockPath, "wx");
813
+ handle = await fs3.open(lockPath, "wx");
761
814
  await handle.writeFile(`${process.pid}:${Date.now()}`);
762
815
  break;
763
816
  } catch (err) {
764
817
  if (handle) {
765
818
  await handle.close().catch(() => {
766
819
  });
767
- await fs2.unlink(lockPath).catch(() => {
820
+ await fs3.unlink(lockPath).catch(() => {
768
821
  });
769
822
  handle = void 0;
770
823
  }
771
824
  const code = err.code;
772
825
  if (code === "ENOENT") {
773
- await fs2.mkdir(dir, { recursive: true });
826
+ await fs3.mkdir(dir, { recursive: true });
774
827
  continue;
775
828
  }
776
829
  if (code !== "EEXIST" && code !== "EPERM") throw err;
777
830
  try {
778
- const stat13 = await fs2.stat(lockPath);
831
+ const stat13 = await fs3.stat(lockPath);
779
832
  if (Date.now() - stat13.mtimeMs > staleMs) {
780
- await fs2.unlink(lockPath);
833
+ await fs3.unlink(lockPath);
781
834
  continue;
782
835
  }
783
836
  } catch {
@@ -803,14 +856,14 @@ async function withFileLock(targetPath, fn, opts = {}) {
803
856
  } catch {
804
857
  }
805
858
  try {
806
- await fs2.unlink(lockPath);
859
+ await fs3.unlink(lockPath);
807
860
  } catch {
808
861
  }
809
862
  }
810
863
  }
811
864
  async function waitForLockRelease(lockPath, remainingMs) {
812
- const parentDir = path3.dirname(lockPath);
813
- const lockName = path3.basename(lockPath);
865
+ const parentDir = path4.dirname(lockPath);
866
+ const lockName = path4.basename(lockPath);
814
867
  const intervalMs = Math.min(remainingMs, 100);
815
868
  return new Promise((resolve11) => {
816
869
  let settled = false;
@@ -838,7 +891,7 @@ async function waitForLockRelease(lockPath, remainingMs) {
838
891
  }
839
892
  return;
840
893
  }
841
- fs2.access(lockPath).then(
894
+ fs3.access(lockPath).then(
842
895
  () => {
843
896
  },
844
897
  () => {
@@ -855,14 +908,14 @@ async function waitForLockRelease(lockPath, remainingMs) {
855
908
  var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
856
909
  async function renameWithRetry(from, to) {
857
910
  if (process.platform !== "win32") {
858
- await fs2.rename(from, to);
911
+ await fs3.rename(from, to);
859
912
  return;
860
913
  }
861
914
  const delays = [10, 25, 60, 120, 250];
862
915
  let lastErr;
863
916
  for (let i = 0; i <= delays.length; i++) {
864
917
  try {
865
- await fs2.rename(from, to);
918
+ await fs3.rename(from, to);
866
919
  return;
867
920
  } catch (err) {
868
921
  lastErr = err;
@@ -879,7 +932,7 @@ async function renameWithRetry(from, to) {
879
932
  // src/storage/file-session-writer.ts
880
933
  import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
881
934
  import * as fsp from "node:fs/promises";
882
- import * as path4 from "node:path";
935
+ import * as path5 from "node:path";
883
936
 
884
937
  // src/storage/session-helpers.ts
885
938
  function userInputTitle(content) {
@@ -896,7 +949,7 @@ var FileSessionWriter = class _FileSessionWriter {
896
949
  this.meta = meta;
897
950
  this.events = events;
898
951
  this.resumed = opts.resumed ?? false;
899
- this.manifestFile = opts.dir ? path4.join(opts.dir, `${path4.basename(id)}.summary.json`) : "";
952
+ this.manifestFile = opts.dir ? path5.join(opts.dir, `${path5.basename(id)}.summary.json`) : "";
900
953
  this.filePath = opts.filePath ?? "";
901
954
  this.secretScrubber = opts.secretScrubber;
902
955
  this.checkpointCas = opts.checkpointCas;
@@ -1678,7 +1731,7 @@ var FileSessionWriter = class _FileSessionWriter {
1678
1731
  import { spawn } from "node:child_process";
1679
1732
  import { createHash as createHash2, randomUUID } from "node:crypto";
1680
1733
  import * as fsp2 from "node:fs/promises";
1681
- import * as path5 from "node:path";
1734
+ import * as path6 from "node:path";
1682
1735
 
1683
1736
  // src/storage/storage-concurrency.ts
1684
1737
  async function mapWithConcurrency(items, concurrency, mapper) {
@@ -1709,29 +1762,29 @@ function sha256(content) {
1709
1762
  return createHash2("sha256").update(content).digest("hex");
1710
1763
  }
1711
1764
  function isInside(root, target) {
1712
- const relative6 = path5.relative(root, target);
1713
- return relative6 === "" || !relative6.startsWith("..") && !path5.isAbsolute(relative6);
1765
+ const relative7 = path6.relative(root, target);
1766
+ return relative7 === "" || !relative7.startsWith("..") && !path6.isAbsolute(relative7);
1714
1767
  }
1715
1768
  function normalizeRelative(input) {
1716
- if (!input || path5.isAbsolute(input)) return null;
1769
+ if (!input || path6.isAbsolute(input)) return null;
1717
1770
  const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
1718
- const resolved = path5.posix.normalize(normalized);
1771
+ const resolved = path6.posix.normalize(normalized);
1719
1772
  if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
1720
1773
  return resolved;
1721
1774
  }
1722
1775
  function parseNulPaths(output) {
1723
1776
  return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
1724
1777
  }
1725
- function isWrongStackWorktreePath(relative6) {
1726
- return relative6 === ".wrongstack/worktrees" || relative6.startsWith(".wrongstack/worktrees/");
1778
+ function isWrongStackWorktreePath(relative7) {
1779
+ return relative7 === ".wrongstack/worktrees" || relative7.startsWith(".wrongstack/worktrees/");
1727
1780
  }
1728
1781
  var SessionCheckpointCas = class {
1729
1782
  rootDir;
1730
1783
  projectRoot;
1731
1784
  runGit;
1732
1785
  constructor(opts) {
1733
- this.rootDir = path5.resolve(opts.rootDir);
1734
- this.projectRoot = path5.resolve(opts.projectRoot);
1786
+ this.rootDir = path6.resolve(opts.rootDir);
1787
+ this.projectRoot = path6.resolve(opts.projectRoot);
1735
1788
  this.runGit = opts.runGit ?? defaultRunGit;
1736
1789
  }
1737
1790
  async capture(_sessionId, _promptIndex) {
@@ -1749,46 +1802,46 @@ var SessionCheckpointCas = class {
1749
1802
  }
1750
1803
  const relativePaths = [
1751
1804
  .../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
1752
- ].filter((relative6) => !isWrongStackWorktreePath(relative6)).sort();
1805
+ ].filter((relative7) => !isWrongStackWorktreePath(relative7)).sort();
1753
1806
  const unresolved = [];
1754
1807
  let scheduledBytes = 0;
1755
1808
  const captured = await mapWithConcurrency(
1756
1809
  relativePaths,
1757
1810
  CAPTURE_CONCURRENCY,
1758
- async (relative6) => {
1759
- const absolute = path5.resolve(this.projectRoot, ...relative6.split("/"));
1811
+ async (relative7) => {
1812
+ const absolute = path6.resolve(this.projectRoot, ...relative7.split("/"));
1760
1813
  if (!isInside(this.projectRoot, absolute)) {
1761
- unresolved.push({ path: relative6, reason: "path escapes project root" });
1814
+ unresolved.push({ path: relative7, reason: "path escapes project root" });
1762
1815
  return null;
1763
1816
  }
1764
1817
  try {
1765
1818
  const stat13 = await fsp2.lstat(absolute);
1766
1819
  if (stat13.isSymbolicLink()) {
1767
1820
  const linkTarget = await fsp2.readlink(absolute);
1768
- const resolvedLink = path5.resolve(path5.dirname(absolute), linkTarget);
1769
- if (path5.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
1821
+ const resolvedLink = path6.resolve(path6.dirname(absolute), linkTarget);
1822
+ if (path6.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
1770
1823
  unresolved.push({
1771
- path: relative6,
1824
+ path: relative7,
1772
1825
  reason: "symlink target escapes project root"
1773
1826
  });
1774
1827
  return null;
1775
1828
  }
1776
- return { path: relative6, state: "symlink", linkTarget };
1829
+ return { path: relative7, state: "symlink", linkTarget };
1777
1830
  }
1778
1831
  if (!stat13.isFile()) {
1779
- unresolved.push({ path: relative6, reason: "changed path is not a regular file" });
1832
+ unresolved.push({ path: relative7, reason: "changed path is not a regular file" });
1780
1833
  return null;
1781
1834
  }
1782
1835
  if (stat13.size > MAX_BLOB_BYTES) {
1783
1836
  unresolved.push({
1784
- path: relative6,
1837
+ path: relative7,
1785
1838
  reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
1786
1839
  });
1787
1840
  return null;
1788
1841
  }
1789
1842
  if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
1790
1843
  unresolved.push({
1791
- path: relative6,
1844
+ path: relative7,
1792
1845
  reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
1793
1846
  });
1794
1847
  return null;
@@ -1797,12 +1850,12 @@ var SessionCheckpointCas = class {
1797
1850
  const content = await fsp2.readFile(absolute);
1798
1851
  const blobHash = sha256(content);
1799
1852
  await this.putBlob(blobHash, content);
1800
- return { path: relative6, state: "file", blobHash, mode: stat13.mode & 511 };
1853
+ return { path: relative7, state: "file", blobHash, mode: stat13.mode & 511 };
1801
1854
  } catch (err) {
1802
1855
  if (err.code === "ENOENT") {
1803
- return { path: relative6, state: "absent" };
1856
+ return { path: relative7, state: "absent" };
1804
1857
  }
1805
- unresolved.push({ path: relative6, reason: toErrorMessage(err) });
1858
+ unresolved.push({ path: relative7, reason: toErrorMessage(err) });
1806
1859
  return null;
1807
1860
  }
1808
1861
  }
@@ -1828,7 +1881,7 @@ var SessionCheckpointCas = class {
1828
1881
  };
1829
1882
  }
1830
1883
  async materialize(checkpoint, targetRoot) {
1831
- const target = path5.resolve(targetRoot);
1884
+ const target = path6.resolve(targetRoot);
1832
1885
  if (target === this.projectRoot) {
1833
1886
  throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
1834
1887
  }
@@ -1868,10 +1921,10 @@ var SessionCheckpointCas = class {
1868
1921
  try {
1869
1922
  const output = await this.safeOutputPath(target, realTarget, entry.path);
1870
1923
  if (entry.state === "symlink") {
1871
- if (path5.isAbsolute(entry.linkTarget)) {
1924
+ if (path6.isAbsolute(entry.linkTarget)) {
1872
1925
  throw new Error("absolute symlink target refused");
1873
1926
  }
1874
- const resolvedLink = path5.resolve(path5.dirname(output), entry.linkTarget);
1927
+ const resolvedLink = path6.resolve(path6.dirname(output), entry.linkTarget);
1875
1928
  if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
1876
1929
  }
1877
1930
  prepared.push({
@@ -1899,7 +1952,7 @@ var SessionCheckpointCas = class {
1899
1952
  await fsp2.unlink(output).catch((err) => {
1900
1953
  if (err.code !== "ENOENT") throw err;
1901
1954
  });
1902
- await fsp2.mkdir(path5.dirname(output), { recursive: true });
1955
+ await fsp2.mkdir(path6.dirname(output), { recursive: true });
1903
1956
  await fsp2.symlink(entry.linkTarget, output);
1904
1957
  writtenFiles.push(entry.path);
1905
1958
  continue;
@@ -1916,15 +1969,15 @@ var SessionCheckpointCas = class {
1916
1969
  }
1917
1970
  objectPath(hash) {
1918
1971
  if (!HASH_RE.test(hash)) throw new Error(`Invalid CAS object hash: ${hash}`);
1919
- return path5.join(this.rootDir, "objects", hash.slice(0, 2), hash.slice(2));
1972
+ return path6.join(this.rootDir, "objects", hash.slice(0, 2), hash.slice(2));
1920
1973
  }
1921
1974
  manifestPath(hash) {
1922
1975
  if (!HASH_RE.test(hash)) throw new Error(`Invalid checkpoint manifest hash: ${hash}`);
1923
- return path5.join(this.rootDir, "manifests", `${hash}.json`);
1976
+ return path6.join(this.rootDir, "manifests", `${hash}.json`);
1924
1977
  }
1925
1978
  async putBlob(hash, content) {
1926
1979
  const target = this.objectPath(hash);
1927
- await fsp2.mkdir(path5.dirname(target), { recursive: true });
1980
+ await fsp2.mkdir(path6.dirname(target), { recursive: true });
1928
1981
  try {
1929
1982
  const existing = await fsp2.readFile(target);
1930
1983
  if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
@@ -1932,9 +1985,9 @@ var SessionCheckpointCas = class {
1932
1985
  } catch (err) {
1933
1986
  if (err.code !== "ENOENT") throw err;
1934
1987
  }
1935
- const temp = path5.join(
1936
- path5.dirname(target),
1937
- `.${path5.basename(target)}.${process.pid}.${randomUUID()}.tmp`
1988
+ const temp = path6.join(
1989
+ path6.dirname(target),
1990
+ `.${path6.basename(target)}.${process.pid}.${randomUUID()}.tmp`
1938
1991
  );
1939
1992
  let handle;
1940
1993
  try {
@@ -1992,10 +2045,10 @@ var SessionCheckpointCas = class {
1992
2045
  }
1993
2046
  return parsed;
1994
2047
  }
1995
- async safeOutputPath(target, realTarget, relative6) {
1996
- const normalized = normalizeRelative(relative6);
2048
+ async safeOutputPath(target, realTarget, relative7) {
2049
+ const normalized = normalizeRelative(relative7);
1997
2050
  if (!normalized) throw new Error("invalid relative path");
1998
- const output = path5.resolve(target, ...normalized.split("/"));
2051
+ const output = path6.resolve(target, ...normalized.split("/"));
1999
2052
  if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
2000
2053
  let probe = output;
2001
2054
  for (; ; ) {
@@ -2005,7 +2058,7 @@ var SessionCheckpointCas = class {
2005
2058
  return output;
2006
2059
  } catch (err) {
2007
2060
  if (err.code !== "ENOENT") throw err;
2008
- const parent = path5.dirname(probe);
2061
+ const parent = path6.dirname(probe);
2009
2062
  if (parent === probe) throw err;
2010
2063
  probe = parent;
2011
2064
  }
@@ -2074,13 +2127,13 @@ function generateSessionId(startedAt, _model) {
2074
2127
  // src/storage/session-resume-validation.ts
2075
2128
  import { createHash as createHash3 } from "node:crypto";
2076
2129
  import * as fsp3 from "node:fs/promises";
2077
- import * as path6 from "node:path";
2130
+ import * as path7 from "node:path";
2078
2131
  var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
2079
2132
  var VALIDATION_CONCURRENCY = 8;
2080
2133
  var NOTICE_PATH_LIMIT = 20;
2081
2134
  function isInside2(root, target) {
2082
- const relative6 = path6.relative(root, target);
2083
- return relative6 === "" || !relative6.startsWith("..") && !path6.isAbsolute(relative6);
2135
+ const relative7 = path7.relative(root, target);
2136
+ return relative7 === "" || !relative7.startsWith("..") && !path7.isAbsolute(relative7);
2084
2137
  }
2085
2138
  function errno(err) {
2086
2139
  return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
@@ -2091,7 +2144,7 @@ function latestObservations(events, projectRoot) {
2091
2144
  if (event.type !== "file_observation" || typeof event.path !== "string" || event.path.length === 0 || typeof event.hash !== "string" || !/^[a-f\d]{64}$/i.test(event.hash)) {
2092
2145
  continue;
2093
2146
  }
2094
- const normalized = path6.resolve(projectRoot, event.path);
2147
+ const normalized = path7.resolve(projectRoot, event.path);
2095
2148
  latest.set(normalized, {
2096
2149
  path: normalized,
2097
2150
  hash: event.hash.toLowerCase(),
@@ -2149,7 +2202,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
2149
2202
  }
2150
2203
  }
2151
2204
  async function validateResumeFileObservations(events, projectRoot) {
2152
- const lexicalRoot = path6.resolve(projectRoot);
2205
+ const lexicalRoot = path7.resolve(projectRoot);
2153
2206
  const realRoot = await fsp3.realpath(lexicalRoot).catch(() => lexicalRoot);
2154
2207
  const observations = latestObservations(events, lexicalRoot);
2155
2208
  const results = await mapWithConcurrency(
@@ -2165,10 +2218,10 @@ async function validateResumeFileObservations(events, projectRoot) {
2165
2218
  }
2166
2219
  function formatResumeValidationNotice(validation, projectRoot) {
2167
2220
  if (validation.staleFiles.length === 0) return null;
2168
- const root = path6.resolve(projectRoot);
2221
+ const root = path7.resolve(projectRoot);
2169
2222
  const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
2170
- const relative6 = path6.relative(root, entry.path);
2171
- const display = isInside2(root, entry.path) ? relative6 || "." : entry.path;
2223
+ const relative7 = path7.relative(root, entry.path);
2224
+ const display = isInside2(root, entry.path) ? relative7 || "." : entry.path;
2172
2225
  return `- ${JSON.stringify(display)} [${entry.status}]`;
2173
2226
  });
2174
2227
  const omitted = validation.staleFiles.length - shown.length;
@@ -2301,9 +2354,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
2301
2354
  static LIST_SCAN_CONCURRENCY = 32;
2302
2355
  constructor(opts) {
2303
2356
  this.dir = opts.dir;
2304
- this.projectRoot = opts.projectRoot ? path7.resolve(opts.projectRoot) : void 0;
2357
+ this.projectRoot = opts.projectRoot ? path8.resolve(opts.projectRoot) : void 0;
2305
2358
  this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
2306
- rootDir: path7.join(this.dir, "_cas"),
2359
+ rootDir: path8.join(this.dir, "_cas"),
2307
2360
  projectRoot: this.projectRoot
2308
2361
  }) : void 0;
2309
2362
  this.events = opts.events;
@@ -2369,17 +2422,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
2369
2422
  }
2370
2423
  /** Absolute path to the session index file. */
2371
2424
  get indexFile() {
2372
- return path7.join(this.dir, "_index.jsonl");
2425
+ return path8.join(this.dir, "_index.jsonl");
2373
2426
  }
2374
2427
  /** Join session ID to its absolute path within the store directory. */
2375
2428
  sessionPath(id, ext) {
2376
2429
  return sessionScopedPath(this.dir, id, ext);
2377
2430
  }
2378
2431
  shardManifestPath(shardKey) {
2379
- return shardKey ? path7.join(this.dir, shardKey, "_manifest.json") : path7.join(this.dir, "_manifest.json");
2432
+ return shardKey ? path8.join(this.dir, shardKey, "_manifest.json") : path8.join(this.dir, "_manifest.json");
2380
2433
  }
2381
2434
  shardKeyForSessionId(id) {
2382
- const dirName = path7.dirname(id);
2435
+ const dirName = path8.dirname(id);
2383
2436
  return dirName === "." ? "" : dirName;
2384
2437
  }
2385
2438
  invalidateShardManifestBySessionId(id) {
@@ -2391,7 +2444,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2391
2444
  * subdirectory so sessions group naturally by day.
2392
2445
  */
2393
2446
  async ensureShardDir(id) {
2394
- const dirPath = path7.dirname(sessionScopedPath(this.dir, id, ""));
2447
+ const dirPath = path8.dirname(sessionScopedPath(this.dir, id, ""));
2395
2448
  await ensureDir(dirPath);
2396
2449
  return dirPath;
2397
2450
  }
@@ -2555,7 +2608,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2555
2608
  // Shard directory (sessions/<date>/) — must match create() so the
2556
2609
  // .summary.json sidecar lands next to the JSONL instead of the
2557
2610
  // sessions root (where summaryFor() would never find it).
2558
- dir: path7.dirname(file),
2611
+ dir: path8.dirname(file),
2559
2612
  filePath: file,
2560
2613
  secretScrubber: this.secretScrubber,
2561
2614
  checkpointCas: this.checkpointCas,
@@ -3176,7 +3229,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
3176
3229
  return entry;
3177
3230
  }
3178
3231
  async collectSessionFilesInShard(shardKey) {
3179
- const dir = shardKey ? path7.join(this.dir, shardKey) : this.dir;
3232
+ const dir = shardKey ? path8.join(this.dir, shardKey) : this.dir;
3180
3233
  const entries = await this.collectSessionFiles(dir, shardKey);
3181
3234
  return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
3182
3235
  }
@@ -3199,13 +3252,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
3199
3252
  if (entry.name === "_index.jsonl") continue;
3200
3253
  const base = entry.name.replace(/\.jsonl$/, "");
3201
3254
  const id = prefix ? `${prefix}/${base}` : base;
3202
- files.push({ id, filePath: path7.join(dir, entry.name) });
3255
+ files.push({ id, filePath: path8.join(dir, entry.name) });
3203
3256
  }
3204
3257
  }
3205
3258
  const childFileArrays = await Promise.all(
3206
3259
  dirEntries.map((entry) => {
3207
3260
  const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
3208
- return this.collectSessionFiles(path7.join(dir, entry.name), childPrefix, depth + 1);
3261
+ return this.collectSessionFiles(path8.join(dir, entry.name), childPrefix, depth + 1);
3209
3262
  })
3210
3263
  );
3211
3264
  return [...childFileArrays.flat(), ...files];
@@ -3238,7 +3291,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
3238
3291
  const childIdArrays = await Promise.all(
3239
3292
  dirEntries.map((entry) => {
3240
3293
  const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
3241
- return this.collectSessionIds(path7.join(dir, entry.name), childPrefix, depth + 1);
3294
+ return this.collectSessionIds(path8.join(dir, entry.name), childPrefix, depth + 1);
3242
3295
  })
3243
3296
  );
3244
3297
  return [...childIdArrays.flat(), ...fileIds];
@@ -3353,9 +3406,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
3353
3406
  async deleteSession(id) {
3354
3407
  const jsonlPath = this.sessionPath(id, ".jsonl");
3355
3408
  const summaryPath = this.sessionPath(id, ".summary.json");
3356
- const shardDir = path7.dirname(jsonlPath);
3357
- const base = path7.basename(id);
3358
- const sessDir = path7.join(shardDir, base);
3409
+ const shardDir = path8.dirname(jsonlPath);
3410
+ const base = path8.basename(id);
3411
+ const sessDir = path8.join(shardDir, base);
3359
3412
  const deletions = [
3360
3413
  fsp4.unlink(jsonlPath),
3361
3414
  fsp4.unlink(summaryPath),
@@ -3400,7 +3453,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
3400
3453
  */
3401
3454
  async readActiveSessionId() {
3402
3455
  try {
3403
- const raw = await fsp4.readFile(path7.join(this.dir, "active.json"), "utf8");
3456
+ const raw = await fsp4.readFile(path8.join(this.dir, "active.json"), "utf8");
3404
3457
  const active = JSON.parse(raw);
3405
3458
  return active.sessionId ?? null;
3406
3459
  } catch {
@@ -3463,7 +3516,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
3463
3516
  const activeSessionId = await this.readActiveSessionId();
3464
3517
  const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
3465
3518
  const pruneFile = async (dir, name, prefix) => {
3466
- const jsonlPath = path7.join(dir, name);
3519
+ const jsonlPath = path8.join(dir, name);
3467
3520
  try {
3468
3521
  const stat13 = await fsp4.stat(jsonlPath);
3469
3522
  if (stat13.mtimeMs >= cutoff) return;
@@ -3483,7 +3536,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
3483
3536
  continue;
3484
3537
  }
3485
3538
  if (!entry.isDirectory()) continue;
3486
- const dateDir = path7.join(this.dir, entry.name);
3539
+ const dateDir = path8.join(this.dir, entry.name);
3487
3540
  const files = await fsp4.readdir(dateDir, { withFileTypes: true }).catch(() => []);
3488
3541
  for (const file of files) {
3489
3542
  if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
@@ -3495,7 +3548,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
3495
3548
  }
3496
3549
  for (const entry of entries) {
3497
3550
  if (!entry.isDirectory()) continue;
3498
- const dateDir = path7.join(this.dir, entry.name);
3551
+ const dateDir = path8.join(this.dir, entry.name);
3499
3552
  try {
3500
3553
  const remaining = await fsp4.readdir(dateDir);
3501
3554
  if (remaining.length === 0) {
@@ -3620,7 +3673,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
3620
3673
 
3621
3674
  // src/storage/queue-store.ts
3622
3675
  import * as fsp5 from "node:fs/promises";
3623
- import * as path8 from "node:path";
3676
+ import * as path9 from "node:path";
3624
3677
  var QueueStore = class {
3625
3678
  file;
3626
3679
  // Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
@@ -3629,7 +3682,7 @@ var QueueStore = class {
3629
3682
  traceId;
3630
3683
  logger;
3631
3684
  constructor(opts) {
3632
- this.file = path8.join(opts.dir, "queue.json");
3685
+ this.file = path9.join(opts.dir, "queue.json");
3633
3686
  this.events = opts.events;
3634
3687
  this.traceId = opts.traceId;
3635
3688
  this.logger = opts.logger;
@@ -3787,7 +3840,7 @@ function isPersistedQueueItem(v) {
3787
3840
  // src/storage/attachment-store.ts
3788
3841
  import { randomBytes as randomBytes3 } from "node:crypto";
3789
3842
  import * as fsp6 from "node:fs/promises";
3790
- import * as path9 from "node:path";
3843
+ import * as path10 from "node:path";
3791
3844
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
3792
3845
  var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
3793
3846
  var DefaultAttachmentStore = class {
@@ -3808,7 +3861,7 @@ var DefaultAttachmentStore = class {
3808
3861
  let data = input.data;
3809
3862
  if (this.spoolDir && bytes >= this.spoolThreshold) {
3810
3863
  await fsp6.mkdir(this.spoolDir, { recursive: true });
3811
- spooledPath = path9.join(this.spoolDir, `${id}.bin`);
3864
+ spooledPath = path10.join(this.spoolDir, `${id}.bin`);
3812
3865
  await atomicWrite(spooledPath, input.data, {
3813
3866
  encoding: input.kind === "image" ? "base64" : "utf8"
3814
3867
  });
@@ -3928,8 +3981,8 @@ function mergeAdjacentText(blocks) {
3928
3981
 
3929
3982
  // src/storage/memory-backend.ts
3930
3983
  import { randomUUID as randomUUID2 } from "node:crypto";
3931
- import * as fs3 from "node:fs/promises";
3932
- import * as path10 from "node:path";
3984
+ import * as fs4 from "node:fs/promises";
3985
+ import * as path11 from "node:path";
3933
3986
 
3934
3987
  // src/types/memory.ts
3935
3988
  var MEMORY_TYPE_LABELS = {
@@ -4096,7 +4149,7 @@ var FileMemoryBackend = class {
4096
4149
  }
4097
4150
  async getMtime(file) {
4098
4151
  try {
4099
- const stat13 = await fs3.stat(file);
4152
+ const stat13 = await fs4.stat(file);
4100
4153
  return stat13.mtimeMs;
4101
4154
  } catch {
4102
4155
  return 0;
@@ -4143,10 +4196,10 @@ var FileMemoryBackend = class {
4143
4196
  }
4144
4197
  async remember(scope, entry, filePath) {
4145
4198
  const file = this.resolveFile(filePath, scope);
4146
- await ensureDir(path10.dirname(file));
4199
+ await ensureDir(path11.dirname(file));
4147
4200
  let existing = "";
4148
4201
  try {
4149
- existing = await fs3.readFile(file, "utf8");
4202
+ existing = await fs4.readFile(file, "utf8");
4150
4203
  } catch {
4151
4204
  }
4152
4205
  const id = `mem_${Date.now()}_${randomUUID2().slice(0, 8)}`;
@@ -4164,7 +4217,7 @@ ${line}`;
4164
4217
  return withFileLock(file, async () => {
4165
4218
  let existing;
4166
4219
  try {
4167
- existing = await fs3.readFile(file, "utf8");
4220
+ existing = await fs4.readFile(file, "utf8");
4168
4221
  } catch {
4169
4222
  return 0;
4170
4223
  }
@@ -4201,7 +4254,7 @@ ${line}`;
4201
4254
  async readAll(scope, filePath) {
4202
4255
  const file = this.resolveFile(filePath, scope);
4203
4256
  try {
4204
- return await fs3.readFile(file, "utf8");
4257
+ return await fs4.readFile(file, "utf8");
4205
4258
  } catch {
4206
4259
  return "";
4207
4260
  }
@@ -4225,7 +4278,7 @@ ${line}`;
4225
4278
  const file = this.resolveFile(filePath, scope);
4226
4279
  let existing;
4227
4280
  try {
4228
- existing = await fs3.readFile(file, "utf8");
4281
+ existing = await fs4.readFile(file, "utf8");
4229
4282
  } catch {
4230
4283
  return 0;
4231
4284
  }
@@ -4245,7 +4298,7 @@ ${line}`;
4245
4298
  const next = lines.join("\n");
4246
4299
  const backup = `${file}.bak.${Date.now()}`;
4247
4300
  try {
4248
- await fs3.copyFile(file, backup);
4301
+ await fs4.copyFile(file, backup);
4249
4302
  await pruneConsolidateBackups(file);
4250
4303
  } catch {
4251
4304
  }
@@ -4259,14 +4312,14 @@ ${line}`;
4259
4312
  }
4260
4313
  };
4261
4314
  async function pruneConsolidateBackups(file) {
4262
- const dir = path10.dirname(file);
4263
- const base = path10.basename(file);
4315
+ const dir = path11.dirname(file);
4316
+ const base = path11.basename(file);
4264
4317
  const prefix = `${base}.bak.`;
4265
- const backups = (await fs3.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
4318
+ const backups = (await fs4.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
4266
4319
  await Promise.all(
4267
4320
  backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {
4268
4321
  try {
4269
- await fs3.unlink(path10.join(dir, name));
4322
+ await fs4.unlink(path11.join(dir, name));
4270
4323
  } catch {
4271
4324
  }
4272
4325
  })
@@ -4282,7 +4335,7 @@ function parseEntries(raw, scope = "project-memory") {
4282
4335
  }
4283
4336
 
4284
4337
  // src/storage/memory-graph-backend.ts
4285
- import * as fs4 from "node:fs/promises";
4338
+ import * as fs5 from "node:fs/promises";
4286
4339
  var GraphMemoryBackend = class _GraphMemoryBackend {
4287
4340
  kind = "graph";
4288
4341
  file;
@@ -4450,7 +4503,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
4450
4503
  this.loadedScope = scope;
4451
4504
  this.loaded = true;
4452
4505
  try {
4453
- await fs4.unlink(this.graphFile);
4506
+ await fs5.unlink(this.graphFile);
4454
4507
  } catch {
4455
4508
  }
4456
4509
  }
@@ -4490,7 +4543,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
4490
4543
  async loadGraph(scope) {
4491
4544
  if (this.loaded && this.loadedScope === scope) return;
4492
4545
  try {
4493
- const raw = await fs4.readFile(this.graphFile, "utf8");
4546
+ const raw = await fs5.readFile(this.graphFile, "utf8");
4494
4547
  const data = JSON.parse(raw);
4495
4548
  this.nodes = new Map(data.nodes);
4496
4549
  this.edges = data.edges;
@@ -4512,10 +4565,10 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
4512
4565
  edges: this.edges
4513
4566
  };
4514
4567
  const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
4515
- await fs4.mkdir(dir, { recursive: true });
4568
+ await fs5.mkdir(dir, { recursive: true });
4516
4569
  const tmp = `${this.graphFile}.tmp`;
4517
- await fs4.writeFile(tmp, JSON.stringify(data));
4518
- await fs4.rename(tmp, this.graphFile);
4570
+ await fs5.writeFile(tmp, JSON.stringify(data));
4571
+ await fs5.rename(tmp, this.graphFile);
4519
4572
  } catch {
4520
4573
  }
4521
4574
  }
@@ -4634,7 +4687,7 @@ function simpleHash(s) {
4634
4687
 
4635
4688
  // src/utils/instruction-file.ts
4636
4689
  import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
4637
- import * as path11 from "node:path";
4690
+ import * as path12 from "node:path";
4638
4691
  import { fileURLToPath } from "node:url";
4639
4692
  var textCache = /* @__PURE__ */ new Map();
4640
4693
  var rootCandidates;
@@ -4644,7 +4697,7 @@ function readBundledInstructionText(relativePath) {
4644
4697
  let resolved = "";
4645
4698
  for (const root of instructionRootCandidates()) {
4646
4699
  try {
4647
- resolved = readFileSync2(path11.join(root, relativePath), "utf8").trimEnd();
4700
+ resolved = readFileSync2(path12.join(root, relativePath), "utf8").trimEnd();
4648
4701
  break;
4649
4702
  } catch {
4650
4703
  }
@@ -4660,11 +4713,11 @@ function renderInstructionTemplate(template, values) {
4660
4713
  }
4661
4714
  function instructionRootCandidates() {
4662
4715
  if (rootCandidates !== void 0) return rootCandidates;
4663
- const here = path11.dirname(fileURLToPath(import.meta.url));
4716
+ const here = path12.dirname(fileURLToPath(import.meta.url));
4664
4717
  const candidates = [
4665
- path11.resolve(here, "../../instructions"),
4666
- path11.resolve(here, "../instructions"),
4667
- path11.resolve(here, "instructions")
4718
+ path12.resolve(here, "../../instructions"),
4719
+ path12.resolve(here, "../instructions"),
4720
+ path12.resolve(here, "instructions")
4668
4721
  ];
4669
4722
  rootCandidates = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
4670
4723
  return rootCandidates;
@@ -4912,8 +4965,8 @@ function deepFreeze(obj) {
4912
4965
 
4913
4966
  // src/storage/provider-config-watcher.ts
4914
4967
  import * as syncFs from "node:fs";
4915
- import * as fs5 from "node:fs/promises";
4916
- import * as path12 from "node:path";
4968
+ import * as fs6 from "node:fs/promises";
4969
+ import * as path13 from "node:path";
4917
4970
 
4918
4971
  // src/security/config-secrets.ts
4919
4972
  function decryptConfigSecrets(cfg, vault, opts) {
@@ -4959,7 +5012,7 @@ function isSecretField(name) {
4959
5012
  async function readProviderSnapshot(configPath, vault, warn) {
4960
5013
  let raw;
4961
5014
  try {
4962
- raw = await fs5.readFile(configPath, "utf8");
5015
+ raw = await fs6.readFile(configPath, "utf8");
4963
5016
  } catch (err) {
4964
5017
  if (err.code !== "ENOENT") {
4965
5018
  warn?.(`Could not read ${configPath}: ${err.message}`);
@@ -4975,7 +5028,8 @@ async function readProviderSnapshot(configPath, vault, warn) {
4975
5028
  }
4976
5029
  const decrypted = decryptConfigSecrets(parsed, vault, warn ? { warn } : {});
4977
5030
  const snapshot = {
4978
- providers: decrypted.providers ?? {}
5031
+ providers: decrypted.providers ?? {},
5032
+ snapshotHasProviders: decrypted.providers !== void 0
4979
5033
  };
4980
5034
  if (typeof decrypted.apiKey === "string") snapshot.apiKey = decrypted.apiKey;
4981
5035
  if (typeof decrypted.baseUrl === "string") snapshot.baseUrl = decrypted.baseUrl;
@@ -5010,7 +5064,7 @@ function serializeSnapshot(s) {
5010
5064
  function watchProviderConfig(configPath, vault, onChange, opts = {}) {
5011
5065
  const debounceMs = opts.debounceMs ?? 200;
5012
5066
  const warn = opts.warn;
5013
- const base = path12.basename(configPath);
5067
+ const base = path13.basename(configPath);
5014
5068
  let timer;
5015
5069
  let closed = false;
5016
5070
  let lastSerialized;
@@ -5021,7 +5075,7 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
5021
5075
  });
5022
5076
  let watcher;
5023
5077
  try {
5024
- watcher = syncFs.watch(path12.dirname(configPath), { recursive: false });
5078
+ watcher = syncFs.watch(path13.dirname(configPath), { recursive: false });
5025
5079
  } catch (err) {
5026
5080
  warn?.(`Provider config watcher could not start: ${err.message}`);
5027
5081
  return { close: () => {
@@ -5064,8 +5118,8 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
5064
5118
  }
5065
5119
 
5066
5120
  // src/storage/config-loader.ts
5067
- import * as fs6 from "node:fs/promises";
5068
- import * as path13 from "node:path";
5121
+ import * as fs7 from "node:fs/promises";
5122
+ import * as path14 from "node:path";
5069
5123
 
5070
5124
  // src/types/config.ts
5071
5125
  var DEFAULT_TUI_THINKING_WORD = "thinking";
@@ -5589,8 +5643,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
5589
5643
  return out;
5590
5644
  }
5591
5645
  function samePath(a, b) {
5592
- let ra = path13.resolve(a);
5593
- let rb = path13.resolve(b);
5646
+ let ra = path14.resolve(a);
5647
+ let rb = path14.resolve(b);
5594
5648
  if (process.platform === "win32" || process.platform === "darwin") {
5595
5649
  ra = ra.toLowerCase();
5596
5650
  rb = rb.toLowerCase();
@@ -5612,7 +5666,7 @@ function deepMerge2(base, patch) {
5612
5666
  opts
5613
5667
  );
5614
5668
  }
5615
- var DefaultConfigLoader = class {
5669
+ var DefaultConfigLoader = class _DefaultConfigLoader {
5616
5670
  paths;
5617
5671
  strict;
5618
5672
  vault;
@@ -5719,6 +5773,11 @@ var DefaultConfigLoader = class {
5719
5773
  }
5720
5774
  return Object.freeze(cfg);
5721
5775
  }
5776
+ /** Check whether a config object contains only the two bootstrap keys. */
5777
+ static isBootstrapOnly(config) {
5778
+ const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
5779
+ return Object.keys(config).every((k) => BOOTSTRAP_KEYS.has(k));
5780
+ }
5722
5781
  async ensureGlobalDefaults() {
5723
5782
  const fp = this.paths.globalConfig;
5724
5783
  const t0 = Date.now();
@@ -5727,7 +5786,7 @@ var DefaultConfigLoader = class {
5727
5786
  let parsed;
5728
5787
  let fileExisted = true;
5729
5788
  try {
5730
- const raw = await fs6.readFile(fp, "utf8");
5789
+ const raw = await fs7.readFile(fp, "utf8");
5731
5790
  const result = safeParse(raw);
5732
5791
  if (!result.ok || !isPlainRecord(result.value)) {
5733
5792
  return;
@@ -5756,9 +5815,12 @@ var DefaultConfigLoader = class {
5756
5815
  fileExisted = false;
5757
5816
  parsed = {};
5758
5817
  }
5818
+ const profileName = parsed.activeProfile ?? "default";
5819
+ const profileFp = this.paths.profileConfig(profileName);
5820
+ await this.ensureProfileConfig(profileFp, parsed, fileExisted);
5759
5821
  const bootstrap = {
5760
5822
  version: 1,
5761
- activeProfile: parsed.activeProfile ?? "default"
5823
+ activeProfile: profileName
5762
5824
  };
5763
5825
  let needsBootstrapWrite = false;
5764
5826
  if (parsed.version !== 1) {
@@ -5767,7 +5829,11 @@ var DefaultConfigLoader = class {
5767
5829
  if (parsed.activeProfile === void 0) {
5768
5830
  needsBootstrapWrite = true;
5769
5831
  }
5832
+ if (!_DefaultConfigLoader.isBootstrapOnly(parsed)) {
5833
+ needsBootstrapWrite = true;
5834
+ }
5770
5835
  if (needsBootstrapWrite) {
5836
+ await backupConfigFile(fp, this.paths);
5771
5837
  await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
5772
5838
  this.events?.emit("storage.write", {
5773
5839
  sessionId: "~config~",
@@ -5779,9 +5845,6 @@ var DefaultConfigLoader = class {
5779
5845
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5780
5846
  });
5781
5847
  }
5782
- const profileName = bootstrap.activeProfile ?? "default";
5783
- const profileFp = this.paths.profileConfig(profileName);
5784
- await this.ensureProfileConfig(profileFp, parsed, fileExisted);
5785
5848
  });
5786
5849
  } catch (err) {
5787
5850
  this.events?.emit("storage.error", {
@@ -5807,77 +5870,92 @@ var DefaultConfigLoader = class {
5807
5870
  * On first boot: migrate content from the old flat global config, or seed
5808
5871
  * with behavior defaults. Subsequent boots: fill any missing keys from
5809
5872
  * BEHAVIOR_DEFAULTS (keeping user settings intact).
5873
+ *
5874
+ * CRITICAL SAFETY NET: when the profile already exists but the old global
5875
+ * config has extra non-bootstrap keys (because WebUI/CLI persistence functions
5876
+ * mistakenly wrote settings to the root config), those keys are merged into
5877
+ * the profile so they are NOT silently destroyed by the subsequent bootstrap
5878
+ * trim. This prevents the "config keeps getting emptied" bug.
5810
5879
  */
5811
5880
  async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
5812
5881
  const t0 = Date.now();
5882
+ let parsed;
5883
+ let existed = true;
5813
5884
  try {
5814
- await withFileLock(profileFp, async () => {
5815
- let parsed;
5816
- let existed = true;
5817
- try {
5818
- const raw = await fs6.readFile(profileFp, "utf8");
5819
- const result = safeParse(raw);
5820
- if (!result.ok || !isPlainRecord(result.value)) {
5821
- this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
5822
- event: "config.profile_parse_failed",
5823
- path: profileFp
5824
- });
5825
- parsed = {};
5826
- } else {
5827
- parsed = result.value;
5885
+ const raw = await fs7.readFile(profileFp, "utf8");
5886
+ const result = safeParse(raw);
5887
+ if (!result.ok || !isPlainRecord(result.value)) {
5888
+ this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
5889
+ event: "config.profile_parse_failed",
5890
+ path: profileFp
5891
+ });
5892
+ parsed = {};
5893
+ } else {
5894
+ parsed = result.value;
5895
+ }
5896
+ } catch (err) {
5897
+ if (err.code !== "ENOENT") {
5898
+ this.logWarn("Profile config read failed", {
5899
+ event: "config.profile_read_failed",
5900
+ path: profileFp,
5901
+ message: toErrorMessage(err)
5902
+ });
5903
+ return;
5904
+ }
5905
+ existed = false;
5906
+ parsed = {};
5907
+ }
5908
+ const profileHasContent = existed && Object.keys(parsed).some((k) => k !== "version" && k !== "activeProfile");
5909
+ let seed;
5910
+ if (!profileHasContent && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
5911
+ seed = { ...oldGlobalParsed };
5912
+ delete seed["activeProfile"];
5913
+ const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
5914
+ seed = filled.value;
5915
+ } else {
5916
+ if (existed && !_DefaultConfigLoader.isBootstrapOnly(oldGlobalParsed)) {
5917
+ const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
5918
+ let merged = false;
5919
+ for (const [k, v] of Object.entries(oldGlobalParsed)) {
5920
+ if (!BOOTSTRAP_KEYS.has(k) && !(k in parsed)) {
5921
+ parsed[k] = v;
5922
+ merged = true;
5828
5923
  }
5829
- } catch (err) {
5830
- if (err.code !== "ENOENT") {
5831
- this.logWarn("Profile config read failed", {
5832
- event: "config.profile_read_failed",
5833
- path: profileFp,
5834
- message: toErrorMessage(err)
5924
+ }
5925
+ if (merged) {
5926
+ const filled2 = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
5927
+ if (filled2.changed) {
5928
+ seed = filled2.value;
5929
+ await backupConfigFile(profileFp, this.paths);
5930
+ await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
5931
+ this.events?.emit("storage.write", {
5932
+ sessionId: "~config~",
5933
+ store: "config",
5934
+ filePath: profileFp,
5935
+ operation: "ensure_profile_defaults",
5936
+ outcome: "success",
5937
+ durationMs: Date.now() - t0,
5938
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5835
5939
  });
5836
5940
  return;
5837
5941
  }
5838
- existed = false;
5839
- parsed = {};
5840
5942
  }
5841
- let seed;
5842
- if (!existed && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
5843
- seed = { ...oldGlobalParsed };
5844
- delete seed["activeProfile"];
5845
- const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
5846
- seed = filled.value;
5847
- } else {
5848
- const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
5849
- if (!filled.changed) return;
5850
- seed = filled.value;
5851
- }
5852
- await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
5853
- this.events?.emit("storage.write", {
5854
- sessionId: "~config~",
5855
- store: "config",
5856
- filePath: profileFp,
5857
- operation: "ensure_profile_defaults",
5858
- outcome: "success",
5859
- durationMs: Date.now() - t0,
5860
- ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5861
- });
5862
- });
5863
- } catch (err) {
5864
- this.events?.emit("storage.error", {
5865
- sessionId: "~config~",
5866
- store: "config",
5867
- filePath: profileFp,
5868
- operation: "ensure_profile_defaults",
5869
- outcome: "failure",
5870
- error: storageErrorString(err),
5871
- recoverable: false,
5872
- durationMs: Date.now() - t0,
5873
- ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5874
- });
5875
- this.logWarn("Profile config defaults write failed", {
5876
- event: "config.profile_write_failed",
5877
- path: profileFp,
5878
- message: toErrorMessage(err)
5879
- });
5943
+ }
5944
+ const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
5945
+ if (!filled.changed) return;
5946
+ seed = filled.value;
5880
5947
  }
5948
+ await backupConfigFile(profileFp, this.paths);
5949
+ await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
5950
+ this.events?.emit("storage.write", {
5951
+ sessionId: "~config~",
5952
+ store: "config",
5953
+ filePath: profileFp,
5954
+ operation: "ensure_profile_defaults",
5955
+ outcome: "success",
5956
+ durationMs: Date.now() - t0,
5957
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5958
+ });
5881
5959
  }
5882
5960
  /**
5883
5961
  * Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
@@ -5927,7 +6005,7 @@ var DefaultConfigLoader = class {
5927
6005
  const fp = this.paths.syncConfig;
5928
6006
  const t0 = Date.now();
5929
6007
  try {
5930
- const raw = await fs6.readFile(fp, "utf8");
6008
+ const raw = await fs7.readFile(fp, "utf8");
5931
6009
  const parsed = safeParse(raw);
5932
6010
  if (!parsed.ok || !parsed.value) {
5933
6011
  this.events?.emit("storage.read", {
@@ -5989,7 +6067,7 @@ var DefaultConfigLoader = class {
5989
6067
  const t0 = Date.now();
5990
6068
  let mtimeMs = null;
5991
6069
  try {
5992
- const stat13 = await fs6.stat(file);
6070
+ const stat13 = await fs7.stat(file);
5993
6071
  mtimeMs = stat13.mtimeMs;
5994
6072
  const cached = this.jsonCache.get(file);
5995
6073
  if (cached && cached.mtimeMs === mtimeMs) {
@@ -6019,7 +6097,7 @@ var DefaultConfigLoader = class {
6019
6097
  }
6020
6098
  let raw;
6021
6099
  try {
6022
- raw = await fs6.readFile(file, "utf8");
6100
+ raw = await fs7.readFile(file, "utf8");
6023
6101
  } catch (err) {
6024
6102
  if (err.code !== "ENOENT") {
6025
6103
  this.events?.emit("storage.read", {
@@ -6182,7 +6260,7 @@ var DEFAULT_CONFIG_MIGRATIONS = [];
6182
6260
  // src/storage/recovery-lock.ts
6183
6261
  import * as fsp7 from "node:fs/promises";
6184
6262
  import * as os2 from "node:os";
6185
- import * as path14 from "node:path";
6263
+ import * as path15 from "node:path";
6186
6264
  var LOCK_FILE = "active.json";
6187
6265
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
6188
6266
  var RecoveryLock = class {
@@ -6193,7 +6271,7 @@ var RecoveryLock = class {
6193
6271
  sessionStore;
6194
6272
  probe;
6195
6273
  constructor(opts) {
6196
- this.file = path14.join(opts.dir, LOCK_FILE);
6274
+ this.file = path15.join(opts.dir, LOCK_FILE);
6197
6275
  this.pid = opts.pid ?? process.pid;
6198
6276
  this.hostname = opts.hostname ?? os2.hostname();
6199
6277
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -6254,7 +6332,7 @@ var RecoveryLock = class {
6254
6332
  * null return before calling this.
6255
6333
  */
6256
6334
  async write(sessionId) {
6257
- await ensureDir(path14.dirname(this.file));
6335
+ await ensureDir(path15.dirname(this.file));
6258
6336
  const lock = {
6259
6337
  v: 1,
6260
6338
  sessionId,
@@ -6322,7 +6400,7 @@ function defaultIsPidAlive(pid) {
6322
6400
  }
6323
6401
 
6324
6402
  // src/storage/session-reader.ts
6325
- import * as fs7 from "node:fs/promises";
6403
+ import * as fs8 from "node:fs/promises";
6326
6404
  var DefaultSessionReader = class _DefaultSessionReader {
6327
6405
  store;
6328
6406
  eventCache = /* @__PURE__ */ new Map();
@@ -6340,7 +6418,7 @@ var DefaultSessionReader = class _DefaultSessionReader {
6340
6418
  const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
6341
6419
  let mtimeMs = null;
6342
6420
  try {
6343
- const stat13 = await fs7.stat(sessionPath);
6421
+ const stat13 = await fs8.stat(sessionPath);
6344
6422
  mtimeMs = stat13.mtimeMs;
6345
6423
  } catch {
6346
6424
  this.eventCache.delete(sessionId);
@@ -6704,7 +6782,7 @@ function renderPlainText(meta, events) {
6704
6782
 
6705
6783
  // src/storage/annotations-store.ts
6706
6784
  import { randomUUID as randomUUID3 } from "node:crypto";
6707
- import * as fs8 from "node:fs/promises";
6785
+ import * as fs9 from "node:fs/promises";
6708
6786
  var FILE_VERSION = 1;
6709
6787
  var MAX_TEXT_LENGTH = 2e3;
6710
6788
  var MAX_ANNOTATIONS = 1e3;
@@ -6931,7 +7009,7 @@ var AnnotationsStore = class {
6931
7009
  const fp = this.filePath(sessionId);
6932
7010
  let raw;
6933
7011
  try {
6934
- raw = await fs8.readFile(fp, "utf8");
7012
+ raw = await fs9.readFile(fp, "utf8");
6935
7013
  } catch (err) {
6936
7014
  if (err.code === "ENOENT") return null;
6937
7015
  throw err;
@@ -6968,8 +7046,8 @@ var AnnotationsStore = class {
6968
7046
  };
6969
7047
 
6970
7048
  // src/storage/replay-log-store.ts
6971
- import * as fs9 from "node:fs/promises";
6972
- import * as path15 from "node:path";
7049
+ import * as fs10 from "node:fs/promises";
7050
+ import * as path16 from "node:path";
6973
7051
 
6974
7052
  // src/replay/hash.ts
6975
7053
  import { createHash as createHash5 } from "node:crypto";
@@ -7058,12 +7136,12 @@ var ReplayLogStore = class {
7058
7136
  const line = JSON.stringify(entry) + "\n";
7059
7137
  let offset2 = 0;
7060
7138
  try {
7061
- const stat13 = await fs9.stat(fp);
7139
+ const stat13 = await fs10.stat(fp);
7062
7140
  offset2 = stat13.size;
7063
7141
  } catch (err) {
7064
7142
  if (err.code !== "ENOENT") throw err;
7065
7143
  }
7066
- await fs9.appendFile(fp, line, "utf8");
7144
+ await fs10.appendFile(fp, line, "utf8");
7067
7145
  cache.set(hash, { entry, offset: offset2, length: Buffer.byteLength(line, "utf8") });
7068
7146
  this.diskCount.set(input.sessionId, currentCount + 1);
7069
7147
  this.events?.emit("storage.write", {
@@ -7191,7 +7269,7 @@ var ReplayLogStore = class {
7191
7269
  const scan = async (dir, prefix, depth) => {
7192
7270
  let entries;
7193
7271
  try {
7194
- entries = await fs9.readdir(dir, { withFileTypes: true });
7272
+ entries = await fs10.readdir(dir, { withFileTypes: true });
7195
7273
  } catch (err) {
7196
7274
  if (depth === 0 && err.code !== "ENOENT") {
7197
7275
  console.warn(JSON.stringify({
@@ -7207,13 +7285,13 @@ var ReplayLogStore = class {
7207
7285
  for (const entry of entries) {
7208
7286
  if (entry.name.startsWith(".")) continue;
7209
7287
  if (entry.isDirectory()) {
7210
- if (depth === 0) await scan(path15.join(dir, entry.name), entry.name, depth + 1);
7288
+ if (depth === 0) await scan(path16.join(dir, entry.name), entry.name, depth + 1);
7211
7289
  continue;
7212
7290
  }
7213
7291
  if (!entry.isFile() || !entry.name.endsWith(".replay.jsonl")) continue;
7214
7292
  const base = entry.name.slice(0, -".replay.jsonl".length);
7215
7293
  const sessionId = prefix ? `${prefix}/${base}` : base;
7216
- const fp = path15.join(dir, entry.name);
7294
+ const fp = path16.join(dir, entry.name);
7217
7295
  out.push({
7218
7296
  sessionId,
7219
7297
  entryCount: await this.countEntries(fp),
@@ -7229,7 +7307,7 @@ var ReplayLogStore = class {
7229
7307
  return sessionScopedPath(this.dir, sessionId, ".replay.jsonl");
7230
7308
  }
7231
7309
  async countEntries(filePath) {
7232
- const handle = await fs9.open(filePath, "r");
7310
+ const handle = await fs10.open(filePath, "r");
7233
7311
  const buffer = Buffer.allocUnsafe(64 * 1024);
7234
7312
  let count = 0;
7235
7313
  let hasNonWhitespace2 = false;
@@ -7270,7 +7348,7 @@ var ReplayLogStore = class {
7270
7348
  async readAll(sessionId) {
7271
7349
  const fp = this.filePath(sessionId);
7272
7350
  try {
7273
- const raw = await fs9.readFile(fp, "utf8");
7351
+ const raw = await fs10.readFile(fp, "utf8");
7274
7352
  const out = [];
7275
7353
  for (const line of raw.split("\n")) {
7276
7354
  if (!line.trim()) continue;
@@ -7306,7 +7384,7 @@ var ReplayLogStore = class {
7306
7384
  const fp = this.filePath(sessionId);
7307
7385
  cache = /* @__PURE__ */ new Map();
7308
7386
  try {
7309
- const handle = await fs9.open(fp, "r");
7387
+ const handle = await fs10.open(fp, "r");
7310
7388
  const CHUNK = 64 * 1024;
7311
7389
  const buffer = Buffer.alloc(CHUNK);
7312
7390
  let leftover = "";
@@ -7357,7 +7435,7 @@ var ReplayLogStore = class {
7357
7435
  async hydrateEntry(sessionId, hash, location) {
7358
7436
  if (location.entry) return location.entry;
7359
7437
  const fp = this.filePath(sessionId);
7360
- const handle = await fs9.open(fp, "r");
7438
+ const handle = await fs10.open(fp, "r");
7361
7439
  try {
7362
7440
  const buffer = Buffer.alloc(location.length);
7363
7441
  const { bytesRead } = await handle.read(buffer, 0, location.length, location.offset);
@@ -7384,8 +7462,8 @@ var ReplayLogStore = class {
7384
7462
  };
7385
7463
 
7386
7464
  // src/storage/session-recovery.ts
7387
- import * as fs10 from "node:fs/promises";
7388
- import * as path16 from "node:path";
7465
+ import * as fs11 from "node:fs/promises";
7466
+ import * as path17 from "node:path";
7389
7467
  var SessionRecovery = class {
7390
7468
  constructor(dir) {
7391
7469
  this.dir = dir;
@@ -7411,7 +7489,7 @@ var SessionRecovery = class {
7411
7489
  const fp = this.filePath(sessionId);
7412
7490
  let stat13;
7413
7491
  try {
7414
- stat13 = await fs10.stat(fp);
7492
+ stat13 = await fs11.stat(fp);
7415
7493
  } catch (err) {
7416
7494
  if (err.code === "ENOENT") return null;
7417
7495
  return null;
@@ -7445,7 +7523,7 @@ var SessionRecovery = class {
7445
7523
  const fp = this.filePath(sessionId);
7446
7524
  let raw;
7447
7525
  try {
7448
- raw = await fs10.readFile(fp, "utf8");
7526
+ raw = await fs11.readFile(fp, "utf8");
7449
7527
  } catch (err) {
7450
7528
  if (err.code === "ENOENT") return null;
7451
7529
  return null;
@@ -7490,7 +7568,7 @@ var SessionRecovery = class {
7490
7568
  const collect = async (dir, prefix, depth) => {
7491
7569
  let entries;
7492
7570
  try {
7493
- entries = await fs10.readdir(dir, { withFileTypes: true });
7571
+ entries = await fs11.readdir(dir, { withFileTypes: true });
7494
7572
  } catch {
7495
7573
  return;
7496
7574
  }
@@ -7500,7 +7578,7 @@ var SessionRecovery = class {
7500
7578
  continue;
7501
7579
  if (entry.isDirectory()) {
7502
7580
  if (depth === 0) {
7503
- await collect(path16.join(dir, entry.name), entry.name, depth + 1);
7581
+ await collect(path17.join(dir, entry.name), entry.name, depth + 1);
7504
7582
  }
7505
7583
  continue;
7506
7584
  }
@@ -7541,7 +7619,7 @@ function hasNonWhitespace(line) {
7541
7619
  }
7542
7620
  async function scanLatestLifecycleBoundary(filePath, size) {
7543
7621
  const CHUNK_SIZE = 64 * 1024;
7544
- const handle = await fs10.open(filePath, "r");
7622
+ const handle = await fs11.open(filePath, "r");
7545
7623
  let position = size;
7546
7624
  let laterLineFragment = Buffer.alloc(0);
7547
7625
  let latestBoundary = null;
@@ -7580,7 +7658,7 @@ async function scanLatestLifecycleBoundary(filePath, size) {
7580
7658
 
7581
7659
  // src/storage/tool-audit-log.ts
7582
7660
  import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
7583
- import * as fs11 from "node:fs/promises";
7661
+ import * as fs12 from "node:fs/promises";
7584
7662
  var GENESIS_PREV = "0".repeat(64);
7585
7663
  var DEFAULT_FSYNC_EVERY = 100;
7586
7664
  var ToolAuditLog = class {
@@ -7650,9 +7728,9 @@ var ToolAuditLog = class {
7650
7728
  isError: input.isError,
7651
7729
  index
7652
7730
  };
7653
- await fs11.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
7731
+ await fs12.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
7654
7732
  try {
7655
- const st = await fs11.stat(fp);
7733
+ const st = await fs12.stat(fp);
7656
7734
  this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
7657
7735
  } catch {
7658
7736
  }
@@ -7699,7 +7777,7 @@ var ToolAuditLog = class {
7699
7777
  const cachedStat = this.tailStat.get(sessionId);
7700
7778
  if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
7701
7779
  try {
7702
- const st = await fs11.stat(fp);
7780
+ const st = await fs12.stat(fp);
7703
7781
  if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
7704
7782
  return { prevHash: cachedHash, nextIndex: cachedIndex };
7705
7783
  }
@@ -7720,7 +7798,7 @@ var ToolAuditLog = class {
7720
7798
  this.tailHash.set(sessionId, prevHash);
7721
7799
  this.tailIndex.set(sessionId, nextIndex);
7722
7800
  try {
7723
- const st = await fs11.stat(fp);
7801
+ const st = await fs12.stat(fp);
7724
7802
  this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
7725
7803
  } catch {
7726
7804
  }
@@ -7841,7 +7919,7 @@ var ToolAuditLog = class {
7841
7919
  async readAll(sessionId) {
7842
7920
  const fp = this.filePath(sessionId);
7843
7921
  try {
7844
- const raw = await fs11.readFile(fp, "utf8");
7922
+ const raw = await fs12.readFile(fp, "utf8");
7845
7923
  const out = [];
7846
7924
  for (const line of raw.split("\n")) {
7847
7925
  if (!line.trim()) continue;
@@ -7879,7 +7957,7 @@ var ToolAuditLog = class {
7879
7957
  }
7880
7958
  async sync(sessionId, fp) {
7881
7959
  try {
7882
- const fh = await fs11.open(fp, "r+");
7960
+ const fh = await fs12.open(fp, "r+");
7883
7961
  try {
7884
7962
  await fh.sync();
7885
7963
  } finally {
@@ -8018,8 +8096,8 @@ var SessionAnalyzer = class {
8018
8096
 
8019
8097
  // src/session-registry.ts
8020
8098
  import { randomUUID as randomUUID5 } from "node:crypto";
8021
- import * as fs12 from "node:fs/promises";
8022
- import * as path17 from "node:path";
8099
+ import * as fs13 from "node:fs/promises";
8100
+ import * as path18 from "node:path";
8023
8101
  var REGISTRY_FILE = "session-registry.json";
8024
8102
  var HEARTBEAT_INTERVAL_MS = 5e3;
8025
8103
  var STALE_TIMEOUT_MS = 3e4;
@@ -8058,7 +8136,7 @@ var SessionRegistry = class {
8058
8136
  */
8059
8137
  lastEntry = null;
8060
8138
  constructor(globalRoot) {
8061
- this.filePath = path17.join(globalRoot, REGISTRY_FILE);
8139
+ this.filePath = path18.join(globalRoot, REGISTRY_FILE);
8062
8140
  }
8063
8141
  // ── Public API ──────────────────────────────────────────────────────────
8064
8142
  /**
@@ -8214,7 +8292,7 @@ var SessionRegistry = class {
8214
8292
  }
8215
8293
  async readAndPrune() {
8216
8294
  try {
8217
- const raw = await fs12.readFile(this.filePath, "utf8");
8295
+ const raw = await fs13.readFile(this.filePath, "utf8");
8218
8296
  const registry = parseRegistry(raw);
8219
8297
  const now = Date.now();
8220
8298
  let pruned = false;
@@ -8267,11 +8345,11 @@ var SessionRegistry = class {
8267
8345
  const retryDelayMs = 20;
8268
8346
  for (let attempt = 0; attempt < maxRetries; attempt++) {
8269
8347
  try {
8270
- await fs12.mkdir(path17.dirname(this.filePath), { recursive: true });
8271
- let lockHandle = await fs12.open(lockPath, "wx").catch(() => null);
8348
+ await fs13.mkdir(path18.dirname(this.filePath), { recursive: true });
8349
+ let lockHandle = await fs13.open(lockPath, "wx").catch(() => null);
8272
8350
  if (!lockHandle) {
8273
8351
  if (await this.breakStaleLock(lockPath)) {
8274
- lockHandle = await fs12.open(lockPath, "wx").catch(() => null);
8352
+ lockHandle = await fs13.open(lockPath, "wx").catch(() => null);
8275
8353
  }
8276
8354
  if (!lockHandle) {
8277
8355
  await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
@@ -8280,14 +8358,14 @@ var SessionRegistry = class {
8280
8358
  }
8281
8359
  try {
8282
8360
  await lockHandle.writeFile(String(process.pid)).catch(() => void 0);
8283
- const raw = await fs12.readFile(this.filePath, "utf8").catch(() => "{}");
8361
+ const raw = await fs13.readFile(this.filePath, "utf8").catch(() => "{}");
8284
8362
  const registry = parseRegistry(raw);
8285
8363
  fn(registry);
8286
8364
  await this.writeAtomicLocked(registry);
8287
8365
  return;
8288
8366
  } finally {
8289
8367
  await lockHandle.close();
8290
- await fs12.unlink(lockPath).catch(() => void 0);
8368
+ await fs13.unlink(lockPath).catch(() => void 0);
8291
8369
  }
8292
8370
  } catch {
8293
8371
  return;
@@ -8304,16 +8382,16 @@ var SessionRegistry = class {
8304
8382
  async breakStaleLock(lockPath) {
8305
8383
  try {
8306
8384
  const [stat13, content] = await Promise.all([
8307
- fs12.stat(lockPath),
8385
+ fs13.stat(lockPath),
8308
8386
  /* v8 ignore start -- best-effort lock-content read; .catch only fires if the lock vanished */
8309
- fs12.readFile(lockPath, "utf8").catch(() => "")
8387
+ fs13.readFile(lockPath, "utf8").catch(() => "")
8310
8388
  /* v8 ignore stop */
8311
8389
  ]);
8312
8390
  const ageMs = Date.now() - stat13.mtimeMs;
8313
8391
  const ownerPid = Number.parseInt(content.trim(), 10);
8314
8392
  const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
8315
8393
  if (ownerDead || ageMs > STALE_LOCK_MS) {
8316
- await fs12.unlink(lockPath).catch(() => void 0);
8394
+ await fs13.unlink(lockPath).catch(() => void 0);
8317
8395
  return true;
8318
8396
  }
8319
8397
  return false;
@@ -8331,41 +8409,41 @@ var SessionRegistry = class {
8331
8409
  await this.writeAtomicFile(registry);
8332
8410
  }
8333
8411
  async writeAtomicFile(registry) {
8334
- const tmp = path17.join(
8335
- path17.dirname(this.filePath),
8336
- `.${path17.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
8412
+ const tmp = path18.join(
8413
+ path18.dirname(this.filePath),
8414
+ `.${path18.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
8337
8415
  );
8338
8416
  try {
8339
- const handle = await fs12.open(tmp, "w");
8417
+ const handle = await fs13.open(tmp, "w");
8340
8418
  try {
8341
8419
  await handle.writeFile(JSON.stringify(registry, null, 2), "utf8");
8342
8420
  await handle.sync().catch(() => void 0);
8343
8421
  } finally {
8344
8422
  await handle.close();
8345
8423
  }
8346
- await fs12.rename(tmp, this.filePath);
8424
+ await fs13.rename(tmp, this.filePath);
8347
8425
  } catch (err) {
8348
- await fs12.unlink(tmp).catch(() => void 0);
8426
+ await fs13.unlink(tmp).catch(() => void 0);
8349
8427
  throw err;
8350
8428
  }
8351
8429
  }
8352
8430
  async pruneStaleTempFiles() {
8353
8431
  try {
8354
- const dir = path17.dirname(this.filePath);
8355
- const base = path17.basename(this.filePath);
8432
+ const dir = path18.dirname(this.filePath);
8433
+ const base = path18.basename(this.filePath);
8356
8434
  const now = Date.now();
8357
8435
  const stale = [];
8358
- for (const name of await fs12.readdir(dir)) {
8436
+ for (const name of await fs13.readdir(dir)) {
8359
8437
  const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
8360
8438
  if (!isTemp) continue;
8361
- const stat13 = await fs12.stat(path17.join(dir, name)).catch(() => null);
8439
+ const stat13 = await fs13.stat(path18.join(dir, name)).catch(() => null);
8362
8440
  if (!stat13) continue;
8363
8441
  if (now - stat13.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat13.mtimeMs });
8364
8442
  }
8365
8443
  stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
8366
8444
  await Promise.all(
8367
8445
  stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
8368
- await fs12.unlink(path17.join(dir, name)).catch(() => void 0);
8446
+ await fs13.unlink(path18.join(dir, name)).catch(() => void 0);
8369
8447
  })
8370
8448
  );
8371
8449
  } catch {
@@ -9100,8 +9178,8 @@ var AgentStatusTracker = class {
9100
9178
  };
9101
9179
 
9102
9180
  // src/fleet-notifier.ts
9103
- import * as fs13 from "node:fs/promises";
9104
- import * as path18 from "node:path";
9181
+ import * as fs14 from "node:fs/promises";
9182
+ import * as path19 from "node:path";
9105
9183
  var INSTANCES_FILE = "webui-instances.json";
9106
9184
  var DISCOVERY_TTL_MS = 2500;
9107
9185
  var COALESCE_MS = 50;
@@ -9116,7 +9194,7 @@ function pidAlive2(pid) {
9116
9194
  }
9117
9195
  }
9118
9196
  function normRoot(root) {
9119
- const resolved = path18.resolve(root);
9197
+ const resolved = path19.resolve(root);
9120
9198
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
9121
9199
  }
9122
9200
  var FleetNotifier = class {
@@ -9163,7 +9241,7 @@ var FleetNotifier = class {
9163
9241
  }
9164
9242
  async discover() {
9165
9243
  try {
9166
- const raw = await fs13.readFile(path18.join(this.baseDir, INSTANCES_FILE), "utf8");
9244
+ const raw = await fs14.readFile(path19.join(this.baseDir, INSTANCES_FILE), "utf8");
9167
9245
  const data = JSON.parse(raw);
9168
9246
  const list = Array.isArray(data?.instances) ? data.instances : [];
9169
9247
  return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
@@ -9184,7 +9262,7 @@ async function defaultPost(url) {
9184
9262
 
9185
9263
  // src/storage/session-rewinder.ts
9186
9264
  import * as fsp8 from "node:fs/promises";
9187
- import * as path19 from "node:path";
9265
+ import * as path20 from "node:path";
9188
9266
  var DefaultSessionRewinder = class {
9189
9267
  constructor(sessionsDir, projectRoot) {
9190
9268
  this.sessionsDir = sessionsDir;
@@ -9322,10 +9400,10 @@ async function revertSnapshots(snapshots, projectRoot) {
9322
9400
  for (const snapshot of [...snapshots].reverse()) {
9323
9401
  for (const file of [...snapshot.files].reverse()) {
9324
9402
  try {
9325
- const absPath = path19.resolve(file.path);
9326
- const root = path19.resolve(projectRoot);
9327
- const rel = path19.relative(root, absPath);
9328
- if (rel.startsWith("..") || path19.isAbsolute(rel)) {
9403
+ const absPath = path20.resolve(file.path);
9404
+ const root = path20.resolve(projectRoot);
9405
+ const rel = path20.relative(root, absPath);
9406
+ if (rel.startsWith("..") || path20.isAbsolute(rel)) {
9329
9407
  errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
9330
9408
  continue;
9331
9409
  }
@@ -10850,8 +10928,8 @@ function normalizeTitle(value) {
10850
10928
 
10851
10929
  // src/storage/prompt-store.ts
10852
10930
  import { createHash as createHash7 } from "node:crypto";
10853
- import * as fs14 from "node:fs/promises";
10854
- import * as path20 from "node:path";
10931
+ import * as fs15 from "node:fs/promises";
10932
+ import * as path21 from "node:path";
10855
10933
  var SCHEMA_VERSION = 2;
10856
10934
  function promptChecksum(content) {
10857
10935
  return createHash7("sha256").update(content, "utf8").digest("hex");
@@ -10915,12 +10993,12 @@ var DefaultPromptStore = class {
10915
10993
  await ensureDir(this.dir);
10916
10994
  const entries = [];
10917
10995
  try {
10918
- const files = await fs14.readdir(this.dir);
10996
+ const files = await fs15.readdir(this.dir);
10919
10997
  for (const file of files) {
10920
10998
  if (!file.endsWith(".json")) continue;
10921
10999
  try {
10922
11000
  const raw = JSON.parse(
10923
- await fs14.readFile(path20.join(this.dir, file), "utf8")
11001
+ await fs15.readFile(path21.join(this.dir, file), "utf8")
10924
11002
  );
10925
11003
  const migrated = migratePromptEntry(raw.entry);
10926
11004
  if (migrated) entries.push(migrated);
@@ -10934,9 +11012,9 @@ var DefaultPromptStore = class {
10934
11012
  );
10935
11013
  }
10936
11014
  async get(id) {
10937
- const file = path20.join(this.dir, `${id}.json`);
11015
+ const file = path21.join(this.dir, `${id}.json`);
10938
11016
  try {
10939
- const raw = JSON.parse(await fs14.readFile(file, "utf8"));
11017
+ const raw = JSON.parse(await fs15.readFile(file, "utf8"));
10940
11018
  return migratePromptEntry(raw.entry);
10941
11019
  } catch {
10942
11020
  return null;
@@ -10944,14 +11022,14 @@ var DefaultPromptStore = class {
10944
11022
  }
10945
11023
  async save(entry) {
10946
11024
  await ensureDir(this.dir);
10947
- const file = path20.join(this.dir, `${entry.id}.json`);
11025
+ const file = path21.join(this.dir, `${entry.id}.json`);
10948
11026
  const raw = { version: SCHEMA_VERSION, entry };
10949
11027
  await atomicWrite(file, JSON.stringify(raw, null, 2));
10950
11028
  }
10951
11029
  async delete(id) {
10952
- const file = path20.join(this.dir, `${id}.json`);
11030
+ const file = path21.join(this.dir, `${id}.json`);
10953
11031
  try {
10954
- await fs14.unlink(file);
11032
+ await fs15.unlink(file);
10955
11033
  return true;
10956
11034
  } catch {
10957
11035
  return false;
@@ -10990,8 +11068,8 @@ var DefaultPromptStore = class {
10990
11068
  };
10991
11069
 
10992
11070
  // src/storage/prompt-usage-store.ts
10993
- import * as fs15 from "node:fs/promises";
10994
- import * as path21 from "node:path";
11071
+ import * as fs16 from "node:fs/promises";
11072
+ import * as path22 from "node:path";
10995
11073
  var PromptUsageStore = class {
10996
11074
  constructor(file) {
10997
11075
  this.file = file;
@@ -10999,7 +11077,7 @@ var PromptUsageStore = class {
10999
11077
  file;
11000
11078
  async load() {
11001
11079
  try {
11002
- const raw = JSON.parse(await fs15.readFile(this.file, "utf8"));
11080
+ const raw = JSON.parse(await fs16.readFile(this.file, "utf8"));
11003
11081
  if (raw && typeof raw === "object" && raw.usage && typeof raw.usage === "object") {
11004
11082
  return raw.usage;
11005
11083
  }
@@ -11012,7 +11090,7 @@ var PromptUsageStore = class {
11012
11090
  const prev = usage[slug];
11013
11091
  const next = { count: (prev?.count ?? 0) + 1, lastUsedAt: at };
11014
11092
  usage[slug] = next;
11015
- await ensureDir(path21.dirname(this.file));
11093
+ await ensureDir(path22.dirname(this.file));
11016
11094
  await atomicWrite(this.file, JSON.stringify({ version: 1, usage }, null, 2));
11017
11095
  return next;
11018
11096
  }
@@ -11036,8 +11114,8 @@ var PromptUsageStore = class {
11036
11114
  };
11037
11115
 
11038
11116
  // src/storage/input-history-store.ts
11039
- import * as fs16 from "node:fs/promises";
11040
- import * as path22 from "node:path";
11117
+ import * as fs17 from "node:fs/promises";
11118
+ import * as path23 from "node:path";
11041
11119
  var INPUT_HISTORY_DEFAULT_MAX = 100;
11042
11120
  var InputHistoryStore = class {
11043
11121
  /**
@@ -11059,7 +11137,7 @@ var InputHistoryStore = class {
11059
11137
  */
11060
11138
  async load() {
11061
11139
  try {
11062
- const raw = JSON.parse(await fs16.readFile(this.file, "utf8"));
11140
+ const raw = JSON.parse(await fs17.readFile(this.file, "utf8"));
11063
11141
  if (raw && typeof raw === "object" && Array.isArray(raw.entries) && raw.entries.every((e) => typeof e === "string")) {
11064
11142
  return raw.entries.slice(0, this.maxEntries);
11065
11143
  }
@@ -11076,7 +11154,7 @@ var InputHistoryStore = class {
11076
11154
  */
11077
11155
  async save(entries) {
11078
11156
  const cleaned = this.scrubAndFilter(entries);
11079
- await ensureDir(path22.dirname(this.file));
11157
+ await ensureDir(path23.dirname(this.file));
11080
11158
  const payload = {
11081
11159
  version: 1,
11082
11160
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11086,7 +11164,7 @@ var InputHistoryStore = class {
11086
11164
  }
11087
11165
  /** Truncate the file to an empty entry list (used by /clear). */
11088
11166
  async clear() {
11089
- await ensureDir(path22.dirname(this.file));
11167
+ await ensureDir(path23.dirname(this.file));
11090
11168
  const payload = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: [] };
11091
11169
  await atomicWrite(this.file, JSON.stringify(payload, null, 2));
11092
11170
  }
@@ -11116,8 +11194,8 @@ var InputHistoryStore = class {
11116
11194
  };
11117
11195
 
11118
11196
  // src/storage/cloud-sync.ts
11119
- import * as fs17 from "node:fs/promises";
11120
- import * as path23 from "node:path";
11197
+ import * as fs18 from "node:fs/promises";
11198
+ import * as path24 from "node:path";
11121
11199
  import { createHash as createHash8 } from "node:crypto";
11122
11200
  var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
11123
11201
  var CloudSync = class {
@@ -11125,7 +11203,7 @@ var CloudSync = class {
11125
11203
  this.paths = paths;
11126
11204
  this.getConfig = getConfig;
11127
11205
  this.setConfig = setConfig;
11128
- this.statePath = path23.join(paths.globalRoot, "sync-state.json");
11206
+ this.statePath = path24.join(paths.globalRoot, "sync-state.json");
11129
11207
  }
11130
11208
  paths;
11131
11209
  getConfig;
@@ -11200,7 +11278,7 @@ var CloudSync = class {
11200
11278
  lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
11201
11279
  localRev: rev
11202
11280
  };
11203
- await fs17.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
11281
+ await fs18.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
11204
11282
  this.state = syncState;
11205
11283
  return {
11206
11284
  ok: true,
@@ -11232,8 +11310,8 @@ var CloudSync = class {
11232
11310
  const rel = segments.slice(2).join("/");
11233
11311
  const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
11234
11312
  const blobData = await this.getBlob(token, owner, repoName, entry.sha);
11235
- await fs17.mkdir(path23.dirname(destPath), { recursive: true });
11236
- await fs17.writeFile(destPath, Buffer.from(blobData, "base64"));
11313
+ await fs18.mkdir(path24.dirname(destPath), { recursive: true });
11314
+ await fs18.writeFile(destPath, Buffer.from(blobData, "base64"));
11237
11315
  }
11238
11316
  const localRev = await this.hashLocalCategories(cfg.categories);
11239
11317
  const syncState = {
@@ -11242,7 +11320,7 @@ var CloudSync = class {
11242
11320
  lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
11243
11321
  localRev
11244
11322
  };
11245
- await fs17.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
11323
+ await fs18.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
11246
11324
  this.state = syncState;
11247
11325
  return {
11248
11326
  ok: true,
@@ -11261,7 +11339,7 @@ var CloudSync = class {
11261
11339
  }
11262
11340
  async loadState() {
11263
11341
  try {
11264
- const raw = await fs17.readFile(this.statePath, "utf8");
11342
+ const raw = await fs18.readFile(this.statePath, "utf8");
11265
11343
  this.state = JSON.parse(raw);
11266
11344
  } catch {
11267
11345
  this.state = null;
@@ -11339,17 +11417,17 @@ var CloudSync = class {
11339
11417
  const localPath = this.categoryToPath(cat);
11340
11418
  if (!localPath) continue;
11341
11419
  try {
11342
- const stat13 = await fs17.stat(localPath);
11420
+ const stat13 = await fs18.stat(localPath);
11343
11421
  if (stat13.isDirectory()) {
11344
11422
  const files = await this.walkDir(localPath, localPath);
11345
11423
  for (const file of files) {
11346
- const content = await fs17.readFile(file, "utf8");
11347
- const rel = path23.relative(localPath, file).replace(/\\/g, "/");
11424
+ const content = await fs18.readFile(file, "utf8");
11425
+ const rel = path24.relative(localPath, file).replace(/\\/g, "/");
11348
11426
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
11349
11427
  hashes.push(content);
11350
11428
  }
11351
11429
  } else {
11352
- const content = await fs17.readFile(localPath, "utf8");
11430
+ const content = await fs18.readFile(localPath, "utf8");
11353
11431
  entries.push({ path: `data/${cat}`, content, mode: "100644" });
11354
11432
  hashes.push(content);
11355
11433
  }
@@ -11365,15 +11443,15 @@ var CloudSync = class {
11365
11443
  const localPath = this.categoryToPath(cat);
11366
11444
  if (!localPath) continue;
11367
11445
  try {
11368
- const stat13 = await fs17.stat(localPath);
11446
+ const stat13 = await fs18.stat(localPath);
11369
11447
  if (stat13.isDirectory()) {
11370
11448
  const files = await this.walkDir(localPath, localPath);
11371
11449
  for (const file of files) {
11372
- const content = await fs17.readFile(file);
11450
+ const content = await fs18.readFile(file);
11373
11451
  hashes.push(content.toString("base64") + file);
11374
11452
  }
11375
11453
  } else {
11376
- const content = await fs17.readFile(localPath);
11454
+ const content = await fs18.readFile(localPath);
11377
11455
  hashes.push(content.toString("base64") + localPath);
11378
11456
  }
11379
11457
  } catch {
@@ -11400,9 +11478,9 @@ var CloudSync = class {
11400
11478
  }
11401
11479
  async walkDir(dir, base) {
11402
11480
  const results = [];
11403
- const entries = await fs17.readdir(dir, { withFileTypes: true });
11481
+ const entries = await fs18.readdir(dir, { withFileTypes: true });
11404
11482
  for (const entry of entries) {
11405
- const full = path23.join(dir, entry.name);
11483
+ const full = path24.join(dir, entry.name);
11406
11484
  if (entry.isDirectory()) {
11407
11485
  results.push(...await this.walkDir(full, base));
11408
11486
  } else {
@@ -11424,9 +11502,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
11424
11502
  return localPath;
11425
11503
  }
11426
11504
  if (!rel) return localPath;
11427
- const normalizedRel = path23.normalize(rel);
11428
- const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path23.sep}`);
11429
- if (path23.isAbsolute(normalizedRel) || traversesUp) {
11505
+ const normalizedRel = path24.normalize(rel);
11506
+ const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path24.sep}`);
11507
+ if (path24.isAbsolute(normalizedRel) || traversesUp) {
11430
11508
  throw new FsError({
11431
11509
  message: `Refusing CloudSync path traversal: ${remotePath}`,
11432
11510
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -11434,10 +11512,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
11434
11512
  context: { reason: "path_traversal", normalizedRel }
11435
11513
  });
11436
11514
  }
11437
- const dest = path23.resolve(localPath, normalizedRel);
11438
- const root = path23.resolve(localPath);
11439
- const relative6 = path23.relative(root, dest);
11440
- if (relative6.startsWith("..") || path23.isAbsolute(relative6)) {
11515
+ const dest = path24.resolve(localPath, normalizedRel);
11516
+ const root = path24.resolve(localPath);
11517
+ const relative7 = path24.relative(root, dest);
11518
+ if (relative7.startsWith("..") || path24.isAbsolute(relative7)) {
11441
11519
  throw new FsError({
11442
11520
  message: `Refusing CloudSync path outside category root: ${remotePath}`,
11443
11521
  code: ERROR_CODES.FS_DELETE_FAILED,