@wrongstack/core 0.283.1 → 0.284.0

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 (78) hide show
  1. package/dist/{compactor-BxddyCFu.d.ts → compactor-DQLL4HTo.d.ts} +1 -1
  2. package/dist/{config-DkCbA3Bw.d.ts → config-MRqn1V-u.d.ts} +130 -22
  3. package/dist/coordination/index.d.ts +43 -20
  4. package/dist/coordination/index.js +1744 -912
  5. package/dist/coordination/index.js.map +1 -1
  6. package/dist/{default-config-Buho8yge.d.ts → default-config-B79_gJYv.d.ts} +7 -0
  7. package/dist/defaults/index.d.ts +25 -29
  8. package/dist/defaults/index.js +2546 -7396
  9. package/dist/defaults/index.js.map +1 -1
  10. package/dist/{director-state-B_GPa78Q.d.ts → director-state-CMS_bMs4.d.ts} +2 -0
  11. package/dist/{events-KmxSmvho.d.ts → events-Bgnh43r9.d.ts} +27 -10
  12. package/dist/execution/index.d.ts +15 -16
  13. package/dist/execution/index.js +534 -140
  14. package/dist/execution/index.js.map +1 -1
  15. package/dist/execution/prompt-enhancer.d.ts +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{global-mailbox-rWVt8YlO.d.ts → global-mailbox-sZwyI6CS.d.ts} +13 -2
  18. package/dist/{goal-store-8owBXJT2.d.ts → goal-store-Datpp-ar.d.ts} +1 -1
  19. package/dist/hq/index.d.ts +6 -6
  20. package/dist/hq/index.js +58 -7
  21. package/dist/hq/index.js.map +1 -1
  22. package/dist/{index-BhCteHAF.d.ts → index--Gm41aBJ.d.ts} +26 -11
  23. package/dist/{index-BEFWNZYs.d.ts → index-CbyuOE5y.d.ts} +5 -1
  24. package/dist/index.d.ts +50 -1015
  25. package/dist/index.js +21028 -27775
  26. package/dist/index.js.map +1 -1
  27. package/dist/infrastructure/index.d.ts +9 -7
  28. package/dist/infrastructure/index.js +7 -2
  29. package/dist/infrastructure/index.js.map +1 -1
  30. package/dist/kernel/index.d.ts +11 -11
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{mcp-servers-BUx9A_AQ.d.ts → mcp-servers-DEV4s6Ks.d.ts} +3 -3
  33. package/dist/models/index.d.ts +4 -4
  34. package/dist/models/index.js +18 -3
  35. package/dist/models/index.js.map +1 -1
  36. package/dist/{agent-subagent-runner-BsuWhB28.d.ts → multi-agent-coordinator-4RCRZKjN.d.ts} +417 -9
  37. package/dist/{null-fleet-bus-D4e9R7Qc.d.ts → null-fleet-bus-CFoNVQEK.d.ts} +53 -12
  38. package/dist/observability/index.d.ts +3 -3
  39. package/dist/{parallel-eternal-engine-iY2uxocj.d.ts → parallel-eternal-engine-HUrtePLd.d.ts} +4 -5
  40. package/dist/{permission-B6YAtNFz.d.ts → permission-ByDKXEcG.d.ts} +1 -1
  41. package/dist/{pipeline-ixSFe5jg.d.ts → pipeline-DGDyIohT.d.ts} +28 -7
  42. package/dist/{provider-model-resolve-DIxKL_T-.d.ts → provider-model-resolve-o2hHLs79.d.ts} +3 -3
  43. package/dist/{provider-runner-oQhDaZmH.d.ts → provider-runner-ChL-kVg6.d.ts} +3 -3
  44. package/dist/{retry-policy-V67lBOJz.d.ts → retry-policy-CQ7KwXJa.d.ts} +2 -2
  45. package/dist/{secret-vault-D7jeyXUQ.d.ts → secret-vault-DxohkUBc.d.ts} +3 -2
  46. package/dist/security/index.d.ts +4 -4
  47. package/dist/security/index.js +232 -2
  48. package/dist/security/index.js.map +1 -1
  49. package/dist/{selector-Bx2HgUkU.d.ts → selector-D8O6B_Rm.d.ts} +1 -1
  50. package/dist/{session-event-bridge-Bkp7ORfc.d.ts → session-event-bridge-D_z_mBwk.d.ts} +3 -3
  51. package/dist/{session-reader-B-Fi9L6I.d.ts → session-reader-7Esxrkpw.d.ts} +1 -1
  52. package/dist/storage/index.d.ts +77 -39
  53. package/dist/storage/index.js +1223 -288
  54. package/dist/storage/index.js.map +1 -1
  55. package/dist/tasking/index.d.ts +124 -0
  56. package/dist/tasking/index.js +492 -0
  57. package/dist/tasking/index.js.map +1 -0
  58. package/dist/{todos-checkpoint-BwCrj4Cb.d.ts → todos-checkpoint-FY7PjmMx.d.ts} +13 -4
  59. package/dist/{tool-LVSLiE1s.d.ts → tool-BNlnIJvo.d.ts} +216 -17
  60. package/dist/{tool-executor-kElSEgO0.d.ts → tool-executor-CckSCM5E.d.ts} +12 -15
  61. package/dist/tools/index.d.ts +2 -2
  62. package/dist/tools/index.js.map +1 -1
  63. package/dist/types/index.d.ts +94 -19
  64. package/dist/types/index.js +76 -12
  65. package/dist/types/index.js.map +1 -1
  66. package/dist/utils/index.d.ts +44 -10
  67. package/dist/utils/index.js +106 -17
  68. package/dist/utils/index.js.map +1 -1
  69. package/dist/{worktree-manager-BjyAW30D.d.ts → worktree-manager-DzwxKE35.d.ts} +1 -1
  70. package/instructions/coordination/subagent-baseline.md +5 -0
  71. package/instructions/llm/autonomy-brain.md +3 -2
  72. package/instructions/system.md +4 -0
  73. package/package.json +10 -3
  74. package/dist/multi-agent-coordinator-CrjeTB9i.d.ts +0 -357
  75. package/dist/sdd/index.d.ts +0 -1993
  76. package/dist/sdd/index.js +0 -9500
  77. package/dist/sdd/index.js.map +0 -1
  78. package/dist/spec-TBi3Jr6T.d.ts +0 -78
@@ -1,15 +1,136 @@
1
+ import { randomUUID, createHash, randomBytes } from 'crypto';
1
2
  import * as syncFs from 'fs';
2
- import { createReadStream, readFileSync, statSync } from 'fs';
3
- import * as fsp2 from 'fs/promises';
3
+ import { createReadStream, openSync, writeSync, fsyncSync, closeSync, readFileSync, statSync } from 'fs';
4
+ import * as fsp4 from 'fs/promises';
4
5
  import * as path2 from 'path';
5
6
  import { createInterface } from 'readline';
6
- import { randomBytes, randomUUID, createHash } from 'crypto';
7
7
  import * as os from 'os';
8
8
  import { hostname } from 'os';
9
+ import { spawn } from 'child_process';
9
10
  import { fileURLToPath } from 'url';
10
11
 
11
12
  // src/storage/session-store.ts
12
13
 
14
+ // src/utils/child-env.ts
15
+ var ALLOWED_KEYS = /* @__PURE__ */ new Set([
16
+ "PATH",
17
+ "HOME",
18
+ "USER",
19
+ "USERNAME",
20
+ "LOGNAME",
21
+ "SHELL",
22
+ "LANG",
23
+ "LC_ALL",
24
+ "LC_CTYPE",
25
+ "TERM",
26
+ "TZ",
27
+ "TMPDIR",
28
+ "TEMP",
29
+ "TMP",
30
+ "PWD",
31
+ "OLDPWD",
32
+ "COMSPEC",
33
+ "SYSTEMROOT",
34
+ "SYSTEMDRIVE",
35
+ "WINDIR",
36
+ "PROGRAMFILES",
37
+ "PROGRAMFILES(X86)",
38
+ "PROGRAMDATA",
39
+ "APPDATA",
40
+ "LOCALAPPDATA",
41
+ "USERPROFILE",
42
+ "PUBLIC",
43
+ "PATHEXT"
44
+ ]);
45
+ var SECRET_NAME_PARTS = [
46
+ "TOKEN",
47
+ "SECRET",
48
+ "PASSWORD",
49
+ "PASSWD",
50
+ "AUTH",
51
+ "CRED",
52
+ "BEARER",
53
+ "COOKIE",
54
+ "PRIVATE"
55
+ ];
56
+ function looksSecret(name) {
57
+ const upper = name.toUpperCase();
58
+ for (const p of SECRET_NAME_PARTS) {
59
+ if (upper.includes(p)) return true;
60
+ }
61
+ if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;
62
+ if (/API[_-]?KEY/i.test(upper)) return true;
63
+ if (/ACCESS[_-]?KEY/i.test(upper)) return true;
64
+ if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+ function valueHasEmbeddedCredential(value) {
70
+ return /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:@]*:[^/\s@]+@/i.test(value);
71
+ }
72
+ var NODE_OPTIONS_INJECTION_FLAG = /^(?:--require|-r|--import|--loader|--experimental-loader)$/;
73
+ var NODE_OPTIONS_INJECTION_FLAG_EQ = /^(?:--require|-r|--import|--loader|--experimental-loader)=/;
74
+ function sanitizeNodeOptions(value) {
75
+ const tokens = value.split(/\s+/).filter(Boolean);
76
+ const kept = [];
77
+ for (let i = 0; i < tokens.length; i++) {
78
+ const tok = tokens[i];
79
+ if (NODE_OPTIONS_INJECTION_FLAG_EQ.test(tok)) continue;
80
+ if (NODE_OPTIONS_INJECTION_FLAG.test(tok)) {
81
+ i++;
82
+ continue;
83
+ }
84
+ kept.push(tok);
85
+ }
86
+ return kept.join(" ");
87
+ }
88
+ function buildChildEnv(optsOrSessionId) {
89
+ const opts = {};
90
+ const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
91
+ const legacyHasOwn = Object.hasOwn(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
92
+ const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
93
+ if (passthrough && !process.env["CI"]) {
94
+ console.warn(
95
+ "[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
96
+ );
97
+ }
98
+ const out = {};
99
+ const nodeEnvDefaulted = process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] === "1";
100
+ for (const [k, v] of Object.entries(process.env)) {
101
+ if (v === void 0) continue;
102
+ if (nodeEnvDefaulted && (k === "NODE_ENV" || k === "WRONGSTACK_NODE_ENV_DEFAULTED")) continue;
103
+ if (passthrough) {
104
+ out[k] = v;
105
+ continue;
106
+ }
107
+ const upper = k.toUpperCase();
108
+ if (valueHasEmbeddedCredential(v)) continue;
109
+ if (ALLOWED_KEYS.has(upper)) {
110
+ out[k] = v;
111
+ continue;
112
+ }
113
+ if (looksSecret(upper)) continue;
114
+ if (upper === "NODE_OPTIONS") {
115
+ const sanitized = sanitizeNodeOptions(v);
116
+ if (sanitized) out[k] = sanitized;
117
+ continue;
118
+ }
119
+ if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, …).
120
+ // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted
121
+ // vault). Forwarding keeps child wstack processes — e.g. ones spawned
122
+ // by the test suite — inside the same redirected global root.
123
+ upper.startsWith("WRONGSTACK_") || upper === "EDITOR" || upper === "VISUAL" || upper === "PAGER") {
124
+ out[k] = v;
125
+ }
126
+ }
127
+ if (opts.extra) {
128
+ Object.assign(out, opts.extra);
129
+ }
130
+ if (opts.sessionId) out["WRONGSTACK_SESSION_ID"] = opts.sessionId;
131
+ return out;
132
+ }
133
+
13
134
  // src/utils/term.ts
14
135
  var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
15
136
  function isStdoutTTY() {
@@ -28,7 +149,7 @@ function envFlag(value) {
28
149
  return !/^(0|false|no|off)$/i.test(value.trim());
29
150
  }
30
151
  var COLOR = isColorTty();
31
- var wrap = (open8, close) => (s) => COLOR ? `\x1B[${open8}m${s}\x1B[${close}m` : s;
152
+ var wrap = (open9, close) => (s) => COLOR ? `\x1B[${open9}m${s}\x1B[${close}m` : s;
32
153
  var color = {
33
154
  reset: wrap("0", "0"),
34
155
  bold: wrap("1", "22"),
@@ -478,16 +599,16 @@ var FsError = class extends WrongStackError {
478
599
  // src/utils/atomic-write.ts
479
600
  async function atomicWrite(targetPath, content, opts = {}) {
480
601
  const dir = path2.dirname(targetPath);
481
- await fsp2.mkdir(dir, { recursive: true });
602
+ await fsp4.mkdir(dir, { recursive: true });
482
603
  const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
483
604
  try {
484
605
  if (typeof content === "string") {
485
- await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
606
+ await fsp4.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
486
607
  } else {
487
- await fsp2.writeFile(tmp, content, { flag: "wx" });
608
+ await fsp4.writeFile(tmp, content, { flag: "wx" });
488
609
  }
489
610
  try {
490
- const fh = await fsp2.open(tmp, "r+");
611
+ const fh = await fsp4.open(tmp, "r+");
491
612
  try {
492
613
  await fh.sync();
493
614
  } finally {
@@ -497,35 +618,35 @@ async function atomicWrite(targetPath, content, opts = {}) {
497
618
  }
498
619
  let mode;
499
620
  try {
500
- const stat11 = await fsp2.stat(targetPath);
501
- mode = stat11.mode & 511;
621
+ const stat13 = await fsp4.stat(targetPath);
622
+ mode = stat13.mode & 511;
502
623
  } catch {
503
624
  mode = opts.mode;
504
625
  }
505
626
  if (mode !== void 0) {
506
- await fsp2.chmod(tmp, mode);
627
+ await fsp4.chmod(tmp, mode);
507
628
  }
508
629
  await renameWithRetry(tmp, targetPath);
509
630
  if (mode !== void 0 && process.platform === "win32") {
510
631
  try {
511
- await fsp2.chmod(targetPath, mode);
632
+ await fsp4.chmod(targetPath, mode);
512
633
  } catch {
513
634
  }
514
635
  }
515
636
  } catch (err) {
516
637
  try {
517
- await fsp2.unlink(tmp);
638
+ await fsp4.unlink(tmp);
518
639
  } catch {
519
640
  }
520
641
  throw err;
521
642
  }
522
643
  }
523
644
  async function ensureDir(dir) {
524
- await fsp2.mkdir(dir, { recursive: true });
645
+ await fsp4.mkdir(dir, { recursive: true });
525
646
  }
526
647
  async function withFileLock(targetPath, fn, opts = {}) {
527
648
  const dir = path2.dirname(targetPath);
528
- await fsp2.mkdir(dir, { recursive: true });
649
+ await fsp4.mkdir(dir, { recursive: true });
529
650
  const lockPath = path2.join(dir, `.${path2.basename(targetPath)}.lock`);
530
651
  const timeoutMs = opts.timeoutMs ?? 5e3;
531
652
  const staleMs = opts.staleMs ?? 3e4;
@@ -533,20 +654,20 @@ async function withFileLock(targetPath, fn, opts = {}) {
533
654
  let handle;
534
655
  for (; ; ) {
535
656
  try {
536
- handle = await fsp2.open(lockPath, "wx");
657
+ handle = await fsp4.open(lockPath, "wx");
537
658
  await handle.writeFile(`${process.pid}:${Date.now()}`);
538
659
  break;
539
660
  } catch (err) {
540
661
  const code = err.code;
541
662
  if (code === "ENOENT") {
542
- await fsp2.mkdir(dir, { recursive: true });
663
+ await fsp4.mkdir(dir, { recursive: true });
543
664
  continue;
544
665
  }
545
666
  if (code !== "EEXIST") throw err;
546
667
  try {
547
- const stat11 = await fsp2.stat(lockPath);
548
- if (Date.now() - stat11.mtimeMs > staleMs) {
549
- await fsp2.unlink(lockPath);
668
+ const stat13 = await fsp4.stat(lockPath);
669
+ if (Date.now() - stat13.mtimeMs > staleMs) {
670
+ await fsp4.unlink(lockPath);
550
671
  continue;
551
672
  }
552
673
  } catch {
@@ -560,7 +681,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
560
681
  context: { timeoutMs }
561
682
  });
562
683
  }
563
- await new Promise((resolve8) => setTimeout(resolve8, 25));
684
+ await new Promise((resolve11) => setTimeout(resolve11, 25));
564
685
  }
565
686
  }
566
687
  try {
@@ -571,7 +692,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
571
692
  } catch {
572
693
  }
573
694
  try {
574
- await fsp2.unlink(lockPath);
695
+ await fsp4.unlink(lockPath);
575
696
  } catch {
576
697
  }
577
698
  }
@@ -579,14 +700,14 @@ async function withFileLock(targetPath, fn, opts = {}) {
579
700
  var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
580
701
  async function renameWithRetry(from, to) {
581
702
  if (process.platform !== "win32") {
582
- await fsp2.rename(from, to);
703
+ await fsp4.rename(from, to);
583
704
  return;
584
705
  }
585
706
  const delays = [10, 25, 60, 120, 250];
586
707
  let lastErr;
587
708
  for (let i = 0; i <= delays.length; i++) {
588
709
  try {
589
- await fsp2.rename(from, to);
710
+ await fsp4.rename(from, to);
590
711
  return;
591
712
  } catch (err) {
592
713
  lastErr = err;
@@ -594,7 +715,7 @@ async function renameWithRetry(from, to) {
594
715
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
595
716
  throw err;
596
717
  }
597
- await new Promise((resolve8) => setTimeout(resolve8, delays[i]));
718
+ await new Promise((resolve11) => setTimeout(resolve11, delays[i]));
598
719
  }
599
720
  }
600
721
  throw lastErr;
@@ -618,6 +739,7 @@ var FileSessionWriter = class _FileSessionWriter {
618
739
  this.manifestFile = opts.dir ? path2.join(opts.dir, `${path2.basename(id)}.summary.json`) : "";
619
740
  this.filePath = opts.filePath ?? "";
620
741
  this.secretScrubber = opts.secretScrubber;
742
+ this.checkpointCas = opts.checkpointCas;
621
743
  this.onCloseCb = opts.onClose;
622
744
  this.summary = {
623
745
  id,
@@ -659,6 +781,7 @@ var FileSessionWriter = class _FileSessionWriter {
659
781
  appendFailCount = 0;
660
782
  lastAppendWarnAt = 0;
661
783
  secretScrubber;
784
+ checkpointCas;
662
785
  onCloseCb;
663
786
  /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
664
787
  traceId;
@@ -679,9 +802,12 @@ var FileSessionWriter = class _FileSessionWriter {
679
802
  // a timer-driven flush racing an explicit flush()/close() issues two
680
803
  // concurrent appendFile() calls on the shared O_APPEND handle — the kernel
681
804
  // may complete them out of order (chronology breaks) or, for large
682
- // batches, interleave partial writes (torn JSONL lines). The chain keeps
683
- // exactly one write in flight; failures don't break the chain.
805
+ // batches, interleave partial writes (torn JSONL lines). The write chain
806
+ // serializes handle I/O; flushChain additionally serializes the act of
807
+ // draining/re-queueing the in-memory event buffer so a failed older batch
808
+ // can never be overtaken by a newer successful one.
684
809
  writeChain = Promise.resolve();
810
+ flushChain = Promise.resolve();
685
811
  /** Enqueue a write on the FIFO chain. Resolves/rejects with that write. */
686
812
  enqueueWrite(data) {
687
813
  const write = this.writeChain.then(() => this.handle.appendFile(data, "utf8"));
@@ -702,13 +828,25 @@ var FileSessionWriter = class _FileSessionWriter {
702
828
  /**
703
829
  * Scrub secrets out of conversation-turn events before they are observed
704
830
  * for the summary, written to the JSONL log, or surfaced on resume. Only
705
- * `user_input` / `llm_response` carry free-form user/model text; other event
706
- * types either have no secret-bearing content or are already scrubbed
707
- * upstream (tool results). Returns the event unchanged when no scrubber is
708
- * configured.
831
+ * `user_input`, `llm_response`, and `context_snapshot` carry free-form
832
+ * user/model text; other event types either have no secret-bearing content
833
+ * or are already scrubbed upstream (tool results). Snapshot normalization
834
+ * also strips transient token-cache fields even when no scrubber is set.
709
835
  */
710
836
  scrubEvent(event) {
711
837
  const s = this.secretScrubber;
838
+ if (event.type === "context_snapshot") {
839
+ return {
840
+ ...event,
841
+ messages: event.messages.map((message) => {
842
+ const { _estTokens: _ignored, ...persisted } = message;
843
+ return {
844
+ ...persisted,
845
+ content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
846
+ };
847
+ })
848
+ };
849
+ }
712
850
  if (!s) return event;
713
851
  if (event.type === "user_input") {
714
852
  return {
@@ -722,10 +860,55 @@ var FileSessionWriter = class _FileSessionWriter {
722
860
  return event;
723
861
  }
724
862
  pendingFileSnapshots = [];
863
+ /** Prompt whose tool work is currently executing. Set by writeCheckpoint. */
864
+ activePromptIndex = null;
725
865
  /** Tracks open tool_use IDs during the current run to serialize on close for resume. */
726
866
  openToolUses = /* @__PURE__ */ new Set();
867
+ /**
868
+ * Buffer an event from a synchronous Context callback. ensureInit() starts
869
+ * by pushing the lifecycle preamble synchronously, so session_start always
870
+ * precedes observations even though these callbacks cannot await append().
871
+ */
872
+ bufferSynchronousEvent(event) {
873
+ if (this.closed) return;
874
+ void this.ensureInit();
875
+ this.observeForSummary(event);
876
+ this.writeBuffer.push(event);
877
+ if (this.writeBuffer.length >= _FileSessionWriter.FLUSH_SIZE) {
878
+ if (this.flushTimer) {
879
+ clearTimeout(this.flushTimer);
880
+ this.flushTimer = null;
881
+ }
882
+ void this.flushBuffer().catch(() => {
883
+ });
884
+ } else {
885
+ this.scheduleFlush();
886
+ }
887
+ }
727
888
  recordFileChange(input) {
728
- this.pendingFileSnapshots.push(input);
889
+ if (this.closed) return;
890
+ if (this.activePromptIndex === null) {
891
+ this.pendingFileSnapshots.push(input);
892
+ return;
893
+ }
894
+ const event = {
895
+ type: "file_snapshot",
896
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
897
+ promptIndex: this.activePromptIndex,
898
+ files: [input]
899
+ };
900
+ this.bufferSynchronousEvent(event);
901
+ }
902
+ recordFileObservation(input) {
903
+ if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs)) return;
904
+ this.bufferSynchronousEvent({
905
+ type: "file_observation",
906
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
907
+ path: input.path,
908
+ hash: input.hash.toLowerCase(),
909
+ mtimeMs: input.mtimeMs,
910
+ source: input.source
911
+ });
729
912
  }
730
913
  recordSideEffect(input) {
731
914
  this.append({
@@ -743,18 +926,13 @@ var FileSessionWriter = class _FileSessionWriter {
743
926
  return Array.from(this.openToolUses);
744
927
  }
745
928
  async writeSessionStartLazy() {
746
- const record = `${JSON.stringify({
929
+ this.writeBuffer.push({
747
930
  type: this.resumed ? "session_resumed" : "session_start",
748
931
  ts: this.startedAt,
749
932
  id: this.id,
750
933
  model: this.meta.model ?? "unknown",
751
934
  provider: this.meta.provider ?? "unknown"
752
- })}
753
- `;
754
- try {
755
- await this.enqueueWrite(record);
756
- } catch {
757
- }
935
+ });
758
936
  }
759
937
  async append(event) {
760
938
  if (this.closed) return;
@@ -767,7 +945,8 @@ var FileSessionWriter = class _FileSessionWriter {
767
945
  clearTimeout(this.flushTimer);
768
946
  this.flushTimer = null;
769
947
  }
770
- await this.flushBuffer();
948
+ await this.flushBuffer().catch(() => {
949
+ });
771
950
  } else {
772
951
  this.scheduleFlush();
773
952
  }
@@ -785,7 +964,8 @@ var FileSessionWriter = class _FileSessionWriter {
785
964
  clearTimeout(this.flushTimer);
786
965
  this.flushTimer = null;
787
966
  }
788
- await this.flushBuffer();
967
+ await this.flushBuffer().catch(() => {
968
+ });
789
969
  } else {
790
970
  this.scheduleFlush();
791
971
  }
@@ -795,16 +975,49 @@ var FileSessionWriter = class _FileSessionWriter {
795
975
  * (user_input, llm_response) call this so they survive SIGKILL/crash
796
976
  * instead of sitting in the in-memory buffer for up to 500ms.
797
977
  *
798
- * Idempotent — cancels any pending timer and writes whatever has
799
- * accumulated in the buffer. Safe to call even when the buffer
800
- * is empty (no-op).
978
+ * Idempotent — cancels any pending timer, writes whatever has accumulated,
979
+ * then asks the OS to synchronize the file data before resolving. Even an
980
+ * empty-buffer flush synchronizes any earlier timer-driven append.
801
981
  */
802
982
  async flush() {
983
+ if (this.closed) return;
803
984
  if (this.flushTimer) {
804
985
  clearTimeout(this.flushTimer);
805
986
  this.flushTimer = null;
806
987
  }
807
988
  await this.flushBuffer();
989
+ await this.writeChain;
990
+ await this.handle.datasync();
991
+ }
992
+ /**
993
+ * Last-gasp synchronous drain for hard-exit paths (process.exit after
994
+ * rapid Ctrl+C). The async write chain cannot be awaited when the process
995
+ * is about to die, but whatever still sits in the in-memory buffer CAN be
996
+ * saved with a blocking append. Best-effort: an in-flight async write may
997
+ * be cut off by the exit regardless; errors here are swallowed.
998
+ */
999
+ flushSync() {
1000
+ if (this.writeBuffer.length === 0 || !this.filePath) return;
1001
+ if (this.flushTimer) {
1002
+ clearTimeout(this.flushTimer);
1003
+ this.flushTimer = null;
1004
+ }
1005
+ const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
1006
+ this.writeBuffer = [];
1007
+ let fd;
1008
+ try {
1009
+ fd = openSync(this.filePath, "a");
1010
+ writeSync(fd, batch, null, "utf8");
1011
+ fsyncSync(fd);
1012
+ } catch {
1013
+ } finally {
1014
+ if (fd !== void 0) {
1015
+ try {
1016
+ closeSync(fd);
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ }
808
1021
  }
809
1022
  /** Schedule a deferred flush. No-op if a timer is already pending. */
810
1023
  scheduleFlush() {
@@ -817,15 +1030,24 @@ var FileSessionWriter = class _FileSessionWriter {
817
1030
  }
818
1031
  /**
819
1032
  * Flush all buffered events to disk as a single appendFile call.
820
- * Errors use the same throttled-warning pattern the old per-event
821
- * append path used one warning every 5s with a suppressed count.
822
- * On failure the buffer is cleared (events are best-effort, same as
823
- * the old per-event path where a failed write was silently dropped).
1033
+ * Concurrent callers join a FIFO flush chain. On failure the drained batch
1034
+ * is prepended to the live buffer, preserving chronology and allowing the
1035
+ * next timer/boundary flush to retry it. Warnings retain the existing
1036
+ * throttled behavior.
824
1037
  */
825
- async flushBuffer() {
1038
+ flushBuffer() {
1039
+ const flush = this.flushChain.then(() => this.flushBufferOnce());
1040
+ this.flushChain = flush.then(
1041
+ () => void 0,
1042
+ () => void 0
1043
+ );
1044
+ return flush;
1045
+ }
1046
+ async flushBufferOnce() {
826
1047
  if (this.writeBuffer.length === 0) return;
827
- const eventCount = this.writeBuffer.length;
828
- const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
1048
+ const events = this.writeBuffer;
1049
+ const eventCount = events.length;
1050
+ const batch = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
829
1051
  this.writeBuffer = [];
830
1052
  const t0 = Date.now();
831
1053
  let outcome = "success";
@@ -835,6 +1057,7 @@ var FileSessionWriter = class _FileSessionWriter {
835
1057
  } catch (err) {
836
1058
  outcome = "failure";
837
1059
  errorMsg = toErrorMessage(err);
1060
+ this.writeBuffer = [...events, ...this.writeBuffer];
838
1061
  this.appendFailCount += eventCount;
839
1062
  const now = Date.now();
840
1063
  if (now - this.lastAppendWarnAt > 5e3) {
@@ -848,6 +1071,8 @@ var FileSessionWriter = class _FileSessionWriter {
848
1071
  this.lastAppendWarnAt = now;
849
1072
  this.appendFailCount = 0;
850
1073
  }
1074
+ if (!this.closed) this.scheduleFlush();
1075
+ throw err;
851
1076
  } finally {
852
1077
  this.events?.emit("storage.write", {
853
1078
  sessionId: this.id,
@@ -902,10 +1127,22 @@ var FileSessionWriter = class _FileSessionWriter {
902
1127
  }
903
1128
  async close() {
904
1129
  if (this.closePromise) return this.closePromise;
905
- this.closePromise = this.doClose();
1130
+ this.closePromise = this.doClose().catch((err) => {
1131
+ this.closed = false;
1132
+ this.closePromise = null;
1133
+ if (this.writeBuffer.length > 0) this.scheduleFlush();
1134
+ throw err;
1135
+ });
906
1136
  return this.closePromise;
907
1137
  }
908
1138
  async doClose() {
1139
+ if (this.pendingFileSnapshots.length > 0) {
1140
+ await this.writeFileSnapshot(
1141
+ this.activePromptIndex ?? 0,
1142
+ [...this.pendingFileSnapshots]
1143
+ );
1144
+ this.pendingFileSnapshots = [];
1145
+ }
909
1146
  this.closed = true;
910
1147
  if (this.flushTimer) {
911
1148
  clearTimeout(this.flushTimer);
@@ -913,6 +1150,7 @@ var FileSessionWriter = class _FileSessionWriter {
913
1150
  }
914
1151
  await this.flushBuffer();
915
1152
  await this.writeChain;
1153
+ await this.handle.datasync();
916
1154
  this.summary = {
917
1155
  ...this.summary,
918
1156
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -977,12 +1215,29 @@ var FileSessionWriter = class _FileSessionWriter {
977
1215
  await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
978
1216
  this.pendingFileSnapshots = [];
979
1217
  }
1218
+ let workspaceCheckpoint;
1219
+ try {
1220
+ workspaceCheckpoint = await this.checkpointCas?.capture(this.id, promptIndex);
1221
+ } catch (err) {
1222
+ console.warn(
1223
+ JSON.stringify({
1224
+ level: "warn",
1225
+ event: "session.workspace_checkpoint_capture_failed",
1226
+ sessionId: this.id,
1227
+ promptIndex,
1228
+ message: toErrorMessage(err),
1229
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1230
+ })
1231
+ );
1232
+ }
980
1233
  await this.append({
981
1234
  type: "checkpoint",
982
1235
  ts: (/* @__PURE__ */ new Date()).toISOString(),
983
1236
  promptIndex,
984
- promptPreview
1237
+ promptPreview,
1238
+ ...workspaceCheckpoint ? { workspaceCheckpoint } : {}
985
1239
  });
1240
+ this.activePromptIndex = promptIndex;
986
1241
  this.events?.emit("checkpoint.written", {
987
1242
  sessionId: this.id,
988
1243
  promptIndex,
@@ -1021,7 +1276,7 @@ var FileSessionWriter = class _FileSessionWriter {
1021
1276
  let removedCount = 0;
1022
1277
  let targetCheckpointSeen = false;
1023
1278
  try {
1024
- fd = await fsp2.open(this.filePath, "r", 384);
1279
+ fd = await fsp4.open(this.filePath, "r", 384);
1025
1280
  while (true) {
1026
1281
  const buf = Buffer.alloc(CHUNK_SIZE);
1027
1282
  const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
@@ -1076,7 +1331,7 @@ var FileSessionWriter = class _FileSessionWriter {
1076
1331
  await this.writeChain;
1077
1332
  await this.handle.close();
1078
1333
  const tmpPath = `${this.filePath}.rewind.tmp`;
1079
- const src = await fsp2.open(this.filePath, "r", 384);
1334
+ const src = await fsp4.open(this.filePath, "r", 384);
1080
1335
  try {
1081
1336
  const statResult = await src.stat();
1082
1337
  const totalSize = statResult.size;
@@ -1092,7 +1347,7 @@ var FileSessionWriter = class _FileSessionWriter {
1092
1347
  } else {
1093
1348
  newlineAfterCheckpoint = totalSize;
1094
1349
  }
1095
- const writeFd = await fsp2.open(tmpPath, "w", 384);
1350
+ const writeFd = await fsp4.open(tmpPath, "w", 384);
1096
1351
  try {
1097
1352
  let readOffset = 0;
1098
1353
  while (readOffset < newlineAfterCheckpoint) {
@@ -1140,11 +1395,11 @@ var FileSessionWriter = class _FileSessionWriter {
1140
1395
  await writeFd.close();
1141
1396
  }
1142
1397
  await src.close();
1143
- await fsp2.rename(tmpPath, this.filePath);
1144
- this.handle = await fsp2.open(this.filePath, "a", 384);
1398
+ await fsp4.rename(tmpPath, this.filePath);
1399
+ this.handle = await fsp4.open(this.filePath, "a", 384);
1145
1400
  } catch (err) {
1146
- await fsp2.unlink(tmpPath).catch(() => void 0);
1147
- this.handle = await fsp2.open(this.filePath, "a", 384).catch(() => this.handle);
1401
+ await fsp4.unlink(tmpPath).catch(() => void 0);
1402
+ this.handle = await fsp4.open(this.filePath, "a", 384).catch(() => this.handle);
1148
1403
  throw err;
1149
1404
  }
1150
1405
  await this.append({
@@ -1153,6 +1408,7 @@ var FileSessionWriter = class _FileSessionWriter {
1153
1408
  toPromptIndex: targetPromptIndex,
1154
1409
  revertedFiles: []
1155
1410
  });
1411
+ this.activePromptIndex = targetPromptIndex;
1156
1412
  this.events?.emit("session.rewound", {
1157
1413
  sessionId: this.id,
1158
1414
  toPromptIndex: targetPromptIndex,
@@ -1167,6 +1423,7 @@ var FileSessionWriter = class _FileSessionWriter {
1167
1423
  clearTimeout(this.flushTimer);
1168
1424
  this.flushTimer = null;
1169
1425
  }
1426
+ await this.flushChain;
1170
1427
  this.writeBuffer = [];
1171
1428
  await this.writeChain;
1172
1429
  const record = `${JSON.stringify({
@@ -1177,7 +1434,9 @@ var FileSessionWriter = class _FileSessionWriter {
1177
1434
  provider: this.meta.provider ?? "unknown"
1178
1435
  })}
1179
1436
  `;
1180
- await fsp2.writeFile(this.filePath, record, "utf8");
1437
+ await fsp4.writeFile(this.filePath, record, "utf8");
1438
+ this.activePromptIndex = null;
1439
+ this.pendingFileSnapshots = [];
1181
1440
  }
1182
1441
  /**
1183
1442
  * Write an in-flight marker. The agent loop should call
@@ -1204,6 +1463,13 @@ var FileSessionWriter = class _FileSessionWriter {
1204
1463
  * cleanly X times, then died without finishing Y".
1205
1464
  */
1206
1465
  async clearInFlightMarker(reason) {
1466
+ if (this.pendingFileSnapshots.length > 0) {
1467
+ await this.writeFileSnapshot(
1468
+ this.activePromptIndex ?? 0,
1469
+ [...this.pendingFileSnapshots]
1470
+ );
1471
+ this.pendingFileSnapshots = [];
1472
+ }
1207
1473
  await this.append({
1208
1474
  type: "in_flight_end",
1209
1475
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1213,6 +1479,492 @@ var FileSessionWriter = class _FileSessionWriter {
1213
1479
  }
1214
1480
  };
1215
1481
 
1482
+ // src/storage/storage-concurrency.ts
1483
+ async function mapWithConcurrency(items, concurrency, mapper) {
1484
+ if (items.length === 0) return [];
1485
+ const limit = Math.max(1, Math.floor(concurrency));
1486
+ const results = new Array(items.length);
1487
+ let next = 0;
1488
+ async function worker() {
1489
+ while (true) {
1490
+ const index = next;
1491
+ next++;
1492
+ if (index >= items.length) return;
1493
+ results[index] = await mapper(items[index], index);
1494
+ }
1495
+ }
1496
+ const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
1497
+ await Promise.all(workers);
1498
+ return results;
1499
+ }
1500
+
1501
+ // src/storage/session-checkpoint-cas.ts
1502
+ var HASH_RE = /^[a-f\d]{64}$/;
1503
+ var CAPTURE_CONCURRENCY = 8;
1504
+ var MAX_GIT_OUTPUT = 16 * 1024 * 1024;
1505
+ var MAX_BLOB_BYTES = 64 * 1024 * 1024;
1506
+ var MAX_CHECKPOINT_BYTES = 512 * 1024 * 1024;
1507
+ function sha256(content) {
1508
+ return createHash("sha256").update(content).digest("hex");
1509
+ }
1510
+ function isInside(root, target) {
1511
+ const relative6 = path2.relative(root, target);
1512
+ return relative6 === "" || !relative6.startsWith("..") && !path2.isAbsolute(relative6);
1513
+ }
1514
+ function normalizeRelative(input) {
1515
+ if (!input || path2.isAbsolute(input)) return null;
1516
+ const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
1517
+ const resolved = path2.posix.normalize(normalized);
1518
+ if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
1519
+ return resolved;
1520
+ }
1521
+ function parseNulPaths(output) {
1522
+ return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
1523
+ }
1524
+ function isWrongStackWorktreePath(relative6) {
1525
+ return relative6 === ".wrongstack/worktrees" || relative6.startsWith(".wrongstack/worktrees/");
1526
+ }
1527
+ var SessionCheckpointCas = class {
1528
+ rootDir;
1529
+ projectRoot;
1530
+ runGit;
1531
+ constructor(opts) {
1532
+ this.rootDir = path2.resolve(opts.rootDir);
1533
+ this.projectRoot = path2.resolve(opts.projectRoot);
1534
+ this.runGit = opts.runGit ?? defaultRunGit;
1535
+ }
1536
+ async capture(_sessionId, _promptIndex) {
1537
+ const head = await this.runGit(["rev-parse", "--verify", "HEAD"], this.projectRoot);
1538
+ const baseHead = head.stdout.trim();
1539
+ if (head.code !== 0 || head.stdoutTruncated || !/^[a-f\d]{40,64}$/i.test(baseHead)) {
1540
+ return void 0;
1541
+ }
1542
+ const [tracked, untracked] = await Promise.all([
1543
+ this.runGit(["diff", "HEAD", "--name-only", "-z", "--"], this.projectRoot),
1544
+ this.runGit(["ls-files", "--others", "--exclude-standard", "-z"], this.projectRoot)
1545
+ ]);
1546
+ if (tracked.code !== 0 || untracked.code !== 0 || tracked.stdoutTruncated || untracked.stdoutTruncated) {
1547
+ return void 0;
1548
+ }
1549
+ const relativePaths = [
1550
+ .../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
1551
+ ].filter((relative6) => !isWrongStackWorktreePath(relative6)).sort();
1552
+ const unresolved = [];
1553
+ let scheduledBytes = 0;
1554
+ const captured = await mapWithConcurrency(
1555
+ relativePaths,
1556
+ CAPTURE_CONCURRENCY,
1557
+ async (relative6) => {
1558
+ const absolute = path2.resolve(this.projectRoot, ...relative6.split("/"));
1559
+ if (!isInside(this.projectRoot, absolute)) {
1560
+ unresolved.push({ path: relative6, reason: "path escapes project root" });
1561
+ return null;
1562
+ }
1563
+ try {
1564
+ const stat13 = await fsp4.lstat(absolute);
1565
+ if (stat13.isSymbolicLink()) {
1566
+ const linkTarget = await fsp4.readlink(absolute);
1567
+ const resolvedLink = path2.resolve(path2.dirname(absolute), linkTarget);
1568
+ if (path2.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
1569
+ unresolved.push({
1570
+ path: relative6,
1571
+ reason: "symlink target escapes project root"
1572
+ });
1573
+ return null;
1574
+ }
1575
+ return { path: relative6, state: "symlink", linkTarget };
1576
+ }
1577
+ if (!stat13.isFile()) {
1578
+ unresolved.push({ path: relative6, reason: "changed path is not a regular file" });
1579
+ return null;
1580
+ }
1581
+ if (stat13.size > MAX_BLOB_BYTES) {
1582
+ unresolved.push({
1583
+ path: relative6,
1584
+ reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
1585
+ });
1586
+ return null;
1587
+ }
1588
+ if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
1589
+ unresolved.push({
1590
+ path: relative6,
1591
+ reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
1592
+ });
1593
+ return null;
1594
+ }
1595
+ scheduledBytes += stat13.size;
1596
+ const content = await fsp4.readFile(absolute);
1597
+ const blobHash = sha256(content);
1598
+ await this.putBlob(blobHash, content);
1599
+ return { path: relative6, state: "file", blobHash, mode: stat13.mode & 511 };
1600
+ } catch (err) {
1601
+ if (err.code === "ENOENT") {
1602
+ return { path: relative6, state: "absent" };
1603
+ }
1604
+ unresolved.push({ path: relative6, reason: toErrorMessage(err) });
1605
+ return null;
1606
+ }
1607
+ }
1608
+ );
1609
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
1610
+ const manifest = {
1611
+ version: 1,
1612
+ baseHead: baseHead.toLowerCase(),
1613
+ coverage: "git-head-plus-dirty",
1614
+ entries: captured.filter((entry) => entry !== null),
1615
+ unresolved: [...unresolved].sort((a, b) => a.path.localeCompare(b.path))
1616
+ };
1617
+ const encoded = JSON.stringify(manifest);
1618
+ const manifestHash = sha256(encoded);
1619
+ await this.putManifest(manifestHash, encoded);
1620
+ return {
1621
+ manifestHash,
1622
+ baseHead: manifest.baseHead,
1623
+ entryCount: manifest.entries.length,
1624
+ unresolvedCount: manifest.unresolved.length,
1625
+ capturedAt,
1626
+ coverage: manifest.coverage
1627
+ };
1628
+ }
1629
+ async materialize(checkpoint, targetRoot) {
1630
+ const target = path2.resolve(targetRoot);
1631
+ if (target === this.projectRoot) {
1632
+ throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
1633
+ }
1634
+ const targetStat = await fsp4.stat(target);
1635
+ if (!targetStat.isDirectory()) throw new Error(`Checkpoint target is not a directory: ${target}`);
1636
+ const manifest = await this.loadManifest(checkpoint.manifestHash);
1637
+ if (manifest.baseHead !== checkpoint.baseHead.toLowerCase() || manifest.coverage !== checkpoint.coverage || manifest.entries.length !== checkpoint.entryCount || manifest.unresolved.length !== checkpoint.unresolvedCount) {
1638
+ throw new Error("Workspace checkpoint reference does not match its CAS manifest");
1639
+ }
1640
+ if (manifest.unresolved.length > 0) {
1641
+ throw new Error(
1642
+ `Workspace checkpoint has ${manifest.unresolved.length} unresolved path(s); exact materialization refused`
1643
+ );
1644
+ }
1645
+ const targetHead = await this.runGit(["rev-parse", "--verify", "HEAD"], target);
1646
+ if (targetHead.code !== 0 || targetHead.stdoutTruncated || targetHead.stdout.trim().toLowerCase() !== manifest.baseHead) {
1647
+ throw new Error(
1648
+ `Checkpoint target HEAD must equal ${manifest.baseHead}; got ${targetHead.stdout.trim() || "unknown"}`
1649
+ );
1650
+ }
1651
+ const targetStatus = await this.runGit(
1652
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignored=matching"],
1653
+ target
1654
+ );
1655
+ if (targetStatus.code !== 0 || targetStatus.stdoutTruncated) {
1656
+ throw new Error("Unable to verify that the checkpoint target is clean");
1657
+ }
1658
+ if (targetStatus.stdout.length > 0) {
1659
+ throw new Error("Checkpoint target must be a clean checkout before materialization");
1660
+ }
1661
+ const realTarget = await fsp4.realpath(target);
1662
+ const writtenFiles = [];
1663
+ const deletedFiles = [];
1664
+ const errors = [];
1665
+ const prepared = [];
1666
+ for (const entry of manifest.entries) {
1667
+ try {
1668
+ const output = await this.safeOutputPath(target, realTarget, entry.path);
1669
+ if (entry.state === "symlink") {
1670
+ if (path2.isAbsolute(entry.linkTarget)) {
1671
+ throw new Error("absolute symlink target refused");
1672
+ }
1673
+ const resolvedLink = path2.resolve(path2.dirname(output), entry.linkTarget);
1674
+ if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
1675
+ }
1676
+ prepared.push({
1677
+ entry,
1678
+ output,
1679
+ ...entry.state === "file" ? { content: await this.readBlob(entry.blobHash) } : {}
1680
+ });
1681
+ } catch (err) {
1682
+ errors.push(`${entry.path}: ${toErrorMessage(err)}`);
1683
+ }
1684
+ }
1685
+ if (errors.length > 0) {
1686
+ return { targetRoot: target, writtenFiles, deletedFiles, errors };
1687
+ }
1688
+ for (const { entry, output, content } of prepared) {
1689
+ try {
1690
+ if (entry.state === "absent") {
1691
+ await fsp4.unlink(output).catch((err) => {
1692
+ if (err.code !== "ENOENT") throw err;
1693
+ });
1694
+ deletedFiles.push(entry.path);
1695
+ continue;
1696
+ }
1697
+ if (entry.state === "symlink") {
1698
+ await fsp4.unlink(output).catch((err) => {
1699
+ if (err.code !== "ENOENT") throw err;
1700
+ });
1701
+ await fsp4.mkdir(path2.dirname(output), { recursive: true });
1702
+ await fsp4.symlink(entry.linkTarget, output);
1703
+ writtenFiles.push(entry.path);
1704
+ continue;
1705
+ }
1706
+ if (!content) throw new Error("CAS blob was not prepared");
1707
+ await atomicWrite(output, content, { mode: entry.mode });
1708
+ await fsp4.chmod(output, entry.mode).catch(() => void 0);
1709
+ writtenFiles.push(entry.path);
1710
+ } catch (err) {
1711
+ errors.push(`${entry.path}: ${toErrorMessage(err)}`);
1712
+ }
1713
+ }
1714
+ return { targetRoot: target, writtenFiles, deletedFiles, errors };
1715
+ }
1716
+ objectPath(hash) {
1717
+ if (!HASH_RE.test(hash)) throw new Error(`Invalid CAS object hash: ${hash}`);
1718
+ return path2.join(this.rootDir, "objects", hash.slice(0, 2), hash.slice(2));
1719
+ }
1720
+ manifestPath(hash) {
1721
+ if (!HASH_RE.test(hash)) throw new Error(`Invalid checkpoint manifest hash: ${hash}`);
1722
+ return path2.join(this.rootDir, "manifests", `${hash}.json`);
1723
+ }
1724
+ async putBlob(hash, content) {
1725
+ const target = this.objectPath(hash);
1726
+ await fsp4.mkdir(path2.dirname(target), { recursive: true });
1727
+ try {
1728
+ const existing = await fsp4.readFile(target);
1729
+ if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
1730
+ return;
1731
+ } catch (err) {
1732
+ if (err.code !== "ENOENT") throw err;
1733
+ }
1734
+ const temp = path2.join(
1735
+ path2.dirname(target),
1736
+ `.${path2.basename(target)}.${process.pid}.${randomUUID()}.tmp`
1737
+ );
1738
+ let handle;
1739
+ try {
1740
+ handle = await fsp4.open(temp, "wx", 384);
1741
+ await handle.writeFile(content);
1742
+ await handle.sync();
1743
+ await handle.close();
1744
+ handle = void 0;
1745
+ try {
1746
+ await fsp4.link(temp, target);
1747
+ } catch (err) {
1748
+ if (err.code !== "EEXIST") throw err;
1749
+ const existing = await fsp4.readFile(target);
1750
+ if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
1751
+ }
1752
+ } finally {
1753
+ await handle?.close().catch(() => void 0);
1754
+ await fsp4.unlink(temp).catch(() => void 0);
1755
+ }
1756
+ }
1757
+ async putManifest(hash, encoded) {
1758
+ const target = this.manifestPath(hash);
1759
+ const existing = await fsp4.readFile(target).catch(() => null);
1760
+ if (existing) {
1761
+ if (sha256(existing) !== hash) throw new Error(`Corrupt checkpoint manifest collision: ${hash}`);
1762
+ return;
1763
+ }
1764
+ await atomicWrite(target, encoded, { mode: 384 });
1765
+ }
1766
+ async readBlob(hash) {
1767
+ const content = await fsp4.readFile(this.objectPath(hash));
1768
+ if (sha256(content) !== hash) throw new Error(`CAS blob integrity check failed: ${hash}`);
1769
+ return content;
1770
+ }
1771
+ async loadManifest(hash) {
1772
+ const raw = await fsp4.readFile(this.manifestPath(hash));
1773
+ if (sha256(raw) !== hash) throw new Error(`Checkpoint manifest integrity check failed: ${hash}`);
1774
+ const parsed = JSON.parse(raw.toString("utf8"));
1775
+ if (parsed.version !== 1 || typeof parsed.baseHead !== "string" || parsed.coverage !== "git-head-plus-dirty" || !Array.isArray(parsed.entries) || !Array.isArray(parsed.unresolved)) {
1776
+ throw new Error(`Invalid workspace checkpoint manifest: ${hash}`);
1777
+ }
1778
+ const entriesValid = parsed.entries.every((entry) => {
1779
+ if (!entry || typeof entry !== "object") return false;
1780
+ const candidate = entry;
1781
+ if (typeof candidate.path !== "string" || !normalizeRelative(candidate.path)) return false;
1782
+ if (candidate.state === "absent") return true;
1783
+ if (candidate.state === "symlink") return typeof candidate.linkTarget === "string";
1784
+ return candidate.state === "file" && typeof candidate.blobHash === "string" && HASH_RE.test(candidate.blobHash) && typeof candidate.mode === "number" && Number.isInteger(candidate.mode) && candidate.mode >= 0 && candidate.mode <= 511;
1785
+ });
1786
+ const unresolvedValid = parsed.unresolved.every(
1787
+ (entry) => entry !== null && typeof entry === "object" && typeof entry.path === "string" && typeof entry.reason === "string"
1788
+ );
1789
+ if (!entriesValid || !unresolvedValid) {
1790
+ throw new Error(`Invalid workspace checkpoint manifest entries: ${hash}`);
1791
+ }
1792
+ return parsed;
1793
+ }
1794
+ async safeOutputPath(target, realTarget, relative6) {
1795
+ const normalized = normalizeRelative(relative6);
1796
+ if (!normalized) throw new Error("invalid relative path");
1797
+ const output = path2.resolve(target, ...normalized.split("/"));
1798
+ if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
1799
+ let probe = output;
1800
+ for (; ; ) {
1801
+ try {
1802
+ const real = await fsp4.realpath(probe);
1803
+ if (!isInside(realTarget, real)) throw new Error("path resolves through a symlink outside checkpoint target");
1804
+ return output;
1805
+ } catch (err) {
1806
+ if (err.code !== "ENOENT") throw err;
1807
+ const parent = path2.dirname(probe);
1808
+ if (parent === probe) throw err;
1809
+ probe = parent;
1810
+ }
1811
+ }
1812
+ }
1813
+ };
1814
+ function defaultRunGit(args, cwd) {
1815
+ return new Promise((resolve11) => {
1816
+ const stdoutChunks = [];
1817
+ const stderrChunks = [];
1818
+ let stdoutBytes = 0;
1819
+ let stderrBytes = 0;
1820
+ let stdoutTruncated = false;
1821
+ let stderrTruncated = false;
1822
+ const child = spawn("git", args, {
1823
+ cwd,
1824
+ env: buildChildEnv(),
1825
+ stdio: ["ignore", "pipe", "pipe"],
1826
+ signal: AbortSignal.timeout(3e4),
1827
+ windowsHide: true
1828
+ });
1829
+ child.stdout?.on("data", (chunk) => {
1830
+ const remaining = MAX_GIT_OUTPUT - stdoutBytes;
1831
+ if (remaining <= 0) {
1832
+ stdoutTruncated = true;
1833
+ return;
1834
+ }
1835
+ const kept = chunk.subarray(0, remaining);
1836
+ stdoutChunks.push(kept);
1837
+ stdoutBytes += kept.length;
1838
+ if (kept.length < chunk.length) stdoutTruncated = true;
1839
+ });
1840
+ child.stderr?.on("data", (chunk) => {
1841
+ const remaining = MAX_GIT_OUTPUT - stderrBytes;
1842
+ if (remaining <= 0) {
1843
+ stderrTruncated = true;
1844
+ return;
1845
+ }
1846
+ const kept = chunk.subarray(0, remaining);
1847
+ stderrChunks.push(kept);
1848
+ stderrBytes += kept.length;
1849
+ if (kept.length < chunk.length) stderrTruncated = true;
1850
+ });
1851
+ const result = (code, extraStderr) => ({
1852
+ code,
1853
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
1854
+ stderr: `${Buffer.concat(stderrChunks).toString("utf8")}${extraStderr ?? ""}`,
1855
+ stdoutTruncated,
1856
+ stderrTruncated
1857
+ });
1858
+ child.on("error", (err) => resolve11(result(1, err.message)));
1859
+ child.on("close", (code) => resolve11(result(code ?? 1)));
1860
+ });
1861
+ }
1862
+ var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
1863
+ var VALIDATION_CONCURRENCY = 8;
1864
+ var NOTICE_PATH_LIMIT = 20;
1865
+ function isInside2(root, target) {
1866
+ const relative6 = path2.relative(root, target);
1867
+ return relative6 === "" || !relative6.startsWith("..") && !path2.isAbsolute(relative6);
1868
+ }
1869
+ function errno(err) {
1870
+ return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
1871
+ }
1872
+ function latestObservations(events, projectRoot) {
1873
+ const latest = /* @__PURE__ */ new Map();
1874
+ for (const event of events) {
1875
+ 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)) {
1876
+ continue;
1877
+ }
1878
+ const normalized = path2.resolve(projectRoot, event.path);
1879
+ latest.set(normalized, {
1880
+ path: normalized,
1881
+ hash: event.hash.toLowerCase(),
1882
+ ts: event.ts
1883
+ });
1884
+ }
1885
+ return [...latest.values()];
1886
+ }
1887
+ async function validateOne(observation, lexicalRoot, realRoot) {
1888
+ const base = {
1889
+ path: observation.path,
1890
+ observedAt: observation.ts,
1891
+ expectedHash: observation.hash
1892
+ };
1893
+ if (!isInside2(lexicalRoot, observation.path)) {
1894
+ return {
1895
+ ...base,
1896
+ status: "outside_project",
1897
+ detail: "Recorded path is outside the active project root."
1898
+ };
1899
+ }
1900
+ let realFile;
1901
+ try {
1902
+ realFile = await fsp4.realpath(observation.path);
1903
+ } catch (err) {
1904
+ if (errno(err) === "ENOENT") return { ...base, status: "deleted" };
1905
+ return { ...base, status: "unreadable", detail: toErrorMessage(err) };
1906
+ }
1907
+ if (!isInside2(realRoot, realFile)) {
1908
+ return {
1909
+ ...base,
1910
+ status: "outside_project",
1911
+ detail: "Recorded path resolves through a symlink outside the active project root."
1912
+ };
1913
+ }
1914
+ try {
1915
+ const stat13 = await fsp4.stat(realFile);
1916
+ if (!stat13.isFile()) {
1917
+ return { ...base, status: "unreadable", detail: "Path is no longer a regular file." };
1918
+ }
1919
+ if (stat13.size > MAX_REVALIDATE_BYTES) {
1920
+ return {
1921
+ ...base,
1922
+ status: "unreadable",
1923
+ detail: `File now exceeds the ${MAX_REVALIDATE_BYTES}-byte validation limit.`
1924
+ };
1925
+ }
1926
+ const content = await fsp4.readFile(realFile, "utf8");
1927
+ const actualHash = createHash("sha256").update(content, "utf8").digest("hex");
1928
+ if (actualHash === observation.hash) return null;
1929
+ return { ...base, status: "modified", actualHash };
1930
+ } catch (err) {
1931
+ if (errno(err) === "ENOENT") return { ...base, status: "deleted" };
1932
+ return { ...base, status: "unreadable", detail: toErrorMessage(err) };
1933
+ }
1934
+ }
1935
+ async function validateResumeFileObservations(events, projectRoot) {
1936
+ const lexicalRoot = path2.resolve(projectRoot);
1937
+ const realRoot = await fsp4.realpath(lexicalRoot).catch(() => lexicalRoot);
1938
+ const observations = latestObservations(events, lexicalRoot);
1939
+ const results = await mapWithConcurrency(
1940
+ observations,
1941
+ VALIDATION_CONCURRENCY,
1942
+ (observation) => validateOne(observation, lexicalRoot, realRoot)
1943
+ );
1944
+ return {
1945
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
1946
+ checkedFileCount: observations.length,
1947
+ staleFiles: results.filter((entry) => entry !== null)
1948
+ };
1949
+ }
1950
+ function formatResumeValidationNotice(validation, projectRoot) {
1951
+ if (validation.staleFiles.length === 0) return null;
1952
+ const root = path2.resolve(projectRoot);
1953
+ const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
1954
+ const relative6 = path2.relative(root, entry.path);
1955
+ const display = isInside2(root, entry.path) ? relative6 || "." : entry.path;
1956
+ return `- ${JSON.stringify(display)} [${entry.status}]`;
1957
+ });
1958
+ const omitted = validation.staleFiles.length - shown.length;
1959
+ if (omitted > 0) shown.push(`- ... and ${omitted} more stale path(s)`);
1960
+ return [
1961
+ "[SESSION RESUME FILE VALIDATION]",
1962
+ `Journal hash validation found ${validation.staleFiles.length} previously observed file(s) whose current state cannot be trusted.`,
1963
+ "Prior tool results and reasoning based on these paths are stale. Re-read each relevant file before relying on it or editing it.",
1964
+ ...shown
1965
+ ].join("\n");
1966
+ }
1967
+
1216
1968
  // src/storage/session-id.ts
1217
1969
  function sanitizeModel(model) {
1218
1970
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
@@ -1260,30 +2012,52 @@ function extractToolCallEnds(events) {
1260
2012
  return out;
1261
2013
  }
1262
2014
 
1263
- // src/storage/storage-concurrency.ts
1264
- async function mapWithConcurrency(items, concurrency, mapper) {
1265
- if (items.length === 0) return [];
1266
- const limit = Math.max(1, Math.floor(concurrency));
1267
- const results = new Array(items.length);
1268
- let next = 0;
1269
- async function worker() {
1270
- while (true) {
1271
- const index = next;
1272
- next++;
1273
- if (index >= items.length) return;
1274
- results[index] = await mapper(items[index], index);
2015
+ // src/storage/session-store.ts
2016
+ function isReplayableMessage(value) {
2017
+ if (!value || typeof value !== "object") return false;
2018
+ const candidate = value;
2019
+ return (candidate.role === "user" || candidate.role === "assistant" || candidate.role === "system") && (typeof candidate.content === "string" || Array.isArray(candidate.content));
2020
+ }
2021
+ function applyContextSnapshot(target, openToolUses, snapshot) {
2022
+ if (!Array.isArray(snapshot) || !snapshot.every(isReplayableMessage)) return false;
2023
+ target.length = 0;
2024
+ openToolUses.clear();
2025
+ for (const raw of snapshot) {
2026
+ const { _estTokens: _ignored, ...message } = raw;
2027
+ target.push(message);
2028
+ if (!Array.isArray(message.content)) continue;
2029
+ for (const block of message.content) {
2030
+ if (block.type === "tool_use") openToolUses.add(block.id);
2031
+ else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
1275
2032
  }
1276
2033
  }
1277
- const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
1278
- await Promise.all(workers);
1279
- return results;
2034
+ return true;
2035
+ }
2036
+ function inheritsIntoFork(event) {
2037
+ switch (event.type) {
2038
+ case "session_start":
2039
+ case "session_resumed":
2040
+ case "session_forked":
2041
+ case "session_end":
2042
+ case "in_flight_start":
2043
+ case "in_flight_end":
2044
+ case "rewound":
2045
+ return false;
2046
+ // Parent snapshots describe mutations owned by the parent journal. A
2047
+ // child that shares the current workspace must not inherit authority to
2048
+ // rewind those historical side effects.
2049
+ case "file_snapshot":
2050
+ return false;
2051
+ default:
2052
+ return true;
2053
+ }
1280
2054
  }
1281
-
1282
- // src/storage/session-store.ts
1283
2055
  var DefaultSessionStore = class _DefaultSessionStore {
1284
2056
  dir;
1285
2057
  events;
1286
2058
  secretScrubber;
2059
+ projectRoot;
2060
+ checkpointCas;
1287
2061
  isSessionInUse;
1288
2062
  /**
1289
2063
  * In-memory cache for load() results, keyed by session ID. The cache is
@@ -1302,6 +2076,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
1302
2076
  static LIST_SCAN_CONCURRENCY = 32;
1303
2077
  constructor(opts) {
1304
2078
  this.dir = opts.dir;
2079
+ this.projectRoot = opts.projectRoot ? path2.resolve(opts.projectRoot) : void 0;
2080
+ this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
2081
+ rootDir: path2.join(this.dir, "_cas"),
2082
+ projectRoot: this.projectRoot
2083
+ }) : void 0;
1305
2084
  this.events = opts.events;
1306
2085
  this.secretScrubber = opts.secretScrubber;
1307
2086
  this.isSessionInUse = opts.isSessionInUse;
@@ -1387,7 +2166,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1387
2166
  const t0 = Date.now();
1388
2167
  let handle;
1389
2168
  try {
1390
- handle = await fsp2.open(file, "a", 384);
2169
+ handle = await fsp4.open(file, "a", 384);
1391
2170
  } catch (err) {
1392
2171
  this.emitError(id, file, "create", toErrorMessage(err), false);
1393
2172
  throw new Error(
@@ -1400,6 +2179,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1400
2179
  dir: shardDir,
1401
2180
  filePath: file,
1402
2181
  secretScrubber: this.secretScrubber,
2182
+ checkpointCas: this.checkpointCas,
1403
2183
  onClose: (s) => this.appendToIndex(s)
1404
2184
  });
1405
2185
  this.emitWrite(id, file, "create", "success", Date.now() - t0);
@@ -1415,13 +2195,99 @@ var DefaultSessionStore = class _DefaultSessionStore {
1415
2195
  throw err;
1416
2196
  }
1417
2197
  }
2198
+ async fork(id, opts = {}) {
2199
+ const parent = await this.load(id);
2200
+ let boundary = parent.events.length - 1;
2201
+ let targetCheckpoint;
2202
+ if (opts.checkpointPromptIndex !== void 0) {
2203
+ boundary = -1;
2204
+ for (let i = 0; i < parent.events.length; i++) {
2205
+ const event = parent.events[i];
2206
+ if (event?.type === "checkpoint" && event.promptIndex === opts.checkpointPromptIndex) {
2207
+ boundary = i;
2208
+ targetCheckpoint = event;
2209
+ }
2210
+ }
2211
+ if (boundary === -1) {
2212
+ throw new Error(
2213
+ `Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`
2214
+ );
2215
+ }
2216
+ }
2217
+ const parentPrefix = parent.events.slice(0, boundary + 1);
2218
+ const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;
2219
+ const checkpointHash = createHash("sha256").update(parentPrefix.map((event) => JSON.stringify(event)).join("\n") + "\n", "utf8").digest("hex");
2220
+ const inherited = parentPrefix.filter(inheritsIntoFork);
2221
+ const writer = await this.create({
2222
+ id: "",
2223
+ title: parent.metadata.title,
2224
+ model: parent.metadata.model,
2225
+ provider: parent.metadata.provider
2226
+ });
2227
+ try {
2228
+ await writer.append({
2229
+ type: "session_forked",
2230
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2231
+ parentSessionId: id,
2232
+ parentCheckpointPromptIndex: opts.checkpointPromptIndex,
2233
+ parentCheckpointHash: checkpointHash,
2234
+ workspace: "shared-current",
2235
+ workspaceCheckpointHash: workspaceCheckpoint?.manifestHash
2236
+ });
2237
+ const batchSize = 250;
2238
+ for (let offset = 0; offset < inherited.length; offset += batchSize) {
2239
+ await writer.appendBatch(inherited.slice(offset, offset + batchSize));
2240
+ }
2241
+ await writer.flush();
2242
+ await writer.close();
2243
+ const data = await this.load(writer.id);
2244
+ return {
2245
+ id: writer.id,
2246
+ data,
2247
+ parentSessionId: id,
2248
+ checkpointPromptIndex: opts.checkpointPromptIndex,
2249
+ checkpointHash,
2250
+ workspace: "shared-current",
2251
+ workspaceCheckpoint
2252
+ };
2253
+ } catch (err) {
2254
+ await writer.close().catch(() => void 0);
2255
+ await this.delete(writer.id).catch(() => void 0);
2256
+ throw err;
2257
+ }
2258
+ }
2259
+ async materializeWorkspaceCheckpoint(checkpoint, targetRoot) {
2260
+ if (!this.checkpointCas) {
2261
+ throw new Error("Workspace checkpoint materialization requires a projectRoot-aware session store");
2262
+ }
2263
+ return this.checkpointCas.materialize(checkpoint, targetRoot);
2264
+ }
1418
2265
  async resume(id) {
1419
2266
  const file = this.sessionPath(id, ".jsonl");
1420
2267
  const t0 = Date.now();
1421
2268
  const data = await this.load(id);
2269
+ let resumedData = data;
2270
+ if (this.projectRoot) {
2271
+ try {
2272
+ const resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);
2273
+ const notice = formatResumeValidationNotice(resumeValidation, this.projectRoot);
2274
+ resumedData = {
2275
+ ...data,
2276
+ resumeValidation,
2277
+ ...notice ? {
2278
+ messages: [
2279
+ ...data.messages,
2280
+ { role: "system", content: notice, ts: resumeValidation.checkedAt }
2281
+ ]
2282
+ } : {}
2283
+ };
2284
+ } catch (err) {
2285
+ this.emitError(id, file, "resume_validation", toErrorMessage(err), true);
2286
+ }
2287
+ }
1422
2288
  let handle;
1423
2289
  try {
1424
- handle = await fsp2.open(file, "a", 384);
2290
+ handle = await fsp4.open(file, "a", 384);
1425
2291
  } catch (err) {
1426
2292
  this.emitError(id, file, "resume", toErrorMessage(err), false);
1427
2293
  throw new Error(
@@ -1448,11 +2314,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
1448
2314
  dir: path2.dirname(file),
1449
2315
  filePath: file,
1450
2316
  secretScrubber: this.secretScrubber,
2317
+ checkpointCas: this.checkpointCas,
1451
2318
  onClose: (s) => this.appendToIndex(s)
1452
2319
  }
1453
2320
  );
1454
2321
  this.emitWrite(id, file, "resume", "success", Date.now() - t0);
1455
- return { writer, data };
2322
+ return { writer, data: resumedData };
1456
2323
  } catch (err) {
1457
2324
  await handle.close().catch((e) => console.warn(JSON.stringify({
1458
2325
  level: "warn",
@@ -1491,10 +2358,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
1491
2358
  let errorMsg;
1492
2359
  let cacheHit = false;
1493
2360
  try {
1494
- const s = await fsp2.stat(file);
1495
- const stat11 = { mtimeMs: s.mtimeMs, size: s.size };
2361
+ const s = await fsp4.stat(file);
2362
+ const stat13 = { mtimeMs: s.mtimeMs, size: s.size };
1496
2363
  const cached = this._loadCache.get(id);
1497
- if (cached && cached.mtimeMs === stat11.mtimeMs && cached.size === stat11.size) {
2364
+ if (cached && cached.mtimeMs === stat13.mtimeMs && cached.size === stat13.size) {
1498
2365
  cacheHit = true;
1499
2366
  this._loadCache.delete(id);
1500
2367
  this._loadCache.set(id, cached);
@@ -1507,6 +2374,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1507
2374
  let sessionModel;
1508
2375
  let sessionProvider;
1509
2376
  let sessionPendingToolUses;
2377
+ let sessionForkedEvent;
1510
2378
  const messages = mode.full ? [] : void 0;
1511
2379
  const openToolUses = mode.full ? /* @__PURE__ */ new Set() : void 0;
1512
2380
  let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
@@ -1529,8 +2397,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
1529
2397
  sessionEndEvent = ev;
1530
2398
  sessionPendingToolUses = ev.pendingToolUses;
1531
2399
  }
2400
+ if (ev.type === "session_forked" && !sessionForkedEvent) {
2401
+ sessionForkedEvent = ev;
2402
+ }
1532
2403
  if (mode.full && messages !== void 0 && openToolUses !== void 0) {
1533
- if (ev.type === "user_input") {
2404
+ if (ev.type === "context_snapshot") {
2405
+ if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
2406
+ this.events?.emit("session.damaged", {
2407
+ sessionId: id,
2408
+ detail: "Ignored malformed context_snapshot event"
2409
+ });
2410
+ }
2411
+ } else if (ev.type === "user_input") {
1534
2412
  openToolUses.clear();
1535
2413
  messages.push({ role: "user", content: ev.content, ts: ev.ts });
1536
2414
  } else if (ev.type === "llm_response") {
@@ -1606,7 +2484,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
1606
2484
  endedAt: sessionEndEvent?.ts,
1607
2485
  model: sessionModel,
1608
2486
  provider: sessionProvider,
1609
- pendingToolUses: sessionPendingToolUses
2487
+ pendingToolUses: sessionPendingToolUses,
2488
+ forkedFrom: sessionForkedEvent ? {
2489
+ sessionId: sessionForkedEvent.parentSessionId,
2490
+ checkpointPromptIndex: sessionForkedEvent.parentCheckpointPromptIndex,
2491
+ checkpointHash: sessionForkedEvent.parentCheckpointHash,
2492
+ workspace: sessionForkedEvent.workspace,
2493
+ workspaceCheckpointHash: sessionForkedEvent.workspaceCheckpointHash
2494
+ } : void 0
1610
2495
  };
1611
2496
  const toolCallEnds = extractToolCallEnds(events);
1612
2497
  const data = { metadata: meta, events, messages: finalMessages, usage, toolCallEnds };
@@ -1617,7 +2502,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1617
2502
  this._loadCache.delete(oldest);
1618
2503
  }
1619
2504
  }
1620
- this._loadCache.set(id, { mtimeMs: stat11.mtimeMs, size: stat11.size, data });
2505
+ this._loadCache.set(id, { mtimeMs: stat13.mtimeMs, size: stat13.size, data });
1621
2506
  }
1622
2507
  return data;
1623
2508
  } catch (err) {
@@ -1659,17 +2544,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
1659
2544
  const limit = opts?.limit;
1660
2545
  const signal = opts?.signal;
1661
2546
  const out = [];
1662
- let stat11;
2547
+ let stat13;
1663
2548
  try {
1664
- stat11 = await fsp2.stat(file);
2549
+ stat13 = await fsp4.stat(file);
1665
2550
  } catch (err) {
1666
2551
  if (err.code === "ENOENT") return [];
1667
2552
  throw err;
1668
2553
  }
1669
- if (stat11.size === 0) return [];
2554
+ if (stat13.size === 0) return [];
1670
2555
  let fh;
1671
2556
  try {
1672
- fh = await fsp2.open(file, "r");
2557
+ fh = await fsp4.open(file, "r");
1673
2558
  const CHUNK = 64 * 1024;
1674
2559
  const buf = Buffer.alloc(CHUNK);
1675
2560
  let leftover = "";
@@ -1782,7 +2667,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1782
2667
  let shouldCompact = false;
1783
2668
  await withFileLock(this.indexFile, async () => {
1784
2669
  const line = JSON.stringify(summary) + "\n";
1785
- await fsp2.appendFile(this.indexFile, line, "utf8");
2670
+ await fsp4.appendFile(this.indexFile, line, "utf8");
1786
2671
  this._indexCache = null;
1787
2672
  this.invalidateShardManifestBySessionId(summary.id);
1788
2673
  this.indexAppendCount++;
@@ -1803,7 +2688,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1803
2688
  await ensureDir(this.dir);
1804
2689
  await withFileLock(this.indexFile, async () => {
1805
2690
  const line = JSON.stringify({ action: "delete", id }) + "\n";
1806
- await fsp2.appendFile(this.indexFile, line, "utf8");
2691
+ await fsp4.appendFile(this.indexFile, line, "utf8");
1807
2692
  this._indexCache = null;
1808
2693
  this.invalidateShardManifestBySessionId(id);
1809
2694
  this.indexAppendCount++;
@@ -1849,20 +2734,20 @@ var DefaultSessionStore = class _DefaultSessionStore {
1849
2734
  * Returns empty array when the index doesn't exist or is corrupt.
1850
2735
  */
1851
2736
  async readIndex() {
1852
- let stat11;
2737
+ let stat13;
1853
2738
  try {
1854
- const s = await fsp2.stat(this.indexFile);
1855
- stat11 = { mtimeMs: s.mtimeMs, size: s.size };
2739
+ const s = await fsp4.stat(this.indexFile);
2740
+ stat13 = { mtimeMs: s.mtimeMs, size: s.size };
1856
2741
  } catch {
1857
2742
  this._indexCache = null;
1858
2743
  return [];
1859
2744
  }
1860
- if (this._indexCache !== null && this._indexCache.mtimeMs === stat11.mtimeMs && this._indexCache.size === stat11.size) {
2745
+ if (this._indexCache !== null && this._indexCache.mtimeMs === stat13.mtimeMs && this._indexCache.size === stat13.size) {
1861
2746
  return [...this._indexCache.summaries];
1862
2747
  }
1863
2748
  let raw;
1864
2749
  try {
1865
- raw = await fsp2.readFile(this.indexFile, "utf8");
2750
+ raw = await fsp4.readFile(this.indexFile, "utf8");
1866
2751
  } catch {
1867
2752
  this._indexCache = null;
1868
2753
  return [];
@@ -1890,7 +2775,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1890
2775
  if (a.startedAt > b.startedAt) return -1;
1891
2776
  return a.id.localeCompare(b.id);
1892
2777
  });
1893
- this._indexCache = { ...stat11, summaries };
2778
+ this._indexCache = { ...stat13, summaries };
1894
2779
  return [...summaries];
1895
2780
  }
1896
2781
  /**
@@ -1933,7 +2818,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1933
2818
  async collectShardKeys() {
1934
2819
  let entries;
1935
2820
  try {
1936
- entries = await fsp2.readdir(this.dir, { withFileTypes: true });
2821
+ entries = await fsp4.readdir(this.dir, { withFileTypes: true });
1937
2822
  } catch {
1938
2823
  return [""];
1939
2824
  }
@@ -1950,7 +2835,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1950
2835
  if (cached) return cached;
1951
2836
  const manifestPath = this.shardManifestPath(shardKey);
1952
2837
  try {
1953
- const raw = await fsp2.readFile(manifestPath, "utf8");
2838
+ const raw = await fsp4.readFile(manifestPath, "utf8");
1954
2839
  const parsed = JSON.parse(raw);
1955
2840
  const entry2 = {
1956
2841
  summaries: Array.isArray(parsed.summaries) ? parsed.summaries : [],
@@ -1988,7 +2873,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1988
2873
  async collectSessionFiles(dir, prefix = "", depth = 0) {
1989
2874
  let entries;
1990
2875
  try {
1991
- entries = await fsp2.readdir(dir, { withFileTypes: true });
2876
+ entries = await fsp4.readdir(dir, { withFileTypes: true });
1992
2877
  } catch {
1993
2878
  return [];
1994
2879
  }
@@ -2022,7 +2907,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2022
2907
  async collectSessionIds(dir, prefix = "", depth = 0) {
2023
2908
  let entries;
2024
2909
  try {
2025
- entries = await fsp2.readdir(dir, { withFileTypes: true });
2910
+ entries = await fsp4.readdir(dir, { withFileTypes: true });
2026
2911
  } catch {
2027
2912
  return [];
2028
2913
  }
@@ -2057,8 +2942,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
2057
2942
  if (fromManifest) return fromManifest;
2058
2943
  try {
2059
2944
  const full = this.sessionPath(id, ".jsonl");
2060
- const stat11 = await fsp2.stat(full);
2061
- const summary = await this.summarize(id, stat11.mtime.toISOString());
2945
+ const stat13 = await fsp4.stat(full);
2946
+ const summary = await this.summarize(id, stat13.mtime.toISOString());
2062
2947
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
2063
2948
  const msg = toErrorMessage(err);
2064
2949
  this.emitError(id, manifest, "summary_fallback", msg, true);
@@ -2091,7 +2976,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2091
2976
  async readSummaryManifest(id, startTime = Date.now()) {
2092
2977
  const manifest = this.sessionPath(id, ".summary.json");
2093
2978
  try {
2094
- const raw = await fsp2.readFile(manifest, "utf8");
2979
+ const raw = await fsp4.readFile(manifest, "utf8");
2095
2980
  this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
2096
2981
  return JSON.parse(raw);
2097
2982
  } catch {
@@ -2101,18 +2986,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
2101
2986
  async summaryHeaderFor(ref) {
2102
2987
  let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
2103
2988
  try {
2104
- const stat11 = await fsp2.stat(ref.filePath);
2105
- if (!stat11.isFile()) {
2989
+ const stat13 = await fsp4.stat(ref.filePath);
2990
+ if (!stat13.isFile()) {
2106
2991
  return {
2107
2992
  id: ref.id,
2108
2993
  title: "(damaged)",
2109
- startedAt: stat11.mtime.toISOString(),
2994
+ startedAt: stat13.mtime.toISOString(),
2110
2995
  model: "unknown",
2111
2996
  provider: "unknown",
2112
2997
  tokenTotal: 0
2113
2998
  };
2114
2999
  }
2115
- mtime = stat11.mtime.toISOString();
3000
+ mtime = stat13.mtime.toISOString();
2116
3001
  } catch {
2117
3002
  return null;
2118
3003
  }
@@ -2164,11 +3049,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
2164
3049
  const base = path2.basename(id);
2165
3050
  const sessDir = path2.join(shardDir, base);
2166
3051
  const deletions = [
2167
- fsp2.unlink(jsonlPath),
2168
- fsp2.unlink(summaryPath),
2169
- fsp2.unlink(sessionScopedPath(this.dir, id, ".plan.json")),
2170
- fsp2.unlink(sessionScopedPath(this.dir, id, ".tasks.json")),
2171
- fsp2.unlink(sessionScopedPath(this.dir, id, ".todos.json"))
3052
+ fsp4.unlink(jsonlPath),
3053
+ fsp4.unlink(summaryPath),
3054
+ fsp4.unlink(sessionScopedPath(this.dir, id, ".plan.json")),
3055
+ fsp4.unlink(sessionScopedPath(this.dir, id, ".tasks.json")),
3056
+ fsp4.unlink(sessionScopedPath(this.dir, id, ".todos.json"))
2172
3057
  ];
2173
3058
  const results = await Promise.allSettled(deletions);
2174
3059
  for (const r of results) {
@@ -2185,7 +3070,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2185
3070
  }
2186
3071
  }
2187
3072
  }
2188
- await fsp2.rm(sessDir, { recursive: true, force: true }).catch((err) => {
3073
+ await fsp4.rm(sessDir, { recursive: true, force: true }).catch((err) => {
2189
3074
  console.warn(JSON.stringify({
2190
3075
  level: "warn",
2191
3076
  event: "session_store.rmdir_failed",
@@ -2203,7 +3088,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2203
3088
  */
2204
3089
  async readActiveSessionId() {
2205
3090
  try {
2206
- const raw = await fsp2.readFile(path2.join(this.dir, "active.json"), "utf8");
3091
+ const raw = await fsp4.readFile(path2.join(this.dir, "active.json"), "utf8");
2207
3092
  const active = JSON.parse(raw);
2208
3093
  return active.sessionId ?? null;
2209
3094
  } catch {
@@ -2230,7 +3115,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2230
3115
  const manifest = this.sessionPath(id, ".summary.json");
2231
3116
  const jsonlPath = this.sessionPath(id, ".jsonl");
2232
3117
  try {
2233
- await fsp2.stat(jsonlPath);
3118
+ await fsp4.stat(jsonlPath);
2234
3119
  } catch (err) {
2235
3120
  const code = err?.code;
2236
3121
  if (code === "ENOENT") {
@@ -2268,8 +3153,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
2268
3153
  const pruneFile = async (dir, name, prefix) => {
2269
3154
  const jsonlPath = path2.join(dir, name);
2270
3155
  try {
2271
- const stat11 = await fsp2.stat(jsonlPath);
2272
- if (stat11.mtimeMs >= cutoff) return;
3156
+ const stat13 = await fsp4.stat(jsonlPath);
3157
+ if (stat13.mtimeMs >= cutoff) return;
2273
3158
  } catch {
2274
3159
  return;
2275
3160
  }
@@ -2279,7 +3164,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2279
3164
  await this.deleteSession(id);
2280
3165
  deleted++;
2281
3166
  };
2282
- const entries = await fsp2.readdir(this.dir, { withFileTypes: true }).catch(() => []);
3167
+ const entries = await fsp4.readdir(this.dir, { withFileTypes: true }).catch(() => []);
2283
3168
  for (const entry of entries) {
2284
3169
  if (entry.isFile()) {
2285
3170
  if (isPrunableJsonl(entry.name)) await pruneFile(this.dir, entry.name, "");
@@ -2287,7 +3172,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2287
3172
  }
2288
3173
  if (!entry.isDirectory()) continue;
2289
3174
  const dateDir = path2.join(this.dir, entry.name);
2290
- const files = await fsp2.readdir(dateDir, { withFileTypes: true }).catch(() => []);
3175
+ const files = await fsp4.readdir(dateDir, { withFileTypes: true }).catch(() => []);
2291
3176
  for (const file of files) {
2292
3177
  if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
2293
3178
  await pruneFile(dateDir, file.name, entry.name);
@@ -2300,9 +3185,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
2300
3185
  if (!entry.isDirectory()) continue;
2301
3186
  const dateDir = path2.join(this.dir, entry.name);
2302
3187
  try {
2303
- const remaining = await fsp2.readdir(dateDir);
3188
+ const remaining = await fsp4.readdir(dateDir);
2304
3189
  if (remaining.length === 0) {
2305
- await fsp2.rmdir(dateDir).catch(() => void 0);
3190
+ await fsp4.rmdir(dateDir).catch(() => void 0);
2306
3191
  }
2307
3192
  } catch {
2308
3193
  }
@@ -2321,8 +3206,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
2321
3206
  provider: "unknown"
2322
3207
  })}
2323
3208
  `;
2324
- await fsp2.writeFile(file, record, "utf8");
2325
- await fsp2.unlink(meta).catch(() => void 0);
3209
+ await fsp4.writeFile(file, record, "utf8");
3210
+ await fsp4.unlink(meta).catch(() => void 0);
2326
3211
  }
2327
3212
  async summarize(id, mtime) {
2328
3213
  try {
@@ -2472,7 +3357,7 @@ var QueueStore = class {
2472
3357
  const t0 = Date.now();
2473
3358
  let raw;
2474
3359
  try {
2475
- raw = await fsp2.readFile(this.file, "utf8");
3360
+ raw = await fsp4.readFile(this.file, "utf8");
2476
3361
  } catch (err) {
2477
3362
  const code = err.code;
2478
3363
  if (code === "ENOENT") {
@@ -2553,7 +3438,7 @@ var QueueStore = class {
2553
3438
  async clear() {
2554
3439
  const t0 = Date.now();
2555
3440
  try {
2556
- await fsp2.unlink(this.file);
3441
+ await fsp4.unlink(this.file);
2557
3442
  this.events?.emit("storage.write", {
2558
3443
  sessionId: this.traceId ?? "~boot~",
2559
3444
  store: "queue",
@@ -2610,7 +3495,7 @@ var DefaultAttachmentStore = class {
2610
3495
  let spooledPath;
2611
3496
  let data = input.data;
2612
3497
  if (this.spoolDir && bytes >= this.spoolThreshold) {
2613
- await fsp2.mkdir(this.spoolDir, { recursive: true });
3498
+ await fsp4.mkdir(this.spoolDir, { recursive: true });
2614
3499
  spooledPath = path2.join(this.spoolDir, `${id}.bin`);
2615
3500
  await atomicWrite(spooledPath, input.data, {
2616
3501
  encoding: input.kind === "image" ? "base64" : "utf8"
@@ -2673,7 +3558,7 @@ var DefaultAttachmentStore = class {
2673
3558
  for (const att of this.items.values()) {
2674
3559
  if (att.path) toDelete.push(att.path);
2675
3560
  }
2676
- await Promise.all(toDelete.map((p) => fsp2.unlink(p).catch(() => void 0)));
3561
+ await Promise.all(toDelete.map((p) => fsp4.unlink(p).catch(() => void 0)));
2677
3562
  }
2678
3563
  this.items.clear();
2679
3564
  this.refs.length = 0;
@@ -2681,7 +3566,7 @@ var DefaultAttachmentStore = class {
2681
3566
  }
2682
3567
  async toBlock(att) {
2683
3568
  if (att.kind === "image") {
2684
- const data = att.data ?? (att.path ? await fsp2.readFile(att.path, { encoding: "base64" }) : "");
3569
+ const data = att.data ?? (att.path ? await fsp4.readFile(att.path, { encoding: "base64" }) : "");
2685
3570
  return {
2686
3571
  type: "image",
2687
3572
  source: {
@@ -2691,7 +3576,7 @@ var DefaultAttachmentStore = class {
2691
3576
  }
2692
3577
  };
2693
3578
  }
2694
- const raw = att.data ?? (att.path ? await fsp2.readFile(att.path, "utf8") : "");
3579
+ const raw = att.data ?? (att.path ? await fsp4.readFile(att.path, "utf8") : "");
2695
3580
  const label = att.meta.filename ? `<file path="${att.meta.filename}">` : "<pasted>";
2696
3581
  const close = att.meta.filename ? "</file>" : "</pasted>";
2697
3582
  return { type: "text", text: `${label}
@@ -2894,8 +3779,8 @@ var FileMemoryBackend = class {
2894
3779
  }
2895
3780
  async getMtime(file) {
2896
3781
  try {
2897
- const stat11 = await fsp2.stat(file);
2898
- return stat11.mtimeMs;
3782
+ const stat13 = await fsp4.stat(file);
3783
+ return stat13.mtimeMs;
2899
3784
  } catch {
2900
3785
  return 0;
2901
3786
  }
@@ -2944,7 +3829,7 @@ var FileMemoryBackend = class {
2944
3829
  await ensureDir(path2.dirname(file));
2945
3830
  let existing = "";
2946
3831
  try {
2947
- existing = await fsp2.readFile(file, "utf8");
3832
+ existing = await fsp4.readFile(file, "utf8");
2948
3833
  } catch {
2949
3834
  }
2950
3835
  const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;
@@ -2962,7 +3847,7 @@ ${line}`;
2962
3847
  return withFileLock(file, async () => {
2963
3848
  let existing;
2964
3849
  try {
2965
- existing = await fsp2.readFile(file, "utf8");
3850
+ existing = await fsp4.readFile(file, "utf8");
2966
3851
  } catch {
2967
3852
  return 0;
2968
3853
  }
@@ -2999,7 +3884,7 @@ ${line}`;
2999
3884
  async readAll(scope, filePath) {
3000
3885
  const file = this.resolveFile(filePath, scope);
3001
3886
  try {
3002
- return await fsp2.readFile(file, "utf8");
3887
+ return await fsp4.readFile(file, "utf8");
3003
3888
  } catch {
3004
3889
  return "";
3005
3890
  }
@@ -3023,7 +3908,7 @@ ${line}`;
3023
3908
  const file = this.resolveFile(filePath, scope);
3024
3909
  let existing;
3025
3910
  try {
3026
- existing = await fsp2.readFile(file, "utf8");
3911
+ existing = await fsp4.readFile(file, "utf8");
3027
3912
  } catch {
3028
3913
  return 0;
3029
3914
  }
@@ -3043,7 +3928,7 @@ ${line}`;
3043
3928
  const next = lines.join("\n");
3044
3929
  const backup = `${file}.bak.${Date.now()}`;
3045
3930
  try {
3046
- await fsp2.copyFile(file, backup);
3931
+ await fsp4.copyFile(file, backup);
3047
3932
  await pruneConsolidateBackups(file);
3048
3933
  } catch {
3049
3934
  }
@@ -3060,11 +3945,11 @@ async function pruneConsolidateBackups(file) {
3060
3945
  const dir = path2.dirname(file);
3061
3946
  const base = path2.basename(file);
3062
3947
  const prefix = `${base}.bak.`;
3063
- const backups = (await fsp2.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
3948
+ const backups = (await fsp4.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
3064
3949
  await Promise.all(
3065
3950
  backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {
3066
3951
  try {
3067
- await fsp2.unlink(path2.join(dir, name));
3952
+ await fsp4.unlink(path2.join(dir, name));
3068
3953
  } catch {
3069
3954
  }
3070
3955
  })
@@ -3631,8 +4516,8 @@ ${body.trim()}`);
3631
4516
  if (!this.persistBackup || scope === "project-agents") return;
3632
4517
  try {
3633
4518
  const content = await this.backend.readAll(scope, this.files[scope]);
3634
- const { writeFile: writeFile8, mkdir: mkdir7 } = await import('fs/promises');
3635
- await mkdir7(this.backupDir, { recursive: true });
4519
+ const { writeFile: writeFile8, mkdir: mkdir8 } = await import('fs/promises');
4520
+ await mkdir8(this.backupDir, { recursive: true });
3636
4521
  await writeFile8(`${this.backupDir}/${scope}.md`, content, "utf8");
3637
4522
  } catch {
3638
4523
  }
@@ -3814,7 +4699,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
3814
4699
  this.loadedScope = scope;
3815
4700
  this.loaded = true;
3816
4701
  try {
3817
- await fsp2.unlink(this.graphFile);
4702
+ await fsp4.unlink(this.graphFile);
3818
4703
  } catch {
3819
4704
  }
3820
4705
  }
@@ -3854,7 +4739,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
3854
4739
  async loadGraph(scope) {
3855
4740
  if (this.loaded && this.loadedScope === scope) return;
3856
4741
  try {
3857
- const raw = await fsp2.readFile(this.graphFile, "utf8");
4742
+ const raw = await fsp4.readFile(this.graphFile, "utf8");
3858
4743
  const data = JSON.parse(raw);
3859
4744
  this.nodes = new Map(data.nodes);
3860
4745
  this.edges = data.edges;
@@ -3876,10 +4761,10 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
3876
4761
  edges: this.edges
3877
4762
  };
3878
4763
  const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
3879
- await fsp2.mkdir(dir, { recursive: true });
4764
+ await fsp4.mkdir(dir, { recursive: true });
3880
4765
  const tmp = `${this.graphFile}.tmp`;
3881
- await fsp2.writeFile(tmp, JSON.stringify(data));
3882
- await fsp2.rename(tmp, this.graphFile);
4766
+ await fsp4.writeFile(tmp, JSON.stringify(data));
4767
+ await fsp4.rename(tmp, this.graphFile);
3883
4768
  } catch {
3884
4769
  }
3885
4770
  }
@@ -4270,7 +5155,7 @@ function isSecretField(name) {
4270
5155
  async function readProviderSnapshot(configPath, vault, warn) {
4271
5156
  let raw;
4272
5157
  try {
4273
- raw = await fsp2.readFile(configPath, "utf8");
5158
+ raw = await fsp4.readFile(configPath, "utf8");
4274
5159
  } catch (err) {
4275
5160
  if (err.code !== "ENOENT") {
4276
5161
  warn?.(`Could not read ${configPath}: ${err.message}`);
@@ -4423,7 +5308,14 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
4423
5308
  descriptionMode: Object.freeze({}),
4424
5309
  disabledTools: Object.freeze([]),
4425
5310
  autoExtendLimit: true,
4426
- restrictToProjectRoot: false
5311
+ restrictToProjectRoot: false,
5312
+ loopDetection: Object.freeze({
5313
+ mode: "steer-then-cut",
5314
+ steerThreshold: 3,
5315
+ cutThreshold: 5,
5316
+ windowSize: 12,
5317
+ callRepeatThreshold: 4
5318
+ })
4427
5319
  });
4428
5320
  var DEFAULT_CONTEXT_CONFIG = Object.freeze({
4429
5321
  preserveK: 10,
@@ -4475,7 +5367,8 @@ var BEHAVIOR_DEFAULTS = {
4475
5367
  descriptionMode: DEFAULT_TOOLS_CONFIG.descriptionMode,
4476
5368
  disabledTools: DEFAULT_TOOLS_CONFIG.disabledTools,
4477
5369
  autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
4478
- restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot
5370
+ restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot,
5371
+ loopDetection: DEFAULT_TOOLS_CONFIG.loopDetection
4479
5372
  },
4480
5373
  log: { level: "info" },
4481
5374
  features: {
@@ -4933,7 +5826,7 @@ var DefaultConfigLoader = class {
4933
5826
  await withFileLock(fp, async () => {
4934
5827
  let parsed;
4935
5828
  try {
4936
- const raw = await fsp2.readFile(fp, "utf8");
5829
+ const raw = await fsp4.readFile(fp, "utf8");
4937
5830
  const result = safeParse(raw);
4938
5831
  if (!result.ok || !isPlainRecord(result.value)) {
4939
5832
  return;
@@ -5052,7 +5945,7 @@ var DefaultConfigLoader = class {
5052
5945
  const fp = this.paths.syncConfig;
5053
5946
  const t0 = Date.now();
5054
5947
  try {
5055
- const raw = await fsp2.readFile(fp, "utf8");
5948
+ const raw = await fsp4.readFile(fp, "utf8");
5056
5949
  const parsed = safeParse(raw);
5057
5950
  if (!parsed.ok || !parsed.value) {
5058
5951
  this.events?.emit("storage.read", {
@@ -5118,8 +6011,8 @@ var DefaultConfigLoader = class {
5118
6011
  const t0 = Date.now();
5119
6012
  let mtimeMs = null;
5120
6013
  try {
5121
- const stat11 = await fsp2.stat(file);
5122
- mtimeMs = stat11.mtimeMs;
6014
+ const stat13 = await fsp4.stat(file);
6015
+ mtimeMs = stat13.mtimeMs;
5123
6016
  const cached = this.jsonCache.get(file);
5124
6017
  if (cached && cached.mtimeMs === mtimeMs) {
5125
6018
  return structuredClone(cached.value);
@@ -5152,7 +6045,7 @@ var DefaultConfigLoader = class {
5152
6045
  }
5153
6046
  let raw;
5154
6047
  try {
5155
- raw = await fsp2.readFile(file, "utf8");
6048
+ raw = await fsp4.readFile(file, "utf8");
5156
6049
  } catch (err) {
5157
6050
  if (err.code !== "ENOENT") {
5158
6051
  this.events?.emit("storage.read", {
@@ -5399,7 +6292,7 @@ var RecoveryLock = class {
5399
6292
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
5400
6293
  };
5401
6294
  try {
5402
- await fsp2.writeFile(this.file, JSON.stringify(lock), { flag: "wx", mode: 384 });
6295
+ await fsp4.writeFile(this.file, JSON.stringify(lock), { flag: "wx", mode: 384 });
5403
6296
  } catch (err) {
5404
6297
  const code = err.code;
5405
6298
  if (code === "EEXIST") {
@@ -5415,7 +6308,7 @@ var RecoveryLock = class {
5415
6308
  */
5416
6309
  async clear() {
5417
6310
  try {
5418
- await fsp2.unlink(this.file);
6311
+ await fsp4.unlink(this.file);
5419
6312
  } catch (err) {
5420
6313
  const code = err.code;
5421
6314
  if (code === "ENOENT") return;
@@ -5425,7 +6318,7 @@ var RecoveryLock = class {
5425
6318
  async readLock() {
5426
6319
  let raw;
5427
6320
  try {
5428
- raw = await fsp2.readFile(this.file, "utf8");
6321
+ raw = await fsp4.readFile(this.file, "utf8");
5429
6322
  } catch (err) {
5430
6323
  const code = err.code;
5431
6324
  if (code === "ENOENT") return null;
@@ -5473,8 +6366,8 @@ var DefaultSessionReader = class _DefaultSessionReader {
5473
6366
  const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
5474
6367
  let mtimeMs = null;
5475
6368
  try {
5476
- const stat11 = await fsp2.stat(sessionPath);
5477
- mtimeMs = stat11.mtimeMs;
6369
+ const stat13 = await fsp4.stat(sessionPath);
6370
+ mtimeMs = stat13.mtimeMs;
5478
6371
  } catch {
5479
6372
  this.eventCache.delete(sessionId);
5480
6373
  this.eventCacheMtimes.delete(sessionId);
@@ -6054,7 +6947,7 @@ var AnnotationsStore = class {
6054
6947
  const fp = this.filePath(sessionId);
6055
6948
  let raw;
6056
6949
  try {
6057
- raw = await fsp2.readFile(fp, "utf8");
6950
+ raw = await fsp4.readFile(fp, "utf8");
6058
6951
  } catch (err) {
6059
6952
  if (err.code === "ENOENT") return null;
6060
6953
  throw err;
@@ -6173,12 +7066,12 @@ var ReplayLogStore = class {
6173
7066
  const line = JSON.stringify(entry) + "\n";
6174
7067
  let offset2 = 0;
6175
7068
  try {
6176
- const stat11 = await fsp2.stat(fp);
6177
- offset2 = stat11.size;
7069
+ const stat13 = await fsp4.stat(fp);
7070
+ offset2 = stat13.size;
6178
7071
  } catch (err) {
6179
7072
  if (err.code !== "ENOENT") throw err;
6180
7073
  }
6181
- await fsp2.appendFile(fp, line, "utf8");
7074
+ await fsp4.appendFile(fp, line, "utf8");
6182
7075
  cache.set(hash, { entry, offset: offset2, length: Buffer.byteLength(line, "utf8") });
6183
7076
  this.diskCount.set(input.sessionId, currentCount + 1);
6184
7077
  this.events?.emit("storage.write", {
@@ -6306,7 +7199,7 @@ var ReplayLogStore = class {
6306
7199
  const scan = async (dir, prefix, depth) => {
6307
7200
  let entries;
6308
7201
  try {
6309
- entries = await fsp2.readdir(dir, { withFileTypes: true });
7202
+ entries = await fsp4.readdir(dir, { withFileTypes: true });
6310
7203
  } catch (err) {
6311
7204
  if (depth === 0 && err.code !== "ENOENT") {
6312
7205
  console.warn(JSON.stringify({
@@ -6344,10 +7237,10 @@ var ReplayLogStore = class {
6344
7237
  return sessionScopedPath(this.dir, sessionId, ".replay.jsonl");
6345
7238
  }
6346
7239
  async countEntries(filePath) {
6347
- const handle = await fsp2.open(filePath, "r");
7240
+ const handle = await fsp4.open(filePath, "r");
6348
7241
  const buffer = Buffer.allocUnsafe(64 * 1024);
6349
7242
  let count = 0;
6350
- let hasNonWhitespace = false;
7243
+ let hasNonWhitespace2 = false;
6351
7244
  try {
6352
7245
  while (true) {
6353
7246
  const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
@@ -6355,19 +7248,19 @@ var ReplayLogStore = class {
6355
7248
  for (let i = 0; i < bytesRead; i++) {
6356
7249
  const ch = buffer[i];
6357
7250
  if (ch === 10) {
6358
- if (hasNonWhitespace) count++;
6359
- hasNonWhitespace = false;
7251
+ if (hasNonWhitespace2) count++;
7252
+ hasNonWhitespace2 = false;
6360
7253
  continue;
6361
7254
  }
6362
7255
  if (ch !== 13 && ch !== 32 && ch !== 9) {
6363
- hasNonWhitespace = true;
7256
+ hasNonWhitespace2 = true;
6364
7257
  }
6365
7258
  }
6366
7259
  }
6367
7260
  } finally {
6368
7261
  await handle.close();
6369
7262
  }
6370
- if (hasNonWhitespace) count++;
7263
+ if (hasNonWhitespace2) count++;
6371
7264
  return count;
6372
7265
  }
6373
7266
  parseReplayLine(line) {
@@ -6385,7 +7278,7 @@ var ReplayLogStore = class {
6385
7278
  async readAll(sessionId) {
6386
7279
  const fp = this.filePath(sessionId);
6387
7280
  try {
6388
- const raw = await fsp2.readFile(fp, "utf8");
7281
+ const raw = await fsp4.readFile(fp, "utf8");
6389
7282
  const out = [];
6390
7283
  for (const line of raw.split("\n")) {
6391
7284
  if (!line.trim()) continue;
@@ -6420,7 +7313,7 @@ var ReplayLogStore = class {
6420
7313
  const fp = this.filePath(sessionId);
6421
7314
  cache = /* @__PURE__ */ new Map();
6422
7315
  try {
6423
- const handle = await fsp2.open(fp, "r");
7316
+ const handle = await fsp4.open(fp, "r");
6424
7317
  const CHUNK = 64 * 1024;
6425
7318
  const buffer = Buffer.alloc(CHUNK);
6426
7319
  let leftover = "";
@@ -6471,7 +7364,7 @@ var ReplayLogStore = class {
6471
7364
  async hydrateEntry(sessionId, hash, location) {
6472
7365
  if (location.entry) return location.entry;
6473
7366
  const fp = this.filePath(sessionId);
6474
- const handle = await fsp2.open(fp, "r");
7367
+ const handle = await fsp4.open(fp, "r");
6475
7368
  try {
6476
7369
  const buffer = Buffer.alloc(location.length);
6477
7370
  const { bytesRead } = await handle.read(buffer, 0, location.length, location.offset);
@@ -6502,66 +7395,50 @@ var SessionRecovery = class {
6502
7395
  }
6503
7396
  dir;
6504
7397
  /**
6505
- * Scan a session log and return a `StaleSession` if and only
6506
- * if the last event is an `in_flight_start` without a matching
6507
- * `in_flight_end`. Returns `null` when:
7398
+ * Scan a session log and return a `StaleSession` if and only if the newest
7399
+ * lifecycle boundary is an `in_flight_start` without a later
7400
+ * `in_flight_end`/`session_end`. Ordinary provider/tool events after the
7401
+ * marker do not make the session clean. Returns `null` when:
6508
7402
  * - the log does not exist;
6509
7403
  * - the log is empty;
6510
- * - the last event is `in_flight_end` (clean shutdown);
6511
- * - the last event is something else (e.g. an unannotated
6512
- * legacy log without in-flight markers).
7404
+ * - the latest lifecycle boundary is `in_flight_end` or `session_end`;
7405
+ * - there is no lifecycle boundary (legacy/pre-marker log).
7406
+ *
7407
+ * The reverse scanner is chunked and line-aware. It can cross arbitrarily
7408
+ * large JSONL records (for example a large tool result) without imposing a
7409
+ * fixed tail-size correctness limit. Clean logs normally return after the
7410
+ * first chunk; stale logs continue counting lines so `eventCount` remains
7411
+ * the documented total rather than a tail-only approximation.
6513
7412
  */
6514
7413
  async detectStale(sessionId) {
6515
7414
  const fp = this.filePath(sessionId);
6516
- const TAIL_SIZE = 8192;
6517
- let stat11;
7415
+ let stat13;
6518
7416
  try {
6519
- stat11 = await fsp2.stat(fp);
7417
+ stat13 = await fsp4.stat(fp);
6520
7418
  } catch (err) {
6521
7419
  if (err.code === "ENOENT") return null;
6522
7420
  return null;
6523
7421
  }
6524
- if (stat11.size === 0) return null;
6525
- const position = Math.max(0, stat11.size - TAIL_SIZE);
6526
- const buf = Buffer.alloc(TAIL_SIZE);
6527
- let fh;
7422
+ if (stat13.size === 0) return null;
6528
7423
  try {
6529
- fh = await fsp2.open(fp, "r");
6530
- const { bytesRead } = await fh.read(buf, 0, TAIL_SIZE, position);
6531
- let eventCount = 0;
6532
- const raw = buf.subarray(0, bytesRead).toString("utf8");
6533
- for (const line of raw.split("\n")) {
6534
- if (line.trim()) eventCount++;
6535
- }
6536
- const lines = raw.split("\n").filter((l) => l.trim());
6537
- for (let i = lines.length - 1; i >= 0; i--) {
6538
- try {
6539
- const ev = JSON.parse(expectDefined(lines[i]));
6540
- if (ev.type === "in_flight_start") {
6541
- return {
6542
- sessionId,
6543
- path: fp,
6544
- lastEventTs: ev.ts,
6545
- context: ev.context,
6546
- eventCount
6547
- };
6548
- }
6549
- return null;
6550
- } catch {
6551
- }
6552
- }
6553
- return null;
7424
+ const scan = await scanLatestLifecycleBoundary(fp, stat13.size);
7425
+ if (scan?.boundary.type !== "in_flight_start") return null;
7426
+ return {
7427
+ sessionId,
7428
+ path: fp,
7429
+ lastEventTs: scan.boundary.ts,
7430
+ context: scan.boundary.context,
7431
+ eventCount: scan.eventCount
7432
+ };
6554
7433
  } catch {
6555
7434
  return null;
6556
- } finally {
6557
- if (fh) await fh.close();
6558
7435
  }
6559
7436
  }
6560
7437
  /**
6561
7438
  * Generate a recovery plan for a session. The plan describes
6562
- * "what would be re-executed" if the user chose to resume
6563
- * everything after the last `checkpoint` event, plus the
6564
- * dangling in-flight marker if present.
7439
+ * the persisted tail after the last checkpoint, plus the dangling in-flight
7440
+ * marker if present. SessionStore.resume() independently reconstructs state
7441
+ * from the journal and does not re-run these events as commands.
6565
7442
  *
6566
7443
  * Returns a non-null plan for ANY session that has at least
6567
7444
  * one event after a checkpoint (or, for legacy sessions, at
@@ -6571,7 +7448,7 @@ var SessionRecovery = class {
6571
7448
  const fp = this.filePath(sessionId);
6572
7449
  let raw;
6573
7450
  try {
6574
- raw = await fsp2.readFile(fp, "utf8");
7451
+ raw = await fsp4.readFile(fp, "utf8");
6575
7452
  } catch (err) {
6576
7453
  if (err.code === "ENOENT") return null;
6577
7454
  return null;
@@ -6594,8 +7471,8 @@ var SessionRecovery = class {
6594
7471
  }
6595
7472
  }
6596
7473
  const pendingEvents = lastCheckpointIdx >= 0 ? events.slice(lastCheckpointIdx + 1) : events;
6597
- const lastEv = expectDefined(events[events.length - 1]);
6598
- const inFlightStart = lastEv.type === "in_flight_start" ? lastEv : null;
7474
+ const latestBoundary = events.findLast(isLifecycleBoundary);
7475
+ const inFlightStart = latestBoundary?.type === "in_flight_start" ? latestBoundary : null;
6599
7476
  const context = inFlightStart && inFlightStart.type === "in_flight_start" ? inFlightStart.context : null;
6600
7477
  return {
6601
7478
  sessionId,
@@ -6616,7 +7493,7 @@ var SessionRecovery = class {
6616
7493
  const collect = async (dir, prefix, depth) => {
6617
7494
  let entries;
6618
7495
  try {
6619
- entries = await fsp2.readdir(dir, { withFileTypes: true });
7496
+ entries = await fsp4.readdir(dir, { withFileTypes: true });
6620
7497
  } catch {
6621
7498
  return;
6622
7499
  }
@@ -6648,6 +7525,61 @@ var SessionRecovery = class {
6648
7525
  return sessionScopedPath(this.dir, sessionId, ".jsonl");
6649
7526
  }
6650
7527
  };
7528
+ function isLifecycleBoundary(event) {
7529
+ return event.type === "in_flight_start" || event.type === "in_flight_end" || event.type === "session_end";
7530
+ }
7531
+ function parseLifecycleBoundary(line) {
7532
+ try {
7533
+ const parsed = JSON.parse(line.toString("utf8"));
7534
+ return isLifecycleBoundary(parsed) ? parsed : null;
7535
+ } catch {
7536
+ return null;
7537
+ }
7538
+ }
7539
+ function hasNonWhitespace(line) {
7540
+ for (const byte of line) {
7541
+ if (byte !== 9 && byte !== 10 && byte !== 13 && byte !== 32) return true;
7542
+ }
7543
+ return false;
7544
+ }
7545
+ async function scanLatestLifecycleBoundary(filePath, size) {
7546
+ const CHUNK_SIZE = 64 * 1024;
7547
+ const handle = await fsp4.open(filePath, "r");
7548
+ let position = size;
7549
+ let laterLineFragment = Buffer.alloc(0);
7550
+ let latestBoundary = null;
7551
+ let eventCount = 0;
7552
+ const observeLine = (line) => {
7553
+ if (!hasNonWhitespace(line)) return null;
7554
+ eventCount++;
7555
+ return parseLifecycleBoundary(line);
7556
+ };
7557
+ try {
7558
+ while (position > 0) {
7559
+ const length = Math.min(CHUNK_SIZE, position);
7560
+ position -= length;
7561
+ const chunk = Buffer.allocUnsafe(length);
7562
+ const { bytesRead } = await handle.read(chunk, 0, length, position);
7563
+ const data = Buffer.concat([chunk.subarray(0, bytesRead), laterLineFragment]);
7564
+ let lineEnd = data.length;
7565
+ for (let i = data.length - 1; i >= 0; i--) {
7566
+ if (data[i] !== 10) continue;
7567
+ const boundary2 = observeLine(data.subarray(i + 1, lineEnd));
7568
+ if (!latestBoundary && boundary2) latestBoundary = boundary2;
7569
+ lineEnd = i;
7570
+ }
7571
+ laterLineFragment = data.subarray(0, lineEnd);
7572
+ if (latestBoundary && latestBoundary.type !== "in_flight_start") {
7573
+ return { boundary: latestBoundary, eventCount };
7574
+ }
7575
+ }
7576
+ const boundary = observeLine(laterLineFragment);
7577
+ if (!latestBoundary && boundary) latestBoundary = boundary;
7578
+ return latestBoundary ? { boundary: latestBoundary, eventCount } : null;
7579
+ } finally {
7580
+ await handle.close();
7581
+ }
7582
+ }
6651
7583
  var GENESIS_PREV = "0".repeat(64);
6652
7584
  var DEFAULT_FSYNC_EVERY = 100;
6653
7585
  var ToolAuditLog = class {
@@ -6717,9 +7649,9 @@ var ToolAuditLog = class {
6717
7649
  isError: input.isError,
6718
7650
  index
6719
7651
  };
6720
- await fsp2.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
7652
+ await fsp4.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
6721
7653
  try {
6722
- const st = await fsp2.stat(fp);
7654
+ const st = await fsp4.stat(fp);
6723
7655
  this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
6724
7656
  } catch {
6725
7657
  }
@@ -6766,7 +7698,7 @@ var ToolAuditLog = class {
6766
7698
  const cachedStat = this.tailStat.get(sessionId);
6767
7699
  if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
6768
7700
  try {
6769
- const st = await fsp2.stat(fp);
7701
+ const st = await fsp4.stat(fp);
6770
7702
  if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
6771
7703
  return { prevHash: cachedHash, nextIndex: cachedIndex };
6772
7704
  }
@@ -6787,7 +7719,7 @@ var ToolAuditLog = class {
6787
7719
  this.tailHash.set(sessionId, prevHash);
6788
7720
  this.tailIndex.set(sessionId, nextIndex);
6789
7721
  try {
6790
- const st = await fsp2.stat(fp);
7722
+ const st = await fsp4.stat(fp);
6791
7723
  this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
6792
7724
  } catch {
6793
7725
  }
@@ -6908,7 +7840,7 @@ var ToolAuditLog = class {
6908
7840
  async readAll(sessionId) {
6909
7841
  const fp = this.filePath(sessionId);
6910
7842
  try {
6911
- const raw = await fsp2.readFile(fp, "utf8");
7843
+ const raw = await fsp4.readFile(fp, "utf8");
6912
7844
  const out = [];
6913
7845
  for (const line of raw.split("\n")) {
6914
7846
  if (!line.trim()) continue;
@@ -6946,7 +7878,7 @@ var ToolAuditLog = class {
6946
7878
  }
6947
7879
  async sync(sessionId, fp) {
6948
7880
  try {
6949
- const fh = await fsp2.open(fp, "r+");
7881
+ const fh = await fsp4.open(fp, "r+");
6950
7882
  try {
6951
7883
  await fh.sync();
6952
7884
  } finally {
@@ -7264,7 +8196,7 @@ var SessionRegistry = class {
7264
8196
  }
7265
8197
  async readAndPrune() {
7266
8198
  try {
7267
- const raw = await fsp2.readFile(this.filePath, "utf8");
8199
+ const raw = await fsp4.readFile(this.filePath, "utf8");
7268
8200
  const registry = JSON.parse(raw);
7269
8201
  const now = Date.now();
7270
8202
  let pruned = false;
@@ -7298,11 +8230,11 @@ var SessionRegistry = class {
7298
8230
  const retryDelayMs = 20;
7299
8231
  for (let attempt = 0; attempt < maxRetries; attempt++) {
7300
8232
  try {
7301
- await fsp2.mkdir(path2.dirname(this.filePath), { recursive: true });
7302
- let lockHandle = await fsp2.open(lockPath, "wx").catch(() => null);
8233
+ await fsp4.mkdir(path2.dirname(this.filePath), { recursive: true });
8234
+ let lockHandle = await fsp4.open(lockPath, "wx").catch(() => null);
7303
8235
  if (!lockHandle) {
7304
8236
  if (await this.breakStaleLock(lockPath)) {
7305
- lockHandle = await fsp2.open(lockPath, "wx").catch(() => null);
8237
+ lockHandle = await fsp4.open(lockPath, "wx").catch(() => null);
7306
8238
  }
7307
8239
  if (!lockHandle) {
7308
8240
  await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
@@ -7311,14 +8243,14 @@ var SessionRegistry = class {
7311
8243
  }
7312
8244
  try {
7313
8245
  await lockHandle.writeFile(String(process.pid)).catch(() => void 0);
7314
- const raw = await fsp2.readFile(this.filePath, "utf8").catch(() => "{}");
8246
+ const raw = await fsp4.readFile(this.filePath, "utf8").catch(() => "{}");
7315
8247
  const registry = JSON.parse(raw);
7316
8248
  fn(registry);
7317
8249
  await this.writeAtomicLocked(registry);
7318
8250
  return;
7319
8251
  } finally {
7320
8252
  await lockHandle.close();
7321
- await fsp2.unlink(lockPath).catch(() => void 0);
8253
+ await fsp4.unlink(lockPath).catch(() => void 0);
7322
8254
  }
7323
8255
  } catch {
7324
8256
  return;
@@ -7334,17 +8266,17 @@ var SessionRegistry = class {
7334
8266
  */
7335
8267
  async breakStaleLock(lockPath) {
7336
8268
  try {
7337
- const [stat11, content] = await Promise.all([
7338
- fsp2.stat(lockPath),
8269
+ const [stat13, content] = await Promise.all([
8270
+ fsp4.stat(lockPath),
7339
8271
  /* v8 ignore start -- best-effort lock-content read; .catch only fires if the lock vanished */
7340
- fsp2.readFile(lockPath, "utf8").catch(() => "")
8272
+ fsp4.readFile(lockPath, "utf8").catch(() => "")
7341
8273
  /* v8 ignore stop */
7342
8274
  ]);
7343
- const ageMs = Date.now() - stat11.mtimeMs;
8275
+ const ageMs = Date.now() - stat13.mtimeMs;
7344
8276
  const ownerPid = Number.parseInt(content.trim(), 10);
7345
8277
  const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
7346
8278
  if (ownerDead || ageMs > STALE_LOCK_MS) {
7347
- await fsp2.unlink(lockPath).catch(() => void 0);
8279
+ await fsp4.unlink(lockPath).catch(() => void 0);
7348
8280
  return true;
7349
8281
  }
7350
8282
  return false;
@@ -7367,10 +8299,10 @@ var SessionRegistry = class {
7367
8299
  `.${path2.basename(this.filePath)}.${randomUUID().slice(0, 8)}.tmp`
7368
8300
  );
7369
8301
  try {
7370
- await fsp2.writeFile(tmp, JSON.stringify(registry, null, 2), "utf8");
7371
- await fsp2.rename(tmp, this.filePath);
8302
+ await fsp4.writeFile(tmp, JSON.stringify(registry, null, 2), "utf8");
8303
+ await fsp4.rename(tmp, this.filePath);
7372
8304
  } catch (err) {
7373
- await fsp2.unlink(tmp).catch(() => void 0);
8305
+ await fsp4.unlink(tmp).catch(() => void 0);
7374
8306
  throw err;
7375
8307
  }
7376
8308
  }
@@ -7380,17 +8312,17 @@ var SessionRegistry = class {
7380
8312
  const base = path2.basename(this.filePath);
7381
8313
  const now = Date.now();
7382
8314
  const stale = [];
7383
- for (const name of await fsp2.readdir(dir)) {
8315
+ for (const name of await fsp4.readdir(dir)) {
7384
8316
  const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
7385
8317
  if (!isTemp) continue;
7386
- const stat11 = await fsp2.stat(path2.join(dir, name)).catch(() => null);
7387
- if (!stat11) continue;
7388
- if (now - stat11.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat11.mtimeMs });
8318
+ const stat13 = await fsp4.stat(path2.join(dir, name)).catch(() => null);
8319
+ if (!stat13) continue;
8320
+ if (now - stat13.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat13.mtimeMs });
7389
8321
  }
7390
8322
  stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
7391
8323
  await Promise.all(
7392
8324
  stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
7393
- await fsp2.unlink(path2.join(dir, name)).catch(() => void 0);
8325
+ await fsp4.unlink(path2.join(dir, name)).catch(() => void 0);
7394
8326
  })
7395
8327
  );
7396
8328
  } catch {
@@ -7858,7 +8790,7 @@ var FleetNotifier = class {
7858
8790
  }
7859
8791
  async discover() {
7860
8792
  try {
7861
- const raw = await fsp2.readFile(path2.join(this.baseDir, INSTANCES_FILE), "utf8");
8793
+ const raw = await fsp4.readFile(path2.join(this.baseDir, INSTANCES_FILE), "utf8");
7862
8794
  const data = JSON.parse(raw);
7863
8795
  const list = Array.isArray(data?.instances) ? data.instances : [];
7864
8796
  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) => {
@@ -7888,7 +8820,7 @@ var DefaultSessionRewinder = class {
7888
8820
  }
7889
8821
  async listCheckpoints(sessionId) {
7890
8822
  const file = this.sessionFile(sessionId);
7891
- const raw = await fsp2.readFile(file, "utf8");
8823
+ const raw = await fsp4.readFile(file, "utf8");
7892
8824
  const events = parseEvents(raw);
7893
8825
  const fileCountMap = /* @__PURE__ */ new Map();
7894
8826
  for (const event of events) {
@@ -7913,7 +8845,7 @@ var DefaultSessionRewinder = class {
7913
8845
  }
7914
8846
  async rewindToCheckpoint(sessionId, checkpointIndex) {
7915
8847
  const file = this.sessionFile(sessionId);
7916
- const raw = await fsp2.readFile(file, "utf8");
8848
+ const raw = await fsp4.readFile(file, "utf8");
7917
8849
  const events = parseEvents(raw);
7918
8850
  let targetIdx = -1;
7919
8851
  for (let i = 0; i < events.length; i++) {
@@ -7952,7 +8884,7 @@ var DefaultSessionRewinder = class {
7952
8884
  }
7953
8885
  async rewindLastN(sessionId, n) {
7954
8886
  const file = this.sessionFile(sessionId);
7955
- const raw = await fsp2.readFile(file, "utf8");
8887
+ const raw = await fsp4.readFile(file, "utf8");
7956
8888
  const events = parseEvents(raw);
7957
8889
  const checkpoints = [];
7958
8890
  for (const event of events) {
@@ -7976,12 +8908,12 @@ var DefaultSessionRewinder = class {
7976
8908
  snapshotsToRevert.push({ promptIndex: event.promptIndex, files: event.files });
7977
8909
  }
7978
8910
  }
7979
- const result = await revertSnapshots(snapshotsToRevert.reverse(), this.projectRoot);
8911
+ const result = await revertSnapshots(snapshotsToRevert, this.projectRoot);
7980
8912
  return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
7981
8913
  }
7982
8914
  async rewindToStart(sessionId) {
7983
8915
  const file = this.sessionFile(sessionId);
7984
- const raw = await fsp2.readFile(file, "utf8");
8916
+ const raw = await fsp4.readFile(file, "utf8");
7985
8917
  const events = parseEvents(raw);
7986
8918
  const allSnapshots = [];
7987
8919
  for (const event of events) {
@@ -7992,7 +8924,7 @@ var DefaultSessionRewinder = class {
7992
8924
  if (allSnapshots.length === 0) {
7993
8925
  return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };
7994
8926
  }
7995
- const result = await revertSnapshots(allSnapshots.reverse(), this.projectRoot);
8927
+ const result = await revertSnapshots(allSnapshots, this.projectRoot);
7996
8928
  return { ...result, toPromptIndex: 0, removedEvents: allSnapshots.length };
7997
8929
  }
7998
8930
  };
@@ -8013,8 +8945,8 @@ function parseEvents(raw) {
8013
8945
  async function revertSnapshots(snapshots, projectRoot) {
8014
8946
  const revertedFiles = [];
8015
8947
  const errors = [];
8016
- for (const snapshot of snapshots) {
8017
- for (const file of snapshot.files) {
8948
+ for (const snapshot of [...snapshots].reverse()) {
8949
+ for (const file of [...snapshot.files].reverse()) {
8018
8950
  try {
8019
8951
  const absPath = path2.resolve(file.path);
8020
8952
  const root = path2.resolve(projectRoot);
@@ -8029,7 +8961,7 @@ async function revertSnapshots(snapshots, projectRoot) {
8029
8961
  revertedFiles.push(file.path);
8030
8962
  }
8031
8963
  } else if (file.action === "created") {
8032
- await fsp2.unlink(file.path);
8964
+ await fsp4.unlink(file.path);
8033
8965
  revertedFiles.push(file.path);
8034
8966
  } else if (file.action === "modified") {
8035
8967
  if (file.before !== null) {
@@ -8048,7 +8980,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
8048
8980
  const t0 = Date.now();
8049
8981
  let raw;
8050
8982
  try {
8051
- raw = await fsp2.readFile(filePath, "utf8");
8983
+ raw = await fsp4.readFile(filePath, "utf8");
8052
8984
  } catch (err) {
8053
8985
  events?.emit("storage.error", {
8054
8986
  sessionId: traceId ?? "~boot~",
@@ -8187,7 +9119,7 @@ async function loadCompletedWorkCheckpoint(filePath, events, traceId) {
8187
9119
  const t0 = Date.now();
8188
9120
  let raw;
8189
9121
  try {
8190
- raw = await fsp2.readFile(filePath, "utf8");
9122
+ raw = await fsp4.readFile(filePath, "utf8");
8191
9123
  } catch (err) {
8192
9124
  events?.emit("storage.error", {
8193
9125
  sessionId: traceId ?? "~boot~",
@@ -8285,7 +9217,7 @@ async function loadPlan(filePath, events) {
8285
9217
  const t0 = Date.now();
8286
9218
  let raw;
8287
9219
  try {
8288
- raw = await fsp2.readFile(filePath, "utf8");
9220
+ raw = await fsp4.readFile(filePath, "utf8");
8289
9221
  } catch (err) {
8290
9222
  events?.emit("storage.error", {
8291
9223
  sessionId: "~boot~",
@@ -8601,7 +9533,7 @@ async function loadTasks(filePath, events, traceId) {
8601
9533
  const t0 = Date.now();
8602
9534
  let raw;
8603
9535
  try {
8604
- raw = await fsp2.readFile(filePath, "utf8");
9536
+ raw = await fsp4.readFile(filePath, "utf8");
8605
9537
  } catch (err) {
8606
9538
  events?.emit("storage.error", {
8607
9539
  sessionId: traceId ?? "~boot~",
@@ -8705,7 +9637,7 @@ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
8705
9637
  async function loadDirectorState(filePath) {
8706
9638
  let raw;
8707
9639
  try {
8708
- raw = await fsp2.readFile(filePath, "utf8");
9640
+ raw = await fsp4.readFile(filePath, "utf8");
8709
9641
  } catch {
8710
9642
  return null;
8711
9643
  }
@@ -8720,7 +9652,7 @@ async function loadDirectorState(filePath) {
8720
9652
  async function acquireDirectorStateLock(lockPath, processId = process.pid) {
8721
9653
  let existing;
8722
9654
  try {
8723
- existing = await fsp2.readFile(lockPath, "utf8");
9655
+ existing = await fsp4.readFile(lockPath, "utf8");
8724
9656
  } catch {
8725
9657
  }
8726
9658
  if (existing) {
@@ -8744,7 +9676,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
8744
9676
  }
8745
9677
  async function releaseDirectorStateLock(lockPath) {
8746
9678
  try {
8747
- await fsp2.unlink(lockPath);
9679
+ await fsp4.unlink(lockPath);
8748
9680
  } catch {
8749
9681
  }
8750
9682
  }
@@ -8907,7 +9839,7 @@ async function loadGoal(filePath, events) {
8907
9839
  const t0 = Date.now();
8908
9840
  let raw;
8909
9841
  try {
8910
- raw = await fsp2.readFile(filePath, "utf8");
9842
+ raw = await fsp4.readFile(filePath, "utf8");
8911
9843
  } catch (err) {
8912
9844
  const code = err.code;
8913
9845
  if (code === "ENOENT") {
@@ -9214,12 +10146,12 @@ var DefaultPromptStore = class {
9214
10146
  await ensureDir(this.dir);
9215
10147
  const entries = [];
9216
10148
  try {
9217
- const files = await fsp2.readdir(this.dir);
10149
+ const files = await fsp4.readdir(this.dir);
9218
10150
  for (const file of files) {
9219
10151
  if (!file.endsWith(".json")) continue;
9220
10152
  try {
9221
10153
  const raw = JSON.parse(
9222
- await fsp2.readFile(path2.join(this.dir, file), "utf8")
10154
+ await fsp4.readFile(path2.join(this.dir, file), "utf8")
9223
10155
  );
9224
10156
  const migrated = migratePromptEntry(raw.entry);
9225
10157
  if (migrated) entries.push(migrated);
@@ -9235,7 +10167,7 @@ var DefaultPromptStore = class {
9235
10167
  async get(id) {
9236
10168
  const file = path2.join(this.dir, `${id}.json`);
9237
10169
  try {
9238
- const raw = JSON.parse(await fsp2.readFile(file, "utf8"));
10170
+ const raw = JSON.parse(await fsp4.readFile(file, "utf8"));
9239
10171
  return migratePromptEntry(raw.entry);
9240
10172
  } catch {
9241
10173
  return null;
@@ -9250,7 +10182,7 @@ var DefaultPromptStore = class {
9250
10182
  async delete(id) {
9251
10183
  const file = path2.join(this.dir, `${id}.json`);
9252
10184
  try {
9253
- await fsp2.unlink(file);
10185
+ await fsp4.unlink(file);
9254
10186
  return true;
9255
10187
  } catch {
9256
10188
  return false;
@@ -9294,7 +10226,7 @@ var PromptUsageStore = class {
9294
10226
  file;
9295
10227
  async load() {
9296
10228
  try {
9297
- const raw = JSON.parse(await fsp2.readFile(this.file, "utf8"));
10229
+ const raw = JSON.parse(await fsp4.readFile(this.file, "utf8"));
9298
10230
  if (raw && typeof raw === "object" && raw.usage && typeof raw.usage === "object") {
9299
10231
  return raw.usage;
9300
10232
  }
@@ -9350,7 +10282,7 @@ var InputHistoryStore = class {
9350
10282
  */
9351
10283
  async load() {
9352
10284
  try {
9353
- const raw = JSON.parse(await fsp2.readFile(this.file, "utf8"));
10285
+ const raw = JSON.parse(await fsp4.readFile(this.file, "utf8"));
9354
10286
  if (raw && typeof raw === "object" && Array.isArray(raw.entries) && raw.entries.every((e) => typeof e === "string")) {
9355
10287
  return raw.entries.slice(0, this.maxEntries);
9356
10288
  }
@@ -9486,7 +10418,7 @@ var CloudSync = class {
9486
10418
  lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
9487
10419
  localRev: rev
9488
10420
  };
9489
- await fsp2.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
10421
+ await fsp4.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
9490
10422
  this.state = syncState;
9491
10423
  return {
9492
10424
  ok: true,
@@ -9518,8 +10450,8 @@ var CloudSync = class {
9518
10450
  const rel = segments.slice(2).join("/");
9519
10451
  const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
9520
10452
  const blobData = await this.getBlob(token, owner, repoName, entry.sha);
9521
- await fsp2.mkdir(path2.dirname(destPath), { recursive: true });
9522
- await fsp2.writeFile(destPath, Buffer.from(blobData, "base64"));
10453
+ await fsp4.mkdir(path2.dirname(destPath), { recursive: true });
10454
+ await fsp4.writeFile(destPath, Buffer.from(blobData, "base64"));
9523
10455
  }
9524
10456
  const localRev = await this.hashLocalCategories(cfg.categories);
9525
10457
  const syncState = {
@@ -9528,7 +10460,7 @@ var CloudSync = class {
9528
10460
  lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
9529
10461
  localRev
9530
10462
  };
9531
- await fsp2.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
10463
+ await fsp4.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
9532
10464
  this.state = syncState;
9533
10465
  return {
9534
10466
  ok: true,
@@ -9547,7 +10479,7 @@ var CloudSync = class {
9547
10479
  }
9548
10480
  async loadState() {
9549
10481
  try {
9550
- const raw = await fsp2.readFile(this.statePath, "utf8");
10482
+ const raw = await fsp4.readFile(this.statePath, "utf8");
9551
10483
  this.state = JSON.parse(raw);
9552
10484
  } catch {
9553
10485
  this.state = null;
@@ -9625,17 +10557,17 @@ var CloudSync = class {
9625
10557
  const localPath = this.categoryToPath(cat);
9626
10558
  if (!localPath) continue;
9627
10559
  try {
9628
- const stat11 = await fsp2.stat(localPath);
9629
- if (stat11.isDirectory()) {
10560
+ const stat13 = await fsp4.stat(localPath);
10561
+ if (stat13.isDirectory()) {
9630
10562
  const files = await this.walkDir(localPath, localPath);
9631
10563
  for (const file of files) {
9632
- const content = await fsp2.readFile(file, "utf8");
10564
+ const content = await fsp4.readFile(file, "utf8");
9633
10565
  const rel = path2.relative(localPath, file).replace(/\\/g, "/");
9634
10566
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
9635
10567
  hashes.push(content);
9636
10568
  }
9637
10569
  } else {
9638
- const content = await fsp2.readFile(localPath, "utf8");
10570
+ const content = await fsp4.readFile(localPath, "utf8");
9639
10571
  entries.push({ path: `data/${cat}`, content, mode: "100644" });
9640
10572
  hashes.push(content);
9641
10573
  }
@@ -9651,15 +10583,15 @@ var CloudSync = class {
9651
10583
  const localPath = this.categoryToPath(cat);
9652
10584
  if (!localPath) continue;
9653
10585
  try {
9654
- const stat11 = await fsp2.stat(localPath);
9655
- if (stat11.isDirectory()) {
10586
+ const stat13 = await fsp4.stat(localPath);
10587
+ if (stat13.isDirectory()) {
9656
10588
  const files = await this.walkDir(localPath, localPath);
9657
10589
  for (const file of files) {
9658
- const content = await fsp2.readFile(file);
10590
+ const content = await fsp4.readFile(file);
9659
10591
  hashes.push(content.toString("base64") + file);
9660
10592
  }
9661
10593
  } else {
9662
- const content = await fsp2.readFile(localPath);
10594
+ const content = await fsp4.readFile(localPath);
9663
10595
  hashes.push(content.toString("base64") + localPath);
9664
10596
  }
9665
10597
  } catch {
@@ -9686,7 +10618,7 @@ var CloudSync = class {
9686
10618
  }
9687
10619
  async walkDir(dir, base) {
9688
10620
  const results = [];
9689
- const entries = await fsp2.readdir(dir, { withFileTypes: true });
10621
+ const entries = await fsp4.readdir(dir, { withFileTypes: true });
9690
10622
  for (const entry of entries) {
9691
10623
  const full = path2.join(dir, entry.name);
9692
10624
  if (entry.isDirectory()) {
@@ -9722,8 +10654,8 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
9722
10654
  }
9723
10655
  const dest = path2.resolve(localPath, normalizedRel);
9724
10656
  const root = path2.resolve(localPath);
9725
- const relative4 = path2.relative(root, dest);
9726
- if (relative4.startsWith("..") || path2.isAbsolute(relative4)) {
10657
+ const relative6 = path2.relative(root, dest);
10658
+ if (relative6.startsWith("..") || path2.isAbsolute(relative6)) {
9727
10659
  throw new FsError({
9728
10660
  message: `Refusing CloudSync path outside category root: ${remotePath}`,
9729
10661
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -9748,11 +10680,14 @@ function timeAgo(iso) {
9748
10680
  var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
9749
10681
  "session_start",
9750
10682
  "session_resumed",
10683
+ "session_forked",
9751
10684
  "user_input",
9752
10685
  "llm_response",
9753
10686
  "tool_result",
10687
+ "context_snapshot",
9754
10688
  "checkpoint",
9755
10689
  "file_snapshot",
10690
+ "file_observation",
9756
10691
  "rewound",
9757
10692
  "in_flight_start",
9758
10693
  "in_flight_end",
@@ -9860,6 +10795,6 @@ function resolveSessionLoggingConfig(cfg) {
9860
10795
  };
9861
10796
  }
9862
10797
 
9863
- export { ALL_SYNC_CATEGORIES, AgentStatusTracker, AnnotationsStore, CORE_RECONSTRUCT_EVENTS, CloudSync, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultPromptStore, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DirectorStateCheckpoint, FileMemoryBackend, FleetNotifier, GraphMemoryBackend, INPUT_HISTORY_DEFAULT_MAX, InputHistoryStore, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, PromptUsageStore, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, generateSessionId, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadCompletedWorkCheckpoint, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, migratePromptEntry, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, promptChecksum, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, sanitizeModel, saveCompletedWorkCheckpoint, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage, watchProviderConfig };
10798
+ export { ALL_SYNC_CATEGORIES, AgentStatusTracker, AnnotationsStore, CORE_RECONSTRUCT_EVENTS, CloudSync, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultPromptStore, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DirectorStateCheckpoint, FileMemoryBackend, FleetNotifier, GraphMemoryBackend, INPUT_HISTORY_DEFAULT_MAX, InputHistoryStore, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, PromptUsageStore, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionCheckpointCas, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, generateSessionId, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadCompletedWorkCheckpoint, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, migratePromptEntry, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, promptChecksum, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, sanitizeModel, saveCompletedWorkCheckpoint, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage, watchProviderConfig };
9864
10799
  //# sourceMappingURL=index.js.map
9865
10800
  //# sourceMappingURL=index.js.map