@wrongstack/core 0.283.0 → 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.
- package/dist/{compactor-BxddyCFu.d.ts → compactor-DQLL4HTo.d.ts} +1 -1
- package/dist/{config-DkCbA3Bw.d.ts → config-MRqn1V-u.d.ts} +130 -22
- package/dist/coordination/index.d.ts +43 -20
- package/dist/coordination/index.js +1744 -912
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-Buho8yge.d.ts → default-config-B79_gJYv.d.ts} +7 -0
- package/dist/defaults/index.d.ts +25 -29
- package/dist/defaults/index.js +2546 -7396
- package/dist/defaults/index.js.map +1 -1
- package/dist/{director-state-B_GPa78Q.d.ts → director-state-CMS_bMs4.d.ts} +2 -0
- package/dist/{events-KmxSmvho.d.ts → events-Bgnh43r9.d.ts} +27 -10
- package/dist/execution/index.d.ts +15 -16
- package/dist/execution/index.js +534 -140
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{global-mailbox-rWVt8YlO.d.ts → global-mailbox-sZwyI6CS.d.ts} +13 -2
- package/dist/{goal-store-8owBXJT2.d.ts → goal-store-Datpp-ar.d.ts} +1 -1
- package/dist/hq/index.d.ts +35 -9
- package/dist/hq/index.js +80 -7
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-BhCteHAF.d.ts → index--Gm41aBJ.d.ts} +26 -11
- package/dist/{index-BEFWNZYs.d.ts → index-CbyuOE5y.d.ts} +5 -1
- package/dist/index.d.ts +52 -1017
- package/dist/index.js +21053 -27778
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +9 -7
- package/dist/infrastructure/index.js +7 -2
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +11 -11
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BUx9A_AQ.d.ts → mcp-servers-DEV4s6Ks.d.ts} +3 -3
- package/dist/models/index.d.ts +4 -4
- package/dist/models/index.js +18 -3
- package/dist/models/index.js.map +1 -1
- package/dist/{agent-subagent-runner-BsuWhB28.d.ts → multi-agent-coordinator-4RCRZKjN.d.ts} +417 -9
- package/dist/{null-fleet-bus-D4e9R7Qc.d.ts → null-fleet-bus-CFoNVQEK.d.ts} +53 -12
- package/dist/observability/index.d.ts +3 -3
- package/dist/{parallel-eternal-engine-iY2uxocj.d.ts → parallel-eternal-engine-HUrtePLd.d.ts} +4 -5
- package/dist/{permission-B6YAtNFz.d.ts → permission-ByDKXEcG.d.ts} +1 -1
- package/dist/{pipeline-ixSFe5jg.d.ts → pipeline-DGDyIohT.d.ts} +28 -7
- package/dist/{provider-model-resolve-DIxKL_T-.d.ts → provider-model-resolve-o2hHLs79.d.ts} +3 -3
- package/dist/{provider-runner-oQhDaZmH.d.ts → provider-runner-ChL-kVg6.d.ts} +3 -3
- package/dist/{retry-policy-V67lBOJz.d.ts → retry-policy-CQ7KwXJa.d.ts} +2 -2
- package/dist/{secret-vault-D7jeyXUQ.d.ts → secret-vault-DxohkUBc.d.ts} +3 -2
- package/dist/security/index.d.ts +4 -4
- package/dist/security/index.js +232 -2
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-Bx2HgUkU.d.ts → selector-D8O6B_Rm.d.ts} +1 -1
- package/dist/{session-event-bridge-Bkp7ORfc.d.ts → session-event-bridge-D_z_mBwk.d.ts} +3 -3
- package/dist/{session-reader-B-Fi9L6I.d.ts → session-reader-7Esxrkpw.d.ts} +1 -1
- package/dist/storage/index.d.ts +77 -39
- package/dist/storage/index.js +1223 -288
- package/dist/storage/index.js.map +1 -1
- package/dist/tasking/index.d.ts +124 -0
- package/dist/tasking/index.js +492 -0
- package/dist/tasking/index.js.map +1 -0
- package/dist/{todos-checkpoint-BwCrj4Cb.d.ts → todos-checkpoint-FY7PjmMx.d.ts} +13 -4
- package/dist/{tool-LVSLiE1s.d.ts → tool-BNlnIJvo.d.ts} +216 -17
- package/dist/{tool-executor-kElSEgO0.d.ts → tool-executor-CckSCM5E.d.ts} +12 -15
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +94 -19
- package/dist/types/index.js +76 -12
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +44 -10
- package/dist/utils/index.js +106 -17
- package/dist/utils/index.js.map +1 -1
- package/dist/{worktree-manager-BjyAW30D.d.ts → worktree-manager-DzwxKE35.d.ts} +1 -1
- package/instructions/coordination/subagent-baseline.md +5 -0
- package/instructions/llm/autonomy-brain.md +3 -2
- package/instructions/system.md +4 -0
- package/package.json +10 -3
- package/skills/mailbox-bridge/SKILL.md +33 -0
- package/dist/multi-agent-coordinator-CrjeTB9i.d.ts +0 -357
- package/dist/sdd/index.d.ts +0 -1993
- package/dist/sdd/index.js +0 -9500
- package/dist/sdd/index.js.map +0 -1
- package/dist/spec-TBi3Jr6T.d.ts +0 -78
package/dist/storage/index.js
CHANGED
|
@@ -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
|
|
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 = (
|
|
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
|
|
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
|
|
606
|
+
await fsp4.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
486
607
|
} else {
|
|
487
|
-
await
|
|
608
|
+
await fsp4.writeFile(tmp, content, { flag: "wx" });
|
|
488
609
|
}
|
|
489
610
|
try {
|
|
490
|
-
const fh = await
|
|
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
|
|
501
|
-
mode =
|
|
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
|
|
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
|
|
632
|
+
await fsp4.chmod(targetPath, mode);
|
|
512
633
|
} catch {
|
|
513
634
|
}
|
|
514
635
|
}
|
|
515
636
|
} catch (err) {
|
|
516
637
|
try {
|
|
517
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
663
|
+
await fsp4.mkdir(dir, { recursive: true });
|
|
543
664
|
continue;
|
|
544
665
|
}
|
|
545
666
|
if (code !== "EEXIST") throw err;
|
|
546
667
|
try {
|
|
547
|
-
const
|
|
548
|
-
if (Date.now() -
|
|
549
|
-
await
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
683
|
-
//
|
|
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`
|
|
706
|
-
* types either have no secret-bearing content
|
|
707
|
-
* upstream (tool results).
|
|
708
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
|
799
|
-
*
|
|
800
|
-
*
|
|
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
|
-
*
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
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
|
-
|
|
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
|
|
828
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1144
|
-
this.handle = await
|
|
1398
|
+
await fsp4.rename(tmpPath, this.filePath);
|
|
1399
|
+
this.handle = await fsp4.open(this.filePath, "a", 384);
|
|
1145
1400
|
} catch (err) {
|
|
1146
|
-
await
|
|
1147
|
-
this.handle = await
|
|
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
|
|
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/
|
|
1264
|
-
|
|
1265
|
-
if (
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
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
|
|
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
|
|
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
|
|
1495
|
-
const
|
|
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 ===
|
|
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 === "
|
|
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:
|
|
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
|
|
2547
|
+
let stat13;
|
|
1663
2548
|
try {
|
|
1664
|
-
|
|
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 (
|
|
2554
|
+
if (stat13.size === 0) return [];
|
|
1670
2555
|
let fh;
|
|
1671
2556
|
try {
|
|
1672
|
-
fh = await
|
|
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
|
|
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
|
|
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
|
|
2737
|
+
let stat13;
|
|
1853
2738
|
try {
|
|
1854
|
-
const s = await
|
|
1855
|
-
|
|
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 ===
|
|
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
|
|
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 = { ...
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2061
|
-
const summary = await this.summarize(id,
|
|
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
|
|
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
|
|
2105
|
-
if (!
|
|
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:
|
|
2994
|
+
startedAt: stat13.mtime.toISOString(),
|
|
2110
2995
|
model: "unknown",
|
|
2111
2996
|
provider: "unknown",
|
|
2112
2997
|
tokenTotal: 0
|
|
2113
2998
|
};
|
|
2114
2999
|
}
|
|
2115
|
-
mtime =
|
|
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
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2272
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
3188
|
+
const remaining = await fsp4.readdir(dateDir);
|
|
2304
3189
|
if (remaining.length === 0) {
|
|
2305
|
-
await
|
|
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
|
|
2325
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
2898
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
3635
|
-
await
|
|
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
|
|
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
|
|
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
|
|
4764
|
+
await fsp4.mkdir(dir, { recursive: true });
|
|
3880
4765
|
const tmp = `${this.graphFile}.tmp`;
|
|
3881
|
-
await
|
|
3882
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5122
|
-
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5477
|
-
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
|
|
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
|
|
6177
|
-
offset2 =
|
|
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
|
|
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
|
|
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
|
|
7240
|
+
const handle = await fsp4.open(filePath, "r");
|
|
6348
7241
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
6349
7242
|
let count = 0;
|
|
6350
|
-
let
|
|
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 (
|
|
6359
|
-
|
|
7251
|
+
if (hasNonWhitespace2) count++;
|
|
7252
|
+
hasNonWhitespace2 = false;
|
|
6360
7253
|
continue;
|
|
6361
7254
|
}
|
|
6362
7255
|
if (ch !== 13 && ch !== 32 && ch !== 9) {
|
|
6363
|
-
|
|
7256
|
+
hasNonWhitespace2 = true;
|
|
6364
7257
|
}
|
|
6365
7258
|
}
|
|
6366
7259
|
}
|
|
6367
7260
|
} finally {
|
|
6368
7261
|
await handle.close();
|
|
6369
7262
|
}
|
|
6370
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
6507
|
-
* `in_flight_end`.
|
|
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
|
|
6511
|
-
* -
|
|
6512
|
-
*
|
|
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
|
-
|
|
6517
|
-
let stat11;
|
|
7415
|
+
let stat13;
|
|
6518
7416
|
try {
|
|
6519
|
-
|
|
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 (
|
|
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
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
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
|
-
*
|
|
6563
|
-
*
|
|
6564
|
-
*
|
|
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
|
|
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
|
|
6598
|
-
const inFlightStart =
|
|
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
|
|
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
|
|
7652
|
+
await fsp4.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
|
|
6721
7653
|
try {
|
|
6722
|
-
const st = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7302
|
-
let lockHandle = await
|
|
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
|
|
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
|
|
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
|
|
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 [
|
|
7338
|
-
|
|
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
|
-
|
|
8272
|
+
fsp4.readFile(lockPath, "utf8").catch(() => "")
|
|
7341
8273
|
/* v8 ignore stop */
|
|
7342
8274
|
]);
|
|
7343
|
-
const ageMs = Date.now() -
|
|
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
|
|
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
|
|
7371
|
-
await
|
|
8302
|
+
await fsp4.writeFile(tmp, JSON.stringify(registry, null, 2), "utf8");
|
|
8303
|
+
await fsp4.rename(tmp, this.filePath);
|
|
7372
8304
|
} catch (err) {
|
|
7373
|
-
await
|
|
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
|
|
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
|
|
7387
|
-
if (!
|
|
7388
|
-
if (now -
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9522
|
-
await
|
|
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
|
|
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
|
|
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
|
|
9629
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
9655
|
-
if (
|
|
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
|
|
10590
|
+
const content = await fsp4.readFile(file);
|
|
9659
10591
|
hashes.push(content.toString("base64") + file);
|
|
9660
10592
|
}
|
|
9661
10593
|
} else {
|
|
9662
|
-
const content = await
|
|
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
|
|
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
|
|
9726
|
-
if (
|
|
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
|