@wrongstack/core 0.257.2 → 0.260.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/{agent-bridge-BrxWHEOm.d.ts → agent-bridge-BbskZ7HH.d.ts} +1 -1
- package/dist/{agent-subagent-runner-US741uBH.d.ts → agent-subagent-runner-BNIGZx18.d.ts} +28 -8
- package/dist/{brain-TjEEwSpw.d.ts → brain-C2yDd7Lw.d.ts} +58 -1
- package/dist/{compactor-C5sT4U7I.d.ts → compactor-t0R_AIt_.d.ts} +1 -1
- package/dist/{config-DuAu23zm.d.ts → config-FG6As4H5.d.ts} +1 -1
- package/dist/{context-CGdgA0q6.d.ts → context-JFOVvu6z.d.ts} +22 -0
- package/dist/coordination/index.d.ts +14 -14
- package/dist/coordination/index.js +189 -28
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +25 -25
- package/dist/defaults/index.js +881 -83
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +15 -15
- package/dist/execution/index.js +108 -26
- 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/{goal-preamble-CznHTZqP.d.ts → goal-preamble-B1IXJtLX.d.ts} +9 -9
- package/dist/{goal-store-CV9Yz2X_.d.ts → goal-store-CPXz6Mml.d.ts} +4 -2
- package/dist/{index-CitPrI3a.d.ts → index-BPcg4N3M.d.ts} +5 -5
- package/dist/{index-CC0Mcm05.d.ts → index-CebbJB94.d.ts} +8 -8
- package/dist/index.d.ts +44 -42
- package/dist/index.js +1452 -274
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-CJ4SyAFE.d.ts → llm-selector-DXxI2tlu.d.ts} +2 -2
- package/dist/{mcp-servers-D8YnLaEp.d.ts → mcp-servers-OwNHo43-.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/{models-registry-ByZCdFuQ.d.ts → models-registry-Djlmq4uB.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-DqTUEAeC.d.ts → multi-agent-coordinator-CEmrSCMJ.d.ts} +1 -1
- package/dist/{null-fleet-bus-B5mfTJXT.d.ts → null-fleet-bus-DT92xqgJ.d.ts} +13 -8
- package/dist/observability/index.d.ts +2 -2
- package/dist/{package-outdated-watcher-BSgR_kK-.d.ts → package-outdated-watcher-C70ag2G9.d.ts} +3 -3
- package/dist/{parallel-eternal-engine-C0juOszP.d.ts → parallel-eternal-engine-0SItuq5r.d.ts} +13 -9
- package/dist/{path-resolver-CbkT-RMU.d.ts → path-resolver-DKBh6Jlo.d.ts} +3 -3
- package/dist/{permission-CwBBpCoF.d.ts → permission-BJ7eO9Vl.d.ts} +1 -1
- package/dist/{permission-policy-B8rSu908.d.ts → permission-policy-DEXOfnpm.d.ts} +3 -2
- package/dist/{pipeline-JG8XoudC.d.ts → pipeline-zflkI2dp.d.ts} +2 -2
- package/dist/{plan-templates-DPiQMkBz.d.ts → plan-templates-BFXyRkEK.d.ts} +32 -11
- package/dist/{provider-runner-hM7EXlLI.d.ts → provider-runner-BC-uywtT.d.ts} +3 -3
- package/dist/{retry-policy-Tg7LXkoK.d.ts → retry-policy-Cavrzmtk.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +20 -2
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-gxtFZYBt.d.ts → secret-vault-CDvDYXWX.d.ts} +1 -1
- package/dist/security/index.d.ts +4 -4
- package/dist/security/index.js +30 -1
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-DWsqVjGf.d.ts → selector-B7AivHsu.d.ts} +1 -1
- package/dist/{session-event-bridge-BAFWdgQ3.d.ts → session-event-bridge-BmIDxdJd.d.ts} +1 -1
- package/dist/{session-reader-CqRvaL5v.d.ts → session-reader-DtofsB-2.d.ts} +1 -1
- package/dist/storage/index.d.ts +30 -21
- package/dist/storage/index.js +1264 -216
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +19 -19
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/package.json +1 -1
- package/skills/output-standards/SKILL.md +14 -9
- package/skills/output-standards/SKILL.save.md +3 -2
package/dist/storage/index.js
CHANGED
|
@@ -234,6 +234,40 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
234
234
|
this.events = opts.events;
|
|
235
235
|
this.secretScrubber = opts.secretScrubber;
|
|
236
236
|
}
|
|
237
|
+
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
238
|
+
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
239
|
+
this.events?.emit("storage.read", {
|
|
240
|
+
sessionId,
|
|
241
|
+
store: "session",
|
|
242
|
+
filePath,
|
|
243
|
+
operation,
|
|
244
|
+
outcome,
|
|
245
|
+
durationMs,
|
|
246
|
+
...error !== void 0 ? { error } : {}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount, error) {
|
|
250
|
+
this.events?.emit("storage.write", {
|
|
251
|
+
sessionId,
|
|
252
|
+
store: "session",
|
|
253
|
+
filePath,
|
|
254
|
+
operation,
|
|
255
|
+
outcome,
|
|
256
|
+
durationMs,
|
|
257
|
+
...eventCount !== void 0 ? { eventCount } : {},
|
|
258
|
+
...error !== void 0 ? { error } : {}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
emitError(sessionId, filePath, operation, error, recoverable) {
|
|
262
|
+
this.events?.emit("storage.error", {
|
|
263
|
+
sessionId,
|
|
264
|
+
store: "session",
|
|
265
|
+
filePath,
|
|
266
|
+
operation,
|
|
267
|
+
error,
|
|
268
|
+
recoverable
|
|
269
|
+
});
|
|
270
|
+
}
|
|
237
271
|
/** Absolute path to the session index file. */
|
|
238
272
|
get indexFile() {
|
|
239
273
|
return path13.join(this.dir, "_index.jsonl");
|
|
@@ -257,22 +291,26 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
257
291
|
const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
|
|
258
292
|
const shardDir = await this.ensureShardDir(id);
|
|
259
293
|
const file = path13.join(shardDir, `${path13.basename(id)}.jsonl`);
|
|
294
|
+
const t0 = Date.now();
|
|
260
295
|
let handle;
|
|
261
296
|
try {
|
|
262
297
|
handle = await fsp.open(file, "a", 384);
|
|
263
298
|
} catch (err) {
|
|
299
|
+
this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), false);
|
|
264
300
|
throw new Error(
|
|
265
301
|
`Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
|
|
266
302
|
{ cause: err }
|
|
267
303
|
);
|
|
268
304
|
}
|
|
269
305
|
try {
|
|
270
|
-
|
|
306
|
+
const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
271
307
|
dir: shardDir,
|
|
272
308
|
filePath: file,
|
|
273
309
|
secretScrubber: this.secretScrubber,
|
|
274
310
|
onClose: (s) => this.appendToIndex(s)
|
|
275
311
|
});
|
|
312
|
+
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
313
|
+
return writer;
|
|
276
314
|
} catch (err) {
|
|
277
315
|
await handle.close().catch((e) => console.warn(JSON.stringify({
|
|
278
316
|
level: "warn",
|
|
@@ -280,16 +318,19 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
280
318
|
message: e instanceof Error ? e.message : String(e),
|
|
281
319
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
282
320
|
})));
|
|
321
|
+
this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), true);
|
|
283
322
|
throw err;
|
|
284
323
|
}
|
|
285
324
|
}
|
|
286
325
|
async resume(id) {
|
|
287
326
|
const file = this.sessionPath(id, ".jsonl");
|
|
327
|
+
const t0 = Date.now();
|
|
288
328
|
const data = await this.load(id);
|
|
289
329
|
let handle;
|
|
290
330
|
try {
|
|
291
331
|
handle = await fsp.open(file, "a", 384);
|
|
292
332
|
} catch (err) {
|
|
333
|
+
this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), false);
|
|
293
334
|
throw new Error(
|
|
294
335
|
`Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
|
|
295
336
|
{ cause: err }
|
|
@@ -317,6 +358,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
317
358
|
onClose: (s) => this.appendToIndex(s)
|
|
318
359
|
}
|
|
319
360
|
);
|
|
361
|
+
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
320
362
|
return { writer, data };
|
|
321
363
|
} catch (err) {
|
|
322
364
|
await handle.close().catch((e) => console.warn(JSON.stringify({
|
|
@@ -325,27 +367,39 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
325
367
|
message: e instanceof Error ? e.message : String(e),
|
|
326
368
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
327
369
|
})));
|
|
370
|
+
this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), true);
|
|
328
371
|
throw err;
|
|
329
372
|
}
|
|
330
373
|
}
|
|
331
374
|
async load(id) {
|
|
332
375
|
const file = this.sessionPath(id, ".jsonl");
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
376
|
+
const t0 = Date.now();
|
|
377
|
+
let outcome = "success";
|
|
378
|
+
let errorMsg;
|
|
379
|
+
try {
|
|
380
|
+
const raw = await fsp.readFile(file, "utf8");
|
|
381
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
382
|
+
const events = [];
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
try {
|
|
385
|
+
const parsed = JSON.parse(line);
|
|
386
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
387
|
+
events.push(parsed);
|
|
388
|
+
}
|
|
389
|
+
} catch {
|
|
341
390
|
}
|
|
342
|
-
} catch {
|
|
343
391
|
}
|
|
392
|
+
const meta = this.metaFromEvents(id, events);
|
|
393
|
+
const { messages, usage } = this.replay(events, id);
|
|
394
|
+
const toolCallEnds = extractToolCallEnds(events);
|
|
395
|
+
return { metadata: meta, events, messages, usage, toolCallEnds };
|
|
396
|
+
} catch (err) {
|
|
397
|
+
outcome = "failure";
|
|
398
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
399
|
+
throw err;
|
|
400
|
+
} finally {
|
|
401
|
+
this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
|
|
344
402
|
}
|
|
345
|
-
const meta = this.metaFromEvents(id, events);
|
|
346
|
-
const { messages, usage } = this.replay(events, id);
|
|
347
|
-
const toolCallEnds = extractToolCallEnds(events);
|
|
348
|
-
return { metadata: meta, events, messages, usage, toolCallEnds };
|
|
349
403
|
}
|
|
350
404
|
async list(limit = 20) {
|
|
351
405
|
try {
|
|
@@ -412,12 +466,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
412
466
|
* (keep latest per session), and rewrite. Atomic via temp+rename.
|
|
413
467
|
*/
|
|
414
468
|
async compactIndex() {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
469
|
+
const t0 = Date.now();
|
|
470
|
+
let outcome = "success";
|
|
471
|
+
let errorMsg;
|
|
472
|
+
try {
|
|
473
|
+
const entries = await this.readIndex();
|
|
474
|
+
if (entries.length === 0) return;
|
|
475
|
+
const tmp = `${this.indexFile}.compact.tmp`;
|
|
476
|
+
const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
477
|
+
await fsp.writeFile(tmp, lines, "utf8");
|
|
478
|
+
await fsp.rename(tmp, this.indexFile);
|
|
479
|
+
} catch (err) {
|
|
480
|
+
outcome = "failure";
|
|
481
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
482
|
+
} finally {
|
|
483
|
+
this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
|
|
484
|
+
}
|
|
421
485
|
}
|
|
422
486
|
/**
|
|
423
487
|
* Read the index file and return deduplicated session summaries.
|
|
@@ -493,22 +557,31 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
493
557
|
}
|
|
494
558
|
async summaryFor(id) {
|
|
495
559
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
560
|
+
const t0 = Date.now();
|
|
561
|
+
let outcome = "success";
|
|
562
|
+
let errorMsg;
|
|
496
563
|
try {
|
|
497
564
|
const raw = await fsp.readFile(manifest, "utf8");
|
|
565
|
+
this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
|
|
498
566
|
return JSON.parse(raw);
|
|
499
567
|
} catch {
|
|
500
568
|
const full = this.sessionPath(id, ".jsonl");
|
|
501
569
|
const stat5 = await fsp.stat(full);
|
|
502
570
|
const summary = await this.summarize(id, stat5.mtime.toISOString());
|
|
503
571
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
572
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
573
|
+
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
504
574
|
console.warn(JSON.stringify({
|
|
505
575
|
level: "warn",
|
|
506
576
|
event: "session_store.manifest_write_failed",
|
|
507
577
|
sessionId: id,
|
|
508
|
-
message:
|
|
578
|
+
message: msg,
|
|
509
579
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
510
580
|
}));
|
|
511
581
|
});
|
|
582
|
+
outcome = "failure";
|
|
583
|
+
errorMsg = "summary fallback \u2014 manifest rebuilt";
|
|
584
|
+
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
512
585
|
return summary;
|
|
513
586
|
}
|
|
514
587
|
}
|
|
@@ -774,7 +847,7 @@ function extractToolCallEnds(events) {
|
|
|
774
847
|
return result;
|
|
775
848
|
}
|
|
776
849
|
var FileSessionWriter = class _FileSessionWriter {
|
|
777
|
-
constructor(id, handle, startedAt, meta, events, opts = {}) {
|
|
850
|
+
constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
|
|
778
851
|
this.id = id;
|
|
779
852
|
this.handle = handle;
|
|
780
853
|
this.startedAt = startedAt;
|
|
@@ -793,6 +866,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
793
866
|
provider: meta.provider ?? "unknown",
|
|
794
867
|
tokenTotal: 0
|
|
795
868
|
};
|
|
869
|
+
this.traceId = traceId;
|
|
796
870
|
}
|
|
797
871
|
id;
|
|
798
872
|
handle;
|
|
@@ -825,6 +899,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
825
899
|
lastAppendWarnAt = 0;
|
|
826
900
|
secretScrubber;
|
|
827
901
|
onCloseCb;
|
|
902
|
+
/** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
|
|
903
|
+
traceId;
|
|
828
904
|
// ── Write buffer — batches events to reduce per-event disk I/O ─────────
|
|
829
905
|
//
|
|
830
906
|
// Every append() pushes the scrubbed event into an in-memory buffer instead
|
|
@@ -978,9 +1054,14 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
978
1054
|
const eventCount = this.writeBuffer.length;
|
|
979
1055
|
const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
980
1056
|
this.writeBuffer = [];
|
|
1057
|
+
const t0 = Date.now();
|
|
1058
|
+
let outcome = "success";
|
|
1059
|
+
let errorMsg;
|
|
981
1060
|
try {
|
|
982
1061
|
await this.enqueueWrite(batch);
|
|
983
1062
|
} catch (err) {
|
|
1063
|
+
outcome = "failure";
|
|
1064
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
984
1065
|
this.appendFailCount += eventCount;
|
|
985
1066
|
const now = Date.now();
|
|
986
1067
|
if (now - this.lastAppendWarnAt > 5e3) {
|
|
@@ -994,6 +1075,18 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
994
1075
|
this.lastAppendWarnAt = now;
|
|
995
1076
|
this.appendFailCount = 0;
|
|
996
1077
|
}
|
|
1078
|
+
} finally {
|
|
1079
|
+
this.events?.emit("storage.write", {
|
|
1080
|
+
sessionId: this.id,
|
|
1081
|
+
store: "session",
|
|
1082
|
+
filePath: this.filePath,
|
|
1083
|
+
operation: "flush",
|
|
1084
|
+
outcome,
|
|
1085
|
+
durationMs: Date.now() - t0,
|
|
1086
|
+
...errorMsg !== void 0 ? { error: errorMsg } : {},
|
|
1087
|
+
...eventCount !== void 0 ? { eventCount } : {},
|
|
1088
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
1089
|
+
});
|
|
997
1090
|
}
|
|
998
1091
|
}
|
|
999
1092
|
observeForSummary(event) {
|
|
@@ -1059,14 +1152,46 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1059
1152
|
outcome: this.outcome ?? "completed"
|
|
1060
1153
|
};
|
|
1061
1154
|
if (this.manifestFile) {
|
|
1155
|
+
const t0 = Date.now();
|
|
1156
|
+
let outcome = "success";
|
|
1157
|
+
let errorMsg;
|
|
1062
1158
|
try {
|
|
1063
1159
|
await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
|
|
1064
|
-
} catch {
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
outcome = "failure";
|
|
1162
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
1163
|
+
} finally {
|
|
1164
|
+
this.events?.emit("storage.write", {
|
|
1165
|
+
sessionId: this.id,
|
|
1166
|
+
store: "session",
|
|
1167
|
+
filePath: this.manifestFile,
|
|
1168
|
+
operation: "close",
|
|
1169
|
+
outcome,
|
|
1170
|
+
durationMs: Date.now() - t0,
|
|
1171
|
+
...errorMsg !== void 0 ? { error: errorMsg } : {},
|
|
1172
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
1173
|
+
});
|
|
1065
1174
|
}
|
|
1066
1175
|
}
|
|
1176
|
+
const idxT0 = Date.now();
|
|
1177
|
+
let idxOutcome = "success";
|
|
1178
|
+
let idxError;
|
|
1067
1179
|
try {
|
|
1068
1180
|
await this.onCloseCb?.(this.summary);
|
|
1069
|
-
} catch {
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
idxOutcome = "failure";
|
|
1183
|
+
idxError = err instanceof Error ? err.message : String(err);
|
|
1184
|
+
} finally {
|
|
1185
|
+
this.events?.emit("storage.write", {
|
|
1186
|
+
sessionId: this.summary.id,
|
|
1187
|
+
store: "session",
|
|
1188
|
+
filePath: this.filePath,
|
|
1189
|
+
operation: "index_append",
|
|
1190
|
+
outcome: idxOutcome,
|
|
1191
|
+
durationMs: Date.now() - idxT0,
|
|
1192
|
+
...idxError !== void 0 ? { error: idxError } : {},
|
|
1193
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
1194
|
+
});
|
|
1070
1195
|
}
|
|
1071
1196
|
try {
|
|
1072
1197
|
await this.handle.close();
|
|
@@ -1225,23 +1350,81 @@ function userInputTitle(content) {
|
|
|
1225
1350
|
}
|
|
1226
1351
|
var QueueStore = class {
|
|
1227
1352
|
file;
|
|
1353
|
+
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
1354
|
+
// reject assigning an optional constructor parameter to these fields.
|
|
1355
|
+
events;
|
|
1356
|
+
traceId;
|
|
1228
1357
|
constructor(opts) {
|
|
1229
1358
|
this.file = path13.join(opts.dir, "queue.json");
|
|
1359
|
+
this.events = opts.events;
|
|
1360
|
+
this.traceId = opts.traceId;
|
|
1230
1361
|
}
|
|
1231
1362
|
async write(items) {
|
|
1363
|
+
const t0 = Date.now();
|
|
1232
1364
|
if (items.length === 0) {
|
|
1233
1365
|
await this.clear();
|
|
1234
1366
|
return;
|
|
1235
1367
|
}
|
|
1236
|
-
|
|
1368
|
+
try {
|
|
1369
|
+
await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
|
|
1370
|
+
this.events?.emit("storage.write", {
|
|
1371
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1372
|
+
store: "queue",
|
|
1373
|
+
filePath: this.file,
|
|
1374
|
+
operation: "write",
|
|
1375
|
+
outcome: "success",
|
|
1376
|
+
durationMs: Date.now() - t0,
|
|
1377
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1378
|
+
});
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
this.events?.emit("storage.error", {
|
|
1381
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1382
|
+
store: "queue",
|
|
1383
|
+
filePath: this.file,
|
|
1384
|
+
operation: "write",
|
|
1385
|
+
outcome: "failure",
|
|
1386
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1387
|
+
recoverable: false,
|
|
1388
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1389
|
+
});
|
|
1390
|
+
console.warn(JSON.stringify({
|
|
1391
|
+
level: "warn",
|
|
1392
|
+
event: "queue_store.write_failed",
|
|
1393
|
+
path: this.file,
|
|
1394
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1395
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1396
|
+
}));
|
|
1397
|
+
}
|
|
1237
1398
|
}
|
|
1238
1399
|
async read() {
|
|
1400
|
+
const t0 = Date.now();
|
|
1239
1401
|
let raw;
|
|
1240
1402
|
try {
|
|
1241
1403
|
raw = await fsp.readFile(this.file, "utf8");
|
|
1242
1404
|
} catch (err) {
|
|
1243
1405
|
const code = err.code;
|
|
1244
|
-
if (code === "ENOENT")
|
|
1406
|
+
if (code === "ENOENT") {
|
|
1407
|
+
this.events?.emit("storage.read", {
|
|
1408
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1409
|
+
store: "queue",
|
|
1410
|
+
filePath: this.file,
|
|
1411
|
+
operation: "read",
|
|
1412
|
+
outcome: "success",
|
|
1413
|
+
durationMs: Date.now() - t0,
|
|
1414
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1415
|
+
});
|
|
1416
|
+
return [];
|
|
1417
|
+
}
|
|
1418
|
+
this.events?.emit("storage.error", {
|
|
1419
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1420
|
+
store: "queue",
|
|
1421
|
+
filePath: this.file,
|
|
1422
|
+
operation: "read",
|
|
1423
|
+
outcome: "failure",
|
|
1424
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1425
|
+
recoverable: true,
|
|
1426
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1427
|
+
});
|
|
1245
1428
|
console.warn(JSON.stringify({
|
|
1246
1429
|
level: "warn",
|
|
1247
1430
|
event: "queue_store.read_failed",
|
|
@@ -1255,9 +1438,40 @@ var QueueStore = class {
|
|
|
1255
1438
|
try {
|
|
1256
1439
|
parsed = JSON.parse(raw);
|
|
1257
1440
|
} catch {
|
|
1441
|
+
this.events?.emit("storage.read", {
|
|
1442
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1443
|
+
store: "queue",
|
|
1444
|
+
filePath: this.file,
|
|
1445
|
+
operation: "read",
|
|
1446
|
+
outcome: "failure",
|
|
1447
|
+
durationMs: Date.now() - t0,
|
|
1448
|
+
error: "parse_failed",
|
|
1449
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1450
|
+
});
|
|
1258
1451
|
return [];
|
|
1259
1452
|
}
|
|
1260
|
-
if (!Array.isArray(parsed))
|
|
1453
|
+
if (!Array.isArray(parsed)) {
|
|
1454
|
+
this.events?.emit("storage.read", {
|
|
1455
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1456
|
+
store: "queue",
|
|
1457
|
+
filePath: this.file,
|
|
1458
|
+
operation: "read",
|
|
1459
|
+
outcome: "failure",
|
|
1460
|
+
durationMs: Date.now() - t0,
|
|
1461
|
+
error: "invalid_schema",
|
|
1462
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1463
|
+
});
|
|
1464
|
+
return [];
|
|
1465
|
+
}
|
|
1466
|
+
this.events?.emit("storage.read", {
|
|
1467
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1468
|
+
store: "queue",
|
|
1469
|
+
filePath: this.file,
|
|
1470
|
+
operation: "read",
|
|
1471
|
+
outcome: "success",
|
|
1472
|
+
durationMs: Date.now() - t0,
|
|
1473
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1474
|
+
});
|
|
1261
1475
|
const out = [];
|
|
1262
1476
|
for (const v of parsed) {
|
|
1263
1477
|
if (isPersistedQueueItem(v)) out.push(v);
|
|
@@ -1265,11 +1479,31 @@ var QueueStore = class {
|
|
|
1265
1479
|
return out;
|
|
1266
1480
|
}
|
|
1267
1481
|
async clear() {
|
|
1482
|
+
const t0 = Date.now();
|
|
1268
1483
|
try {
|
|
1269
1484
|
await fsp.unlink(this.file);
|
|
1485
|
+
this.events?.emit("storage.write", {
|
|
1486
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1487
|
+
store: "queue",
|
|
1488
|
+
filePath: this.file,
|
|
1489
|
+
operation: "clear",
|
|
1490
|
+
outcome: "success",
|
|
1491
|
+
durationMs: Date.now() - t0,
|
|
1492
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1493
|
+
});
|
|
1270
1494
|
} catch (err) {
|
|
1271
1495
|
const code = err.code;
|
|
1272
1496
|
if (code === "ENOENT") return;
|
|
1497
|
+
this.events?.emit("storage.error", {
|
|
1498
|
+
sessionId: this.traceId ?? "~boot~",
|
|
1499
|
+
store: "queue",
|
|
1500
|
+
filePath: this.file,
|
|
1501
|
+
operation: "clear",
|
|
1502
|
+
outcome: "failure",
|
|
1503
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1504
|
+
recoverable: true,
|
|
1505
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1506
|
+
});
|
|
1273
1507
|
console.warn(JSON.stringify({
|
|
1274
1508
|
level: "warn",
|
|
1275
1509
|
event: "queue_store.clear_failed",
|
|
@@ -1652,6 +1886,7 @@ var MAX_BYTES_TOTAL = 32e3;
|
|
|
1652
1886
|
var DefaultMemoryStore = class {
|
|
1653
1887
|
files;
|
|
1654
1888
|
events;
|
|
1889
|
+
traceId;
|
|
1655
1890
|
backend;
|
|
1656
1891
|
/**
|
|
1657
1892
|
* Per-scope serialization queue. `remember` / `forget` / `consolidate` /
|
|
@@ -1717,15 +1952,70 @@ var DefaultMemoryStore = class {
|
|
|
1717
1952
|
if (writeErr) {
|
|
1718
1953
|
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
|
|
1719
1954
|
}
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1955
|
+
const t0 = Date.now();
|
|
1956
|
+
const filePath = this.files[scope];
|
|
1957
|
+
try {
|
|
1958
|
+
const body = await this.backend.readAll(scope, filePath);
|
|
1959
|
+
const dur = Date.now() - t0;
|
|
1960
|
+
this.events?.emit("storage.read", {
|
|
1961
|
+
sessionId: "~memory~",
|
|
1962
|
+
store: "memory",
|
|
1963
|
+
filePath,
|
|
1964
|
+
operation: "readAll",
|
|
1965
|
+
outcome: "success",
|
|
1966
|
+
durationMs: dur,
|
|
1967
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1968
|
+
});
|
|
1969
|
+
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
1722
1970
|
|
|
1723
1971
|
${body.trim()}`);
|
|
1972
|
+
} catch (err) {
|
|
1973
|
+
const dur = Date.now() - t0;
|
|
1974
|
+
this.events?.emit("storage.read", {
|
|
1975
|
+
sessionId: "~memory~",
|
|
1976
|
+
store: "memory",
|
|
1977
|
+
filePath,
|
|
1978
|
+
operation: "readAll",
|
|
1979
|
+
outcome: "failure",
|
|
1980
|
+
durationMs: dur,
|
|
1981
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1982
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1983
|
+
});
|
|
1984
|
+
throw err;
|
|
1985
|
+
}
|
|
1724
1986
|
}
|
|
1725
1987
|
return parts.join("\n\n");
|
|
1726
1988
|
}
|
|
1727
1989
|
async read(scope) {
|
|
1728
|
-
|
|
1990
|
+
const t0 = Date.now();
|
|
1991
|
+
const filePath = this.files[scope];
|
|
1992
|
+
try {
|
|
1993
|
+
const body = await this.backend.readAll(scope, filePath);
|
|
1994
|
+
const dur = Date.now() - t0;
|
|
1995
|
+
this.events?.emit("storage.read", {
|
|
1996
|
+
sessionId: "~memory~",
|
|
1997
|
+
store: "memory",
|
|
1998
|
+
filePath,
|
|
1999
|
+
operation: "read",
|
|
2000
|
+
outcome: "success",
|
|
2001
|
+
durationMs: dur,
|
|
2002
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2003
|
+
});
|
|
2004
|
+
return body;
|
|
2005
|
+
} catch (err) {
|
|
2006
|
+
const dur = Date.now() - t0;
|
|
2007
|
+
this.events?.emit("storage.read", {
|
|
2008
|
+
sessionId: "~memory~",
|
|
2009
|
+
store: "memory",
|
|
2010
|
+
filePath,
|
|
2011
|
+
operation: "read",
|
|
2012
|
+
outcome: "failure",
|
|
2013
|
+
durationMs: dur,
|
|
2014
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2015
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2016
|
+
});
|
|
2017
|
+
throw err;
|
|
2018
|
+
}
|
|
1729
2019
|
}
|
|
1730
2020
|
/**
|
|
1731
2021
|
* List entries from a scope, newest first. Delegates to the backend
|
|
@@ -1751,7 +2041,34 @@ ${body.trim()}`);
|
|
|
1751
2041
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1752
2042
|
return this.runSerialized(scope, async () => {
|
|
1753
2043
|
const entry = { scope, text, ts, ...metadata };
|
|
1754
|
-
|
|
2044
|
+
const filePath = this.files[scope];
|
|
2045
|
+
const t0 = Date.now();
|
|
2046
|
+
try {
|
|
2047
|
+
await this.backend.remember(scope, entry, filePath);
|
|
2048
|
+
const dur = Date.now() - t0;
|
|
2049
|
+
this.events?.emit("storage.write", {
|
|
2050
|
+
sessionId: "~memory~",
|
|
2051
|
+
store: "memory",
|
|
2052
|
+
filePath,
|
|
2053
|
+
operation: "remember",
|
|
2054
|
+
outcome: "success",
|
|
2055
|
+
durationMs: dur,
|
|
2056
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2057
|
+
});
|
|
2058
|
+
} catch (err) {
|
|
2059
|
+
const dur = Date.now() - t0;
|
|
2060
|
+
this.events?.emit("storage.write", {
|
|
2061
|
+
sessionId: "~memory~",
|
|
2062
|
+
store: "memory",
|
|
2063
|
+
filePath,
|
|
2064
|
+
operation: "remember",
|
|
2065
|
+
outcome: "failure",
|
|
2066
|
+
durationMs: dur,
|
|
2067
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2068
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2069
|
+
});
|
|
2070
|
+
throw err;
|
|
2071
|
+
}
|
|
1755
2072
|
const raw = await this.backend.readAll(scope, this.files[scope]);
|
|
1756
2073
|
if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
|
|
1757
2074
|
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
@@ -1879,7 +2196,35 @@ ${body.trim()}`);
|
|
|
1879
2196
|
}
|
|
1880
2197
|
async forget(query, scope = "project-memory") {
|
|
1881
2198
|
return this.runSerialized(scope, async () => {
|
|
1882
|
-
const
|
|
2199
|
+
const filePath = this.files[scope];
|
|
2200
|
+
const t0 = Date.now();
|
|
2201
|
+
let removed = 0;
|
|
2202
|
+
try {
|
|
2203
|
+
removed = await this.backend.forget(scope, query, filePath);
|
|
2204
|
+
const dur = Date.now() - t0;
|
|
2205
|
+
this.events?.emit("storage.write", {
|
|
2206
|
+
sessionId: "~memory~",
|
|
2207
|
+
store: "memory",
|
|
2208
|
+
filePath,
|
|
2209
|
+
operation: "forget",
|
|
2210
|
+
outcome: "success",
|
|
2211
|
+
durationMs: dur,
|
|
2212
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2213
|
+
});
|
|
2214
|
+
} catch (err) {
|
|
2215
|
+
const dur = Date.now() - t0;
|
|
2216
|
+
this.events?.emit("storage.write", {
|
|
2217
|
+
sessionId: "~memory~",
|
|
2218
|
+
store: "memory",
|
|
2219
|
+
filePath,
|
|
2220
|
+
operation: "forget",
|
|
2221
|
+
outcome: "failure",
|
|
2222
|
+
durationMs: dur,
|
|
2223
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2224
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2225
|
+
});
|
|
2226
|
+
throw err;
|
|
2227
|
+
}
|
|
1883
2228
|
if (removed > 0) {
|
|
1884
2229
|
this.events?.emit("memory.forgotten", {
|
|
1885
2230
|
scope,
|
|
@@ -1893,7 +2238,35 @@ ${body.trim()}`);
|
|
|
1893
2238
|
}
|
|
1894
2239
|
async consolidate(scope) {
|
|
1895
2240
|
return this.runSerialized(scope, async () => {
|
|
1896
|
-
const
|
|
2241
|
+
const filePath = this.files[scope];
|
|
2242
|
+
const t0 = Date.now();
|
|
2243
|
+
let removed = 0;
|
|
2244
|
+
try {
|
|
2245
|
+
removed = await this.backend.consolidate(scope, filePath);
|
|
2246
|
+
const dur = Date.now() - t0;
|
|
2247
|
+
this.events?.emit("storage.write", {
|
|
2248
|
+
sessionId: "~memory~",
|
|
2249
|
+
store: "memory",
|
|
2250
|
+
filePath,
|
|
2251
|
+
operation: "consolidate",
|
|
2252
|
+
outcome: "success",
|
|
2253
|
+
durationMs: dur,
|
|
2254
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2255
|
+
});
|
|
2256
|
+
} catch (err) {
|
|
2257
|
+
const dur = Date.now() - t0;
|
|
2258
|
+
this.events?.emit("storage.write", {
|
|
2259
|
+
sessionId: "~memory~",
|
|
2260
|
+
store: "memory",
|
|
2261
|
+
filePath,
|
|
2262
|
+
operation: "consolidate",
|
|
2263
|
+
outcome: "failure",
|
|
2264
|
+
durationMs: dur,
|
|
2265
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2266
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2267
|
+
});
|
|
2268
|
+
throw err;
|
|
2269
|
+
}
|
|
1897
2270
|
if (removed > 0) {
|
|
1898
2271
|
this.events?.emit("memory.consolidated", {
|
|
1899
2272
|
scope,
|
|
@@ -1906,7 +2279,34 @@ ${body.trim()}`);
|
|
|
1906
2279
|
async clear(scope) {
|
|
1907
2280
|
if (scope) {
|
|
1908
2281
|
await this.runSerialized(scope, async () => {
|
|
1909
|
-
|
|
2282
|
+
const filePath = this.files[scope];
|
|
2283
|
+
const t0 = Date.now();
|
|
2284
|
+
try {
|
|
2285
|
+
await this.backend.clear(scope, filePath);
|
|
2286
|
+
const dur = Date.now() - t0;
|
|
2287
|
+
this.events?.emit("storage.write", {
|
|
2288
|
+
sessionId: "~memory~",
|
|
2289
|
+
store: "memory",
|
|
2290
|
+
filePath,
|
|
2291
|
+
operation: "clear",
|
|
2292
|
+
outcome: "success",
|
|
2293
|
+
durationMs: dur,
|
|
2294
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2295
|
+
});
|
|
2296
|
+
} catch (err) {
|
|
2297
|
+
const dur = Date.now() - t0;
|
|
2298
|
+
this.events?.emit("storage.write", {
|
|
2299
|
+
sessionId: "~memory~",
|
|
2300
|
+
store: "memory",
|
|
2301
|
+
filePath,
|
|
2302
|
+
operation: "clear",
|
|
2303
|
+
outcome: "failure",
|
|
2304
|
+
durationMs: dur,
|
|
2305
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2306
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2307
|
+
});
|
|
2308
|
+
throw err;
|
|
2309
|
+
}
|
|
1910
2310
|
this.events?.emit("memory.cleared", { scope });
|
|
1911
2311
|
await this.mirrorBackup(scope);
|
|
1912
2312
|
});
|
|
@@ -1914,15 +2314,54 @@ ${body.trim()}`);
|
|
|
1914
2314
|
}
|
|
1915
2315
|
await Promise.all(
|
|
1916
2316
|
["project-agents", "project-memory", "user-memory"].map(
|
|
1917
|
-
(s) => this.runSerialized(s, async () => {
|
|
1918
|
-
|
|
2317
|
+
async (s) => this.runSerialized(s, async () => {
|
|
2318
|
+
const filePath = this.files[s];
|
|
2319
|
+
const t0 = Date.now();
|
|
2320
|
+
try {
|
|
2321
|
+
await this.backend.clear(s, filePath);
|
|
2322
|
+
const dur = Date.now() - t0;
|
|
2323
|
+
this.events?.emit("storage.write", {
|
|
2324
|
+
sessionId: "~memory~",
|
|
2325
|
+
store: "memory",
|
|
2326
|
+
filePath,
|
|
2327
|
+
operation: "clear",
|
|
2328
|
+
outcome: "success",
|
|
2329
|
+
durationMs: dur,
|
|
2330
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2331
|
+
});
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
const dur = Date.now() - t0;
|
|
2334
|
+
this.events?.emit("storage.write", {
|
|
2335
|
+
sessionId: "~memory~",
|
|
2336
|
+
store: "memory",
|
|
2337
|
+
filePath,
|
|
2338
|
+
operation: "clear",
|
|
2339
|
+
outcome: "failure",
|
|
2340
|
+
durationMs: dur,
|
|
2341
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2342
|
+
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2343
|
+
});
|
|
2344
|
+
throw err;
|
|
2345
|
+
}
|
|
1919
2346
|
this.events?.emit("memory.cleared", { scope: s });
|
|
1920
2347
|
await this.mirrorBackup(s);
|
|
1921
2348
|
})
|
|
1922
2349
|
)
|
|
1923
2350
|
);
|
|
1924
2351
|
}
|
|
1925
|
-
/**
|
|
2352
|
+
/**
|
|
2353
|
+
* Return a new MemoryStore proxy that carries `traceId` on every storage
|
|
2354
|
+
* event. The original store is left unchanged — callers that need a
|
|
2355
|
+
* trace-decorated view (e.g. session-run tools) receive the proxy while
|
|
2356
|
+
* the singleton remains trace-free for boot-time use.
|
|
2357
|
+
*
|
|
2358
|
+
* The proxy implements the full `MemoryStore` interface; all other
|
|
2359
|
+
* properties (backend, etc.) are delegated to the original store.
|
|
2360
|
+
*/
|
|
2361
|
+
withTraceId(traceId) {
|
|
2362
|
+
this.traceId = traceId;
|
|
2363
|
+
return this;
|
|
2364
|
+
}
|
|
1926
2365
|
async mirrorBackup(scope) {
|
|
1927
2366
|
if (!this.persistBackup || scope === "project-agents") return;
|
|
1928
2367
|
try {
|
|
@@ -2673,6 +3112,13 @@ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
|
|
|
2673
3112
|
});
|
|
2674
3113
|
|
|
2675
3114
|
// src/storage/config-loader.ts
|
|
3115
|
+
function storageErrorString(err) {
|
|
3116
|
+
if (err instanceof Error) {
|
|
3117
|
+
const code = err.code;
|
|
3118
|
+
return code ? `${code}: ${err.message}` : err.message;
|
|
3119
|
+
}
|
|
3120
|
+
return String(err);
|
|
3121
|
+
}
|
|
2676
3122
|
var BEHAVIOR_DEFAULTS = {
|
|
2677
3123
|
version: 1,
|
|
2678
3124
|
context: {
|
|
@@ -2775,11 +3221,15 @@ var DefaultConfigLoader = class {
|
|
|
2775
3221
|
strict;
|
|
2776
3222
|
vault;
|
|
2777
3223
|
extraSources;
|
|
3224
|
+
events;
|
|
3225
|
+
traceId;
|
|
2778
3226
|
constructor(opts) {
|
|
2779
3227
|
this.paths = opts.paths;
|
|
2780
3228
|
this.strict = opts.strict ?? false;
|
|
2781
3229
|
this.vault = opts.vault;
|
|
2782
3230
|
this.extraSources = opts.sources ?? [];
|
|
3231
|
+
this.events = opts.events;
|
|
3232
|
+
this.traceId = opts.traceId;
|
|
2783
3233
|
}
|
|
2784
3234
|
async load(opts = {}) {
|
|
2785
3235
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
@@ -2856,7 +3306,33 @@ var DefaultConfigLoader = class {
|
|
|
2856
3306
|
if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
|
|
2857
3307
|
toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
|
|
2858
3308
|
}
|
|
2859
|
-
|
|
3309
|
+
const fp = this.paths.syncConfig;
|
|
3310
|
+
const t0 = Date.now();
|
|
3311
|
+
try {
|
|
3312
|
+
await atomicWrite(fp, JSON.stringify(toWrite, null, 2), { mode: 384 });
|
|
3313
|
+
this.events?.emit("storage.write", {
|
|
3314
|
+
sessionId: "~config~",
|
|
3315
|
+
store: "config",
|
|
3316
|
+
filePath: fp,
|
|
3317
|
+
operation: "persist_sync",
|
|
3318
|
+
outcome: "success",
|
|
3319
|
+
durationMs: Date.now() - t0,
|
|
3320
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3321
|
+
});
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
this.events?.emit("storage.error", {
|
|
3324
|
+
sessionId: "~config~",
|
|
3325
|
+
store: "config",
|
|
3326
|
+
filePath: fp,
|
|
3327
|
+
operation: "persist_sync",
|
|
3328
|
+
outcome: "failure",
|
|
3329
|
+
error: storageErrorString(err),
|
|
3330
|
+
recoverable: false,
|
|
3331
|
+
durationMs: Date.now() - t0,
|
|
3332
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3333
|
+
});
|
|
3334
|
+
throw err;
|
|
3335
|
+
}
|
|
2860
3336
|
}
|
|
2861
3337
|
/**
|
|
2862
3338
|
* Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
|
|
@@ -2865,17 +3341,60 @@ var DefaultConfigLoader = class {
|
|
|
2865
3341
|
* isolated — it should never be part of project-local or env-driven config.
|
|
2866
3342
|
*/
|
|
2867
3343
|
async loadSyncConfig() {
|
|
3344
|
+
const fp = this.paths.syncConfig;
|
|
3345
|
+
const t0 = Date.now();
|
|
2868
3346
|
try {
|
|
2869
|
-
const raw = await fsp.readFile(
|
|
3347
|
+
const raw = await fsp.readFile(fp, "utf8");
|
|
2870
3348
|
const parsed = safeParse(raw);
|
|
2871
|
-
if (!parsed.ok || !parsed.value)
|
|
3349
|
+
if (!parsed.ok || !parsed.value) {
|
|
3350
|
+
this.events?.emit("storage.read", {
|
|
3351
|
+
sessionId: "~config~",
|
|
3352
|
+
store: "config",
|
|
3353
|
+
filePath: fp,
|
|
3354
|
+
operation: "load_sync",
|
|
3355
|
+
outcome: "failure",
|
|
3356
|
+
durationMs: Date.now() - t0,
|
|
3357
|
+
error: "parse error or empty file",
|
|
3358
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3359
|
+
});
|
|
3360
|
+
return null;
|
|
3361
|
+
}
|
|
2872
3362
|
if (this.vault) {
|
|
2873
3363
|
const decrypted = decryptConfigSecrets({ sync: parsed.value }, this.vault);
|
|
2874
|
-
|
|
3364
|
+
const result = decrypted.sync ?? null;
|
|
3365
|
+
this.events?.emit("storage.read", {
|
|
3366
|
+
sessionId: "~config~",
|
|
3367
|
+
store: "config",
|
|
3368
|
+
filePath: fp,
|
|
3369
|
+
operation: "load_sync",
|
|
3370
|
+
outcome: "success",
|
|
3371
|
+
durationMs: Date.now() - t0,
|
|
3372
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3373
|
+
});
|
|
3374
|
+
return result;
|
|
2875
3375
|
}
|
|
3376
|
+
this.events?.emit("storage.read", {
|
|
3377
|
+
sessionId: "~config~",
|
|
3378
|
+
store: "config",
|
|
3379
|
+
filePath: fp,
|
|
3380
|
+
operation: "load_sync",
|
|
3381
|
+
outcome: "success",
|
|
3382
|
+
durationMs: Date.now() - t0,
|
|
3383
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3384
|
+
});
|
|
2876
3385
|
return parsed.value;
|
|
2877
3386
|
} catch (err) {
|
|
2878
3387
|
if (err.code === "ENOENT") return null;
|
|
3388
|
+
this.events?.emit("storage.read", {
|
|
3389
|
+
sessionId: "~config~",
|
|
3390
|
+
store: "config",
|
|
3391
|
+
filePath: fp,
|
|
3392
|
+
operation: "load_sync",
|
|
3393
|
+
outcome: "failure",
|
|
3394
|
+
durationMs: Date.now() - t0,
|
|
3395
|
+
error: storageErrorString(err),
|
|
3396
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3397
|
+
});
|
|
2879
3398
|
console.warn(JSON.stringify({
|
|
2880
3399
|
level: "warn",
|
|
2881
3400
|
event: "config.sync_load_failed",
|
|
@@ -2887,10 +3406,21 @@ var DefaultConfigLoader = class {
|
|
|
2887
3406
|
}
|
|
2888
3407
|
async readJson(file) {
|
|
2889
3408
|
let raw;
|
|
3409
|
+
const t0 = Date.now();
|
|
2890
3410
|
try {
|
|
2891
3411
|
raw = await fsp.readFile(file, "utf8");
|
|
2892
3412
|
} catch (err) {
|
|
2893
3413
|
if (err.code !== "ENOENT") {
|
|
3414
|
+
this.events?.emit("storage.read", {
|
|
3415
|
+
sessionId: "~config~",
|
|
3416
|
+
store: "config",
|
|
3417
|
+
filePath: file,
|
|
3418
|
+
operation: "read_json",
|
|
3419
|
+
outcome: "failure",
|
|
3420
|
+
durationMs: Date.now() - t0,
|
|
3421
|
+
error: storageErrorString(err),
|
|
3422
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3423
|
+
});
|
|
2894
3424
|
console.warn(JSON.stringify({
|
|
2895
3425
|
level: "warn",
|
|
2896
3426
|
event: "config.read_failed",
|
|
@@ -2903,6 +3433,16 @@ var DefaultConfigLoader = class {
|
|
|
2903
3433
|
}
|
|
2904
3434
|
const parsed = safeParse(raw);
|
|
2905
3435
|
if (!parsed.ok || !parsed.value) {
|
|
3436
|
+
this.events?.emit("storage.read", {
|
|
3437
|
+
sessionId: "~config~",
|
|
3438
|
+
store: "config",
|
|
3439
|
+
filePath: file,
|
|
3440
|
+
operation: "read_json",
|
|
3441
|
+
outcome: "failure",
|
|
3442
|
+
durationMs: Date.now() - t0,
|
|
3443
|
+
error: "parse error or empty file",
|
|
3444
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
3445
|
+
});
|
|
2906
3446
|
console.warn(JSON.stringify({
|
|
2907
3447
|
level: "warn",
|
|
2908
3448
|
event: "config.parse_failed",
|
|
@@ -3501,20 +4041,53 @@ var MAX_TEXT_LENGTH = 2e3;
|
|
|
3501
4041
|
var MAX_ANNOTATIONS = 1e3;
|
|
3502
4042
|
var AnnotationsStore = class {
|
|
3503
4043
|
dir;
|
|
4044
|
+
events;
|
|
4045
|
+
traceId;
|
|
3504
4046
|
/** Per-session write queue. Created lazily on first add. */
|
|
3505
4047
|
writeChains = /* @__PURE__ */ new Map();
|
|
3506
4048
|
constructor(opts) {
|
|
3507
4049
|
this.dir = opts.dir;
|
|
4050
|
+
this.events = opts.events;
|
|
4051
|
+
this.traceId = opts.traceId;
|
|
3508
4052
|
}
|
|
3509
4053
|
// ── Reads ──────────────────────────────────────────────────────────────
|
|
3510
4054
|
/**
|
|
3511
4055
|
* Return all annotations for `sessionId` in insertion order
|
|
3512
4056
|
* (oldest first). Returns an empty array when no file exists
|
|
3513
|
-
* yet (the normal case for a fresh session)
|
|
4057
|
+
* yet (the normal case for a fresh session) and also degrades
|
|
4058
|
+
* gracefully to `[]` on a read error (permissions, corruption) —
|
|
4059
|
+
* the failure is still surfaced via a `storage.read` event so it
|
|
4060
|
+
* never silently hides I/O problems from observers.
|
|
3514
4061
|
*/
|
|
3515
4062
|
async list(sessionId) {
|
|
3516
|
-
const
|
|
3517
|
-
|
|
4063
|
+
const t0 = Date.now();
|
|
4064
|
+
const fp = this.filePath(sessionId);
|
|
4065
|
+
try {
|
|
4066
|
+
const file = await this.readFile(sessionId);
|
|
4067
|
+
const durationMs = Date.now() - t0;
|
|
4068
|
+
this.events?.emit("storage.read", {
|
|
4069
|
+
sessionId,
|
|
4070
|
+
store: "annotations",
|
|
4071
|
+
filePath: fp,
|
|
4072
|
+
operation: "list",
|
|
4073
|
+
outcome: "success",
|
|
4074
|
+
durationMs,
|
|
4075
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4076
|
+
});
|
|
4077
|
+
return file ? file.annotations : [];
|
|
4078
|
+
} catch (err) {
|
|
4079
|
+
this.events?.emit("storage.read", {
|
|
4080
|
+
sessionId,
|
|
4081
|
+
store: "annotations",
|
|
4082
|
+
filePath: fp,
|
|
4083
|
+
operation: "list",
|
|
4084
|
+
outcome: "failure",
|
|
4085
|
+
durationMs: Date.now() - t0,
|
|
4086
|
+
error: err instanceof Error ? err.message : String(err),
|
|
4087
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4088
|
+
});
|
|
4089
|
+
return [];
|
|
4090
|
+
}
|
|
3518
4091
|
}
|
|
3519
4092
|
/**
|
|
3520
4093
|
* Convenience: only unresolved annotations, newest first — the
|
|
@@ -3566,25 +4139,62 @@ var AnnotationsStore = class {
|
|
|
3566
4139
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3567
4140
|
resolved: false
|
|
3568
4141
|
};
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
const
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
4142
|
+
const fp = this.filePath(input.sessionId);
|
|
4143
|
+
const t0 = Date.now();
|
|
4144
|
+
try {
|
|
4145
|
+
await this.enqueue(input.sessionId, async () => {
|
|
4146
|
+
await withFileLock(fp, async () => {
|
|
4147
|
+
const all = await this.list(input.sessionId);
|
|
4148
|
+
all.push(annotation);
|
|
4149
|
+
if (all.length > MAX_ANNOTATIONS) {
|
|
4150
|
+
const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
|
|
4151
|
+
if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
|
|
4152
|
+
return x.a.createdAt.localeCompare(y.a.createdAt);
|
|
4153
|
+
});
|
|
4154
|
+
const evictCount = all.length - MAX_ANNOTATIONS;
|
|
4155
|
+
const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
|
|
4156
|
+
const kept = all.filter((a) => !toEvict.has(a.id));
|
|
4157
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
|
|
4158
|
+
const durationMs = Date.now() - t0;
|
|
4159
|
+
this.events?.emit("storage.write", {
|
|
4160
|
+
sessionId: input.sessionId,
|
|
4161
|
+
store: "annotations",
|
|
4162
|
+
filePath: fp,
|
|
4163
|
+
operation: "evict",
|
|
4164
|
+
outcome: "success",
|
|
4165
|
+
durationMs,
|
|
4166
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4167
|
+
});
|
|
4168
|
+
} else {
|
|
4169
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
|
|
4170
|
+
const durationMs = Date.now() - t0;
|
|
4171
|
+
this.events?.emit("storage.write", {
|
|
4172
|
+
sessionId: input.sessionId,
|
|
4173
|
+
store: "annotations",
|
|
4174
|
+
filePath: fp,
|
|
4175
|
+
operation: "add",
|
|
4176
|
+
outcome: "success",
|
|
4177
|
+
durationMs,
|
|
4178
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4179
|
+
});
|
|
4180
|
+
}
|
|
4181
|
+
});
|
|
3585
4182
|
});
|
|
3586
|
-
|
|
3587
|
-
|
|
4183
|
+
return annotation;
|
|
4184
|
+
} catch (err) {
|
|
4185
|
+
this.events?.emit("storage.error", {
|
|
4186
|
+
sessionId: input.sessionId,
|
|
4187
|
+
store: "annotations",
|
|
4188
|
+
filePath: fp,
|
|
4189
|
+
operation: "add",
|
|
4190
|
+
outcome: "failure",
|
|
4191
|
+
error: err instanceof Error ? err.message : String(err),
|
|
4192
|
+
recoverable: false,
|
|
4193
|
+
durationMs: Date.now() - t0,
|
|
4194
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4195
|
+
});
|
|
4196
|
+
throw err;
|
|
4197
|
+
}
|
|
3588
4198
|
}
|
|
3589
4199
|
/**
|
|
3590
4200
|
* Mark an annotation as resolved. Returns the updated record, or
|
|
@@ -3594,26 +4204,53 @@ var AnnotationsStore = class {
|
|
|
3594
4204
|
*/
|
|
3595
4205
|
async resolve(input) {
|
|
3596
4206
|
let updated = null;
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
4207
|
+
const fp = this.filePath(input.sessionId);
|
|
4208
|
+
const t0 = Date.now();
|
|
4209
|
+
try {
|
|
4210
|
+
await this.enqueue(input.sessionId, async () => {
|
|
4211
|
+
await withFileLock(fp, async () => {
|
|
4212
|
+
const all = await this.list(input.sessionId);
|
|
4213
|
+
const idx = all.findIndex((a) => a.id === input.annotationId);
|
|
4214
|
+
if (idx === -1) {
|
|
4215
|
+
updated = null;
|
|
4216
|
+
return;
|
|
4217
|
+
}
|
|
4218
|
+
const next = {
|
|
4219
|
+
...expectDefined(all[idx]),
|
|
4220
|
+
resolved: true,
|
|
4221
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4222
|
+
resolvedBy: input.resolvedBy
|
|
4223
|
+
};
|
|
4224
|
+
all[idx] = next;
|
|
4225
|
+
await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
|
|
4226
|
+
updated = next;
|
|
4227
|
+
const durationMs = Date.now() - t0;
|
|
4228
|
+
this.events?.emit("storage.write", {
|
|
4229
|
+
sessionId: input.sessionId,
|
|
4230
|
+
store: "annotations",
|
|
4231
|
+
filePath: fp,
|
|
4232
|
+
operation: "resolve",
|
|
4233
|
+
outcome: "success",
|
|
4234
|
+
durationMs,
|
|
4235
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4236
|
+
});
|
|
4237
|
+
});
|
|
3614
4238
|
});
|
|
3615
|
-
|
|
3616
|
-
|
|
4239
|
+
return updated;
|
|
4240
|
+
} catch (err) {
|
|
4241
|
+
this.events?.emit("storage.error", {
|
|
4242
|
+
sessionId: input.sessionId,
|
|
4243
|
+
store: "annotations",
|
|
4244
|
+
filePath: fp,
|
|
4245
|
+
operation: "resolve",
|
|
4246
|
+
outcome: "failure",
|
|
4247
|
+
error: err instanceof Error ? err.message : String(err),
|
|
4248
|
+
recoverable: false,
|
|
4249
|
+
durationMs: Date.now() - t0,
|
|
4250
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4251
|
+
});
|
|
4252
|
+
throw err;
|
|
4253
|
+
}
|
|
3617
4254
|
}
|
|
3618
4255
|
// ── Internals ──────────────────────────────────────────────────────────
|
|
3619
4256
|
filePath(sessionId) {
|
|
@@ -3621,16 +4258,21 @@ var AnnotationsStore = class {
|
|
|
3621
4258
|
}
|
|
3622
4259
|
async readFile(sessionId) {
|
|
3623
4260
|
const fp = this.filePath(sessionId);
|
|
4261
|
+
let raw;
|
|
4262
|
+
try {
|
|
4263
|
+
raw = await fsp.readFile(fp, "utf8");
|
|
4264
|
+
} catch (err) {
|
|
4265
|
+
if (err.code === "ENOENT") return null;
|
|
4266
|
+
throw err;
|
|
4267
|
+
}
|
|
3624
4268
|
try {
|
|
3625
|
-
const raw = await fsp.readFile(fp, "utf8");
|
|
3626
4269
|
const parsed = JSON.parse(raw);
|
|
3627
4270
|
if (parsed.version !== FILE_VERSION) {
|
|
3628
4271
|
return { version: FILE_VERSION, annotations: [] };
|
|
3629
4272
|
}
|
|
3630
4273
|
return parsed;
|
|
3631
|
-
} catch
|
|
3632
|
-
|
|
3633
|
-
return { version: FILE_VERSION, annotations: [] };
|
|
4274
|
+
} catch {
|
|
4275
|
+
return null;
|
|
3634
4276
|
}
|
|
3635
4277
|
}
|
|
3636
4278
|
async writeFile(sessionId, file) {
|
|
@@ -3684,9 +4326,20 @@ function hashRequest(request) {
|
|
|
3684
4326
|
const digest = createHash("sha256").update(json, "utf8").digest("hex");
|
|
3685
4327
|
return `sha256:${digest}`;
|
|
3686
4328
|
}
|
|
4329
|
+
|
|
4330
|
+
// src/storage/replay-log-store.ts
|
|
4331
|
+
function storageErrorString2(err) {
|
|
4332
|
+
if (err instanceof Error) {
|
|
4333
|
+
const code = err.code;
|
|
4334
|
+
return code ? `${code}: ${err.message}` : err.message;
|
|
4335
|
+
}
|
|
4336
|
+
return String(err);
|
|
4337
|
+
}
|
|
3687
4338
|
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
3688
4339
|
var ReplayLogStore = class {
|
|
3689
4340
|
dir;
|
|
4341
|
+
events;
|
|
4342
|
+
traceId;
|
|
3690
4343
|
writeChains = /* @__PURE__ */ new Map();
|
|
3691
4344
|
/** Per-session hash → entry index, kept in memory after the first load. */
|
|
3692
4345
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -3695,6 +4348,8 @@ var ReplayLogStore = class {
|
|
|
3695
4348
|
maxEntries;
|
|
3696
4349
|
constructor(opts) {
|
|
3697
4350
|
this.dir = opts.dir;
|
|
4351
|
+
this.events = opts.events;
|
|
4352
|
+
this.traceId = opts.traceId;
|
|
3698
4353
|
this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
3699
4354
|
}
|
|
3700
4355
|
// ── Writes ──────────────────────────────────────────────────────────────
|
|
@@ -3705,38 +4360,43 @@ var ReplayLogStore = class {
|
|
|
3705
4360
|
*/
|
|
3706
4361
|
async record(input) {
|
|
3707
4362
|
const hash = hashRequest(input.request);
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
4363
|
+
const fp = this.filePath(input.sessionId);
|
|
4364
|
+
const t0 = Date.now();
|
|
4365
|
+
try {
|
|
4366
|
+
await this.enqueue(input.sessionId, async () => {
|
|
4367
|
+
await withFileLock(this.filePath(input.sessionId), async () => {
|
|
4368
|
+
const entries = await this.readAll(input.sessionId);
|
|
4369
|
+
if (entries.some((entry2) => entry2.hash === hash)) return;
|
|
4370
|
+
const entry = {
|
|
4371
|
+
hash,
|
|
4372
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4373
|
+
request: input.request,
|
|
4374
|
+
response: input.response
|
|
4375
|
+
};
|
|
4376
|
+
entries.push(entry);
|
|
4377
|
+
const keep = entries.slice(-this.maxEntries);
|
|
4378
|
+
const didEvict = keep.length < entries.length;
|
|
4379
|
+
const cache = /* @__PURE__ */ new Map();
|
|
4380
|
+
for (const e of keep) cache.set(e.hash, e);
|
|
4381
|
+
this.cache.set(input.sessionId, cache);
|
|
4382
|
+
await this.writeAll(input.sessionId, keep, didEvict ? "compact" : "record");
|
|
4383
|
+
});
|
|
3724
4384
|
});
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
4385
|
+
return hash;
|
|
4386
|
+
} catch (err) {
|
|
4387
|
+
this.events?.emit("storage.error", {
|
|
4388
|
+
sessionId: input.sessionId,
|
|
4389
|
+
store: "replay",
|
|
4390
|
+
filePath: fp,
|
|
4391
|
+
operation: "record",
|
|
4392
|
+
outcome: "failure",
|
|
4393
|
+
error: storageErrorString2(err),
|
|
4394
|
+
recoverable: false,
|
|
4395
|
+
durationMs: Date.now() - t0,
|
|
4396
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4397
|
+
});
|
|
4398
|
+
throw err;
|
|
4399
|
+
}
|
|
3740
4400
|
}
|
|
3741
4401
|
// ── Reads ───────────────────────────────────────────────────────────────
|
|
3742
4402
|
/**
|
|
@@ -3745,13 +4405,65 @@ var ReplayLogStore = class {
|
|
|
3745
4405
|
* per session (in-memory cache).
|
|
3746
4406
|
*/
|
|
3747
4407
|
async lookup(sessionId, hash) {
|
|
3748
|
-
const
|
|
3749
|
-
|
|
4408
|
+
const fp = this.filePath(sessionId);
|
|
4409
|
+
const t0 = Date.now();
|
|
4410
|
+
try {
|
|
4411
|
+
const cache = await this.ensureCache(sessionId);
|
|
4412
|
+
this.events?.emit("storage.read", {
|
|
4413
|
+
sessionId,
|
|
4414
|
+
store: "replay",
|
|
4415
|
+
filePath: fp,
|
|
4416
|
+
operation: "lookup",
|
|
4417
|
+
outcome: "success",
|
|
4418
|
+
durationMs: Date.now() - t0,
|
|
4419
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4420
|
+
});
|
|
4421
|
+
return cache.get(hash) ?? null;
|
|
4422
|
+
} catch (err) {
|
|
4423
|
+
this.events?.emit("storage.read", {
|
|
4424
|
+
sessionId,
|
|
4425
|
+
store: "replay",
|
|
4426
|
+
filePath: fp,
|
|
4427
|
+
operation: "lookup",
|
|
4428
|
+
outcome: "failure",
|
|
4429
|
+
durationMs: Date.now() - t0,
|
|
4430
|
+
error: storageErrorString2(err),
|
|
4431
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4432
|
+
});
|
|
4433
|
+
throw err;
|
|
4434
|
+
}
|
|
3750
4435
|
}
|
|
3751
4436
|
/** All recorded entries for a session, in insertion order. */
|
|
3752
4437
|
async load(sessionId) {
|
|
3753
|
-
const
|
|
3754
|
-
|
|
4438
|
+
const fp = this.filePath(sessionId);
|
|
4439
|
+
const t0 = Date.now();
|
|
4440
|
+
try {
|
|
4441
|
+
const cache = await this.ensureCache(sessionId);
|
|
4442
|
+
const durationMs = Date.now() - t0;
|
|
4443
|
+
this.events?.emit("storage.read", {
|
|
4444
|
+
sessionId,
|
|
4445
|
+
store: "replay",
|
|
4446
|
+
filePath: fp,
|
|
4447
|
+
operation: "load",
|
|
4448
|
+
outcome: "success",
|
|
4449
|
+
durationMs,
|
|
4450
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4451
|
+
});
|
|
4452
|
+
return [...cache.values()];
|
|
4453
|
+
} catch (err) {
|
|
4454
|
+
const durationMs = Date.now() - t0;
|
|
4455
|
+
this.events?.emit("storage.read", {
|
|
4456
|
+
sessionId,
|
|
4457
|
+
store: "replay",
|
|
4458
|
+
filePath: fp,
|
|
4459
|
+
operation: "load",
|
|
4460
|
+
outcome: "failure",
|
|
4461
|
+
durationMs,
|
|
4462
|
+
error: storageErrorString2(err),
|
|
4463
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4464
|
+
});
|
|
4465
|
+
throw err;
|
|
4466
|
+
}
|
|
3755
4467
|
}
|
|
3756
4468
|
/**
|
|
3757
4469
|
* List every session id that has a replay log in the store dir.
|
|
@@ -3821,13 +4533,24 @@ var ReplayLogStore = class {
|
|
|
3821
4533
|
return out;
|
|
3822
4534
|
} catch (err) {
|
|
3823
4535
|
if (err.code === "ENOENT") return [];
|
|
3824
|
-
|
|
4536
|
+
throw err;
|
|
3825
4537
|
}
|
|
3826
4538
|
}
|
|
3827
|
-
async writeAll(sessionId, entries) {
|
|
4539
|
+
async writeAll(sessionId, entries, operation = "record") {
|
|
3828
4540
|
const fp = this.filePath(sessionId);
|
|
4541
|
+
const t0 = Date.now();
|
|
3829
4542
|
const body = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
|
|
3830
4543
|
await atomicWrite(fp, body);
|
|
4544
|
+
const durationMs = Date.now() - t0;
|
|
4545
|
+
this.events?.emit("storage.write", {
|
|
4546
|
+
sessionId,
|
|
4547
|
+
store: "replay",
|
|
4548
|
+
filePath: fp,
|
|
4549
|
+
operation,
|
|
4550
|
+
outcome: "success",
|
|
4551
|
+
durationMs,
|
|
4552
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4553
|
+
});
|
|
3831
4554
|
}
|
|
3832
4555
|
async ensureCache(sessionId) {
|
|
3833
4556
|
let cache = this.cache.get(sessionId);
|
|
@@ -4005,6 +4728,8 @@ var GENESIS_PREV = "0".repeat(64);
|
|
|
4005
4728
|
var DEFAULT_FSYNC_EVERY = 100;
|
|
4006
4729
|
var ToolAuditLog = class {
|
|
4007
4730
|
dir;
|
|
4731
|
+
events;
|
|
4732
|
+
traceId;
|
|
4008
4733
|
/** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
|
|
4009
4734
|
tailHash = /* @__PURE__ */ new Map();
|
|
4010
4735
|
/** In-memory counter for entry indices — avoids re-reading the file on every write. */
|
|
@@ -4015,6 +4740,8 @@ var ToolAuditLog = class {
|
|
|
4015
4740
|
fsyncEvery;
|
|
4016
4741
|
constructor(opts) {
|
|
4017
4742
|
this.dir = opts.dir;
|
|
4743
|
+
this.events = opts.events;
|
|
4744
|
+
this.traceId = opts.traceId;
|
|
4018
4745
|
this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;
|
|
4019
4746
|
}
|
|
4020
4747
|
/**
|
|
@@ -4025,96 +4752,180 @@ var ToolAuditLog = class {
|
|
|
4025
4752
|
*/
|
|
4026
4753
|
async record(input) {
|
|
4027
4754
|
let entry;
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
id
|
|
4038
|
-
ts
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4755
|
+
const fp = this.filePath(input.sessionId);
|
|
4756
|
+
const t0 = Date.now();
|
|
4757
|
+
try {
|
|
4758
|
+
await this.enqueue(input.sessionId, async () => {
|
|
4759
|
+
await withFileLock(fp, async () => {
|
|
4760
|
+
const entries = await this.readAll(input.sessionId);
|
|
4761
|
+
const prev = entries.at(-1);
|
|
4762
|
+
const prevHash = prev?.hash ?? GENESIS_PREV;
|
|
4763
|
+
const index = prev ? prev.index + 1 : 0;
|
|
4764
|
+
const id = randomUUID();
|
|
4765
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
4766
|
+
const content = {
|
|
4767
|
+
id,
|
|
4768
|
+
ts,
|
|
4769
|
+
prevHash,
|
|
4770
|
+
toolName: input.toolName,
|
|
4771
|
+
toolUseId: input.toolUseId,
|
|
4772
|
+
input: input.input,
|
|
4773
|
+
output: input.output,
|
|
4774
|
+
isError: input.isError,
|
|
4775
|
+
index
|
|
4776
|
+
};
|
|
4777
|
+
const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
|
|
4778
|
+
entry = {
|
|
4779
|
+
id,
|
|
4780
|
+
ts,
|
|
4781
|
+
prevHash,
|
|
4782
|
+
hash,
|
|
4783
|
+
toolName: input.toolName,
|
|
4784
|
+
toolUseId: input.toolUseId,
|
|
4785
|
+
input: input.input,
|
|
4786
|
+
output: input.output,
|
|
4787
|
+
isError: input.isError,
|
|
4788
|
+
index
|
|
4789
|
+
};
|
|
4790
|
+
entries.push(entry);
|
|
4791
|
+
await this.writeAll(input.sessionId, entries);
|
|
4792
|
+
this.tailHash.set(input.sessionId, hash);
|
|
4793
|
+
this.tailIndex.set(input.sessionId, index + 1);
|
|
4794
|
+
const durationMs = Date.now() - t0;
|
|
4795
|
+
this.events?.emit("storage.write", {
|
|
4796
|
+
sessionId: input.sessionId,
|
|
4797
|
+
store: "audit",
|
|
4798
|
+
filePath: fp,
|
|
4799
|
+
operation: "record",
|
|
4800
|
+
outcome: "success",
|
|
4801
|
+
durationMs,
|
|
4802
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4803
|
+
});
|
|
4804
|
+
});
|
|
4064
4805
|
});
|
|
4065
|
-
|
|
4066
|
-
|
|
4806
|
+
return entry;
|
|
4807
|
+
} catch (err) {
|
|
4808
|
+
this.events?.emit("storage.error", {
|
|
4809
|
+
sessionId: input.sessionId,
|
|
4810
|
+
store: "audit",
|
|
4811
|
+
filePath: fp,
|
|
4812
|
+
operation: "record",
|
|
4813
|
+
outcome: "failure",
|
|
4814
|
+
error: err instanceof Error ? err.message : String(err),
|
|
4815
|
+
recoverable: false,
|
|
4816
|
+
durationMs: Date.now() - t0,
|
|
4817
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4818
|
+
});
|
|
4819
|
+
throw err;
|
|
4820
|
+
}
|
|
4067
4821
|
}
|
|
4068
4822
|
/**
|
|
4069
4823
|
* Walk the chain and verify every entry's hash and prevHash.
|
|
4070
4824
|
* Returns a structured verdict — never throws.
|
|
4071
4825
|
*/
|
|
4072
4826
|
async verify(sessionId) {
|
|
4073
|
-
const
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4827
|
+
const fp = this.filePath(sessionId);
|
|
4828
|
+
const t0 = Date.now();
|
|
4829
|
+
let entries;
|
|
4830
|
+
try {
|
|
4831
|
+
entries = await this.readAll(sessionId);
|
|
4832
|
+
} catch (err) {
|
|
4833
|
+
this.events?.emit("storage.read", {
|
|
4834
|
+
sessionId,
|
|
4835
|
+
store: "audit",
|
|
4836
|
+
filePath: fp,
|
|
4837
|
+
operation: "verify",
|
|
4838
|
+
outcome: "failure",
|
|
4839
|
+
durationMs: Date.now() - t0,
|
|
4840
|
+
error: err instanceof Error ? err.message : String(err),
|
|
4841
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4842
|
+
});
|
|
4843
|
+
return { ok: true, entries: 0 };
|
|
4081
4844
|
}
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
if (e.prevHash !== prevHash) {
|
|
4845
|
+
const verdict = (() => {
|
|
4846
|
+
if (entries.length === 0) return { ok: true, entries: 0 };
|
|
4847
|
+
if (entries[0]?.prevHash !== GENESIS_PREV) {
|
|
4086
4848
|
return {
|
|
4087
4849
|
ok: false,
|
|
4088
|
-
brokenAt:
|
|
4089
|
-
reason:
|
|
4850
|
+
brokenAt: 0,
|
|
4851
|
+
reason: "first entry is not the genesis (prevHash != 0\u20260)"
|
|
4090
4852
|
};
|
|
4091
4853
|
}
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4854
|
+
let prevHash = GENESIS_PREV;
|
|
4855
|
+
for (let i = 0; i < entries.length; i++) {
|
|
4856
|
+
const e = expectDefined(entries[i]);
|
|
4857
|
+
if (e.prevHash !== prevHash) {
|
|
4858
|
+
return {
|
|
4859
|
+
ok: false,
|
|
4860
|
+
brokenAt: i,
|
|
4861
|
+
reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
|
|
4862
|
+
};
|
|
4863
|
+
}
|
|
4864
|
+
const content = {
|
|
4865
|
+
id: e.id,
|
|
4866
|
+
ts: e.ts,
|
|
4867
|
+
prevHash: e.prevHash,
|
|
4868
|
+
toolName: e.toolName,
|
|
4869
|
+
toolUseId: e.toolUseId,
|
|
4870
|
+
input: e.input,
|
|
4871
|
+
output: e.output,
|
|
4872
|
+
isError: e.isError,
|
|
4873
|
+
index: e.index
|
|
4109
4874
|
};
|
|
4875
|
+
const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
|
|
4876
|
+
if (expectedHash !== e.hash) {
|
|
4877
|
+
return {
|
|
4878
|
+
ok: false,
|
|
4879
|
+
brokenAt: i,
|
|
4880
|
+
reason: `hash mismatch at entry ${i} (entry content was modified)`
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
prevHash = e.hash;
|
|
4110
4884
|
}
|
|
4111
|
-
|
|
4112
|
-
}
|
|
4113
|
-
|
|
4885
|
+
return { ok: true, entries: entries.length };
|
|
4886
|
+
})();
|
|
4887
|
+
this.events?.emit("storage.read", {
|
|
4888
|
+
sessionId,
|
|
4889
|
+
store: "audit",
|
|
4890
|
+
filePath: fp,
|
|
4891
|
+
operation: "verify",
|
|
4892
|
+
outcome: verdict.ok ? "success" : "failure",
|
|
4893
|
+
durationMs: Date.now() - t0,
|
|
4894
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4895
|
+
});
|
|
4896
|
+
return verdict;
|
|
4114
4897
|
}
|
|
4115
4898
|
/** All entries for a session, in insertion order. */
|
|
4116
4899
|
async load(sessionId) {
|
|
4117
|
-
|
|
4900
|
+
const fp = this.filePath(sessionId);
|
|
4901
|
+
const t0 = Date.now();
|
|
4902
|
+
try {
|
|
4903
|
+
const entries = await this.readAll(sessionId);
|
|
4904
|
+
const durationMs = Date.now() - t0;
|
|
4905
|
+
this.events?.emit("storage.read", {
|
|
4906
|
+
sessionId,
|
|
4907
|
+
store: "audit",
|
|
4908
|
+
filePath: fp,
|
|
4909
|
+
operation: "load",
|
|
4910
|
+
outcome: "success",
|
|
4911
|
+
durationMs,
|
|
4912
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4913
|
+
});
|
|
4914
|
+
return entries;
|
|
4915
|
+
} catch (err) {
|
|
4916
|
+
const durationMs = Date.now() - t0;
|
|
4917
|
+
this.events?.emit("storage.read", {
|
|
4918
|
+
sessionId,
|
|
4919
|
+
store: "audit",
|
|
4920
|
+
filePath: fp,
|
|
4921
|
+
operation: "load",
|
|
4922
|
+
outcome: "failure",
|
|
4923
|
+
durationMs,
|
|
4924
|
+
error: err instanceof Error ? err.message : String(err),
|
|
4925
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4926
|
+
});
|
|
4927
|
+
throw err;
|
|
4928
|
+
}
|
|
4118
4929
|
}
|
|
4119
4930
|
// ── Internals ────────────────────────────────────────────────────────────
|
|
4120
4931
|
filePath(sessionId) {
|
|
@@ -4136,7 +4947,7 @@ var ToolAuditLog = class {
|
|
|
4136
4947
|
return out;
|
|
4137
4948
|
} catch (err) {
|
|
4138
4949
|
if (err.code === "ENOENT") return [];
|
|
4139
|
-
|
|
4950
|
+
throw err;
|
|
4140
4951
|
}
|
|
4141
4952
|
}
|
|
4142
4953
|
async writeAll(sessionId, entries) {
|
|
@@ -4848,24 +5659,66 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
4848
5659
|
}
|
|
4849
5660
|
return { revertedFiles, errors };
|
|
4850
5661
|
}
|
|
4851
|
-
async function loadTodosCheckpoint(filePath) {
|
|
5662
|
+
async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
5663
|
+
const t0 = Date.now();
|
|
4852
5664
|
let raw;
|
|
4853
5665
|
try {
|
|
4854
5666
|
raw = await fsp.readFile(filePath, "utf8");
|
|
4855
|
-
} catch {
|
|
5667
|
+
} catch (err) {
|
|
5668
|
+
events?.emit("storage.error", {
|
|
5669
|
+
sessionId: traceId ?? "~boot~",
|
|
5670
|
+
store: "todos",
|
|
5671
|
+
filePath,
|
|
5672
|
+
operation: "load",
|
|
5673
|
+
outcome: "failure",
|
|
5674
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5675
|
+
recoverable: true
|
|
5676
|
+
});
|
|
4856
5677
|
return null;
|
|
4857
5678
|
}
|
|
4858
5679
|
try {
|
|
4859
5680
|
const parsed = JSON.parse(raw);
|
|
4860
|
-
if (parsed?.version !== 1 || !Array.isArray(parsed.todos))
|
|
5681
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) {
|
|
5682
|
+
events?.emit("storage.read", {
|
|
5683
|
+
sessionId: traceId ?? "~boot~",
|
|
5684
|
+
store: "todos",
|
|
5685
|
+
filePath,
|
|
5686
|
+
operation: "load",
|
|
5687
|
+
outcome: "failure",
|
|
5688
|
+
durationMs: Date.now() - t0,
|
|
5689
|
+
error: "invalid_schema",
|
|
5690
|
+
...traceId !== void 0 && { traceId }
|
|
5691
|
+
});
|
|
5692
|
+
return null;
|
|
5693
|
+
}
|
|
5694
|
+
events?.emit("storage.read", {
|
|
5695
|
+
sessionId: traceId ?? "~boot~",
|
|
5696
|
+
store: "todos",
|
|
5697
|
+
filePath,
|
|
5698
|
+
operation: "load",
|
|
5699
|
+
outcome: "success",
|
|
5700
|
+
durationMs: Date.now() - t0,
|
|
5701
|
+
...traceId !== void 0 && { traceId }
|
|
5702
|
+
});
|
|
4861
5703
|
return parsed.todos.filter(
|
|
4862
5704
|
(t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string" && (t.activeForm === void 0 || typeof t.activeForm === "string")
|
|
4863
5705
|
);
|
|
4864
5706
|
} catch {
|
|
5707
|
+
events?.emit("storage.read", {
|
|
5708
|
+
sessionId: traceId ?? "~boot~",
|
|
5709
|
+
store: "todos",
|
|
5710
|
+
filePath,
|
|
5711
|
+
operation: "load",
|
|
5712
|
+
outcome: "failure",
|
|
5713
|
+
durationMs: Date.now() - t0,
|
|
5714
|
+
error: "parse_failed",
|
|
5715
|
+
...traceId !== void 0 && { traceId }
|
|
5716
|
+
});
|
|
4865
5717
|
return null;
|
|
4866
5718
|
}
|
|
4867
5719
|
}
|
|
4868
|
-
async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
5720
|
+
async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
|
|
5721
|
+
const t0 = Date.now();
|
|
4869
5722
|
const payload = {
|
|
4870
5723
|
version: 1,
|
|
4871
5724
|
sessionId,
|
|
@@ -4874,7 +5727,25 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
|
4874
5727
|
};
|
|
4875
5728
|
try {
|
|
4876
5729
|
await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
|
|
5730
|
+
events?.emit("storage.write", {
|
|
5731
|
+
sessionId: traceId ?? sessionId,
|
|
5732
|
+
store: "todos",
|
|
5733
|
+
filePath,
|
|
5734
|
+
operation: "save",
|
|
5735
|
+
outcome: "success",
|
|
5736
|
+
durationMs: Date.now() - t0,
|
|
5737
|
+
...traceId !== void 0 && { traceId }
|
|
5738
|
+
});
|
|
4877
5739
|
} catch (err) {
|
|
5740
|
+
events?.emit("storage.error", {
|
|
5741
|
+
sessionId: traceId ?? sessionId,
|
|
5742
|
+
store: "todos",
|
|
5743
|
+
filePath,
|
|
5744
|
+
operation: "save",
|
|
5745
|
+
outcome: "failure",
|
|
5746
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5747
|
+
recoverable: false
|
|
5748
|
+
});
|
|
4878
5749
|
console.warn(JSON.stringify({
|
|
4879
5750
|
level: "warn",
|
|
4880
5751
|
event: "todos_checkpoint.save_failed",
|
|
@@ -4883,12 +5754,12 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
|
4883
5754
|
}));
|
|
4884
5755
|
}
|
|
4885
5756
|
}
|
|
4886
|
-
function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
5757
|
+
function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
|
|
4887
5758
|
let timer = null;
|
|
4888
5759
|
let pending = null;
|
|
4889
5760
|
let writeChain = Promise.resolve();
|
|
4890
5761
|
const enqueueWrite = (todos) => {
|
|
4891
|
-
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos)).catch((err) => {
|
|
5762
|
+
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
|
|
4892
5763
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4893
5764
|
console.error(JSON.stringify({
|
|
4894
5765
|
level: "error",
|
|
@@ -4927,25 +5798,79 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
|
4927
5798
|
}
|
|
4928
5799
|
};
|
|
4929
5800
|
}
|
|
4930
|
-
async function loadPlan(filePath) {
|
|
5801
|
+
async function loadPlan(filePath, events) {
|
|
5802
|
+
const t0 = Date.now();
|
|
4931
5803
|
let raw;
|
|
4932
5804
|
try {
|
|
4933
5805
|
raw = await fsp.readFile(filePath, "utf8");
|
|
4934
|
-
} catch {
|
|
5806
|
+
} catch (err) {
|
|
5807
|
+
events?.emit("storage.error", {
|
|
5808
|
+
sessionId: "~boot~",
|
|
5809
|
+
store: "plan",
|
|
5810
|
+
filePath,
|
|
5811
|
+
operation: "load",
|
|
5812
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5813
|
+
recoverable: true
|
|
5814
|
+
});
|
|
4935
5815
|
return null;
|
|
4936
5816
|
}
|
|
4937
5817
|
try {
|
|
4938
5818
|
const parsed = JSON.parse(raw);
|
|
4939
|
-
if (parsed?.version !== 1 || !Array.isArray(parsed.items))
|
|
5819
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.items)) {
|
|
5820
|
+
events?.emit("storage.read", {
|
|
5821
|
+
sessionId: "~boot~",
|
|
5822
|
+
store: "plan",
|
|
5823
|
+
filePath,
|
|
5824
|
+
operation: "load",
|
|
5825
|
+
outcome: "failure",
|
|
5826
|
+
durationMs: Date.now() - t0,
|
|
5827
|
+
error: "invalid_schema"
|
|
5828
|
+
});
|
|
5829
|
+
return null;
|
|
5830
|
+
}
|
|
5831
|
+
events?.emit("storage.read", {
|
|
5832
|
+
sessionId: "~boot~",
|
|
5833
|
+
store: "plan",
|
|
5834
|
+
filePath,
|
|
5835
|
+
operation: "load",
|
|
5836
|
+
outcome: "success",
|
|
5837
|
+
durationMs: Date.now() - t0
|
|
5838
|
+
});
|
|
4940
5839
|
return parsed;
|
|
4941
5840
|
} catch {
|
|
5841
|
+
events?.emit("storage.read", {
|
|
5842
|
+
sessionId: "~boot~",
|
|
5843
|
+
store: "plan",
|
|
5844
|
+
filePath,
|
|
5845
|
+
operation: "load",
|
|
5846
|
+
outcome: "failure",
|
|
5847
|
+
durationMs: Date.now() - t0,
|
|
5848
|
+
error: "parse_failed"
|
|
5849
|
+
});
|
|
4942
5850
|
return null;
|
|
4943
5851
|
}
|
|
4944
5852
|
}
|
|
4945
|
-
async function savePlan(filePath, plan) {
|
|
5853
|
+
async function savePlan(filePath, plan, events) {
|
|
5854
|
+
const t0 = Date.now();
|
|
4946
5855
|
try {
|
|
4947
5856
|
await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
|
|
5857
|
+
events?.emit("storage.write", {
|
|
5858
|
+
sessionId: "~boot~",
|
|
5859
|
+
store: "plan",
|
|
5860
|
+
filePath,
|
|
5861
|
+
operation: "save",
|
|
5862
|
+
outcome: "success",
|
|
5863
|
+
durationMs: Date.now() - t0
|
|
5864
|
+
});
|
|
4948
5865
|
} catch (err) {
|
|
5866
|
+
events?.emit("storage.error", {
|
|
5867
|
+
sessionId: "~boot~",
|
|
5868
|
+
store: "plan",
|
|
5869
|
+
filePath,
|
|
5870
|
+
operation: "save",
|
|
5871
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5872
|
+
recoverable: false
|
|
5873
|
+
});
|
|
4949
5874
|
console.warn(
|
|
4950
5875
|
"[plan-store] save failed:",
|
|
4951
5876
|
err instanceof Error ? err.message : String(err)
|
|
@@ -5179,37 +6104,98 @@ function emptyTaskFile(sessionId) {
|
|
|
5179
6104
|
tasks: []
|
|
5180
6105
|
};
|
|
5181
6106
|
}
|
|
5182
|
-
async function loadTasks(filePath) {
|
|
6107
|
+
async function loadTasks(filePath, events, traceId) {
|
|
6108
|
+
const t0 = Date.now();
|
|
5183
6109
|
let raw;
|
|
5184
6110
|
try {
|
|
5185
6111
|
raw = await fsp.readFile(filePath, "utf8");
|
|
5186
|
-
} catch {
|
|
6112
|
+
} catch (err) {
|
|
6113
|
+
events?.emit("storage.error", {
|
|
6114
|
+
sessionId: traceId ?? "~boot~",
|
|
6115
|
+
store: "tasks",
|
|
6116
|
+
filePath,
|
|
6117
|
+
operation: "load",
|
|
6118
|
+
outcome: "failure",
|
|
6119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
6120
|
+
recoverable: true
|
|
6121
|
+
});
|
|
5187
6122
|
return null;
|
|
5188
6123
|
}
|
|
5189
6124
|
try {
|
|
5190
6125
|
const parsed = JSON.parse(raw);
|
|
5191
|
-
if (parsed?.version !== 1 || !Array.isArray(parsed.tasks))
|
|
6126
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) {
|
|
6127
|
+
events?.emit("storage.read", {
|
|
6128
|
+
sessionId: traceId ?? "~boot~",
|
|
6129
|
+
store: "tasks",
|
|
6130
|
+
filePath,
|
|
6131
|
+
operation: "load",
|
|
6132
|
+
outcome: "failure",
|
|
6133
|
+
durationMs: Date.now() - t0,
|
|
6134
|
+
error: "invalid_schema",
|
|
6135
|
+
...traceId !== void 0 && { traceId }
|
|
6136
|
+
});
|
|
6137
|
+
return null;
|
|
6138
|
+
}
|
|
6139
|
+
events?.emit("storage.read", {
|
|
6140
|
+
sessionId: traceId ?? "~boot~",
|
|
6141
|
+
store: "tasks",
|
|
6142
|
+
filePath,
|
|
6143
|
+
operation: "load",
|
|
6144
|
+
outcome: "success",
|
|
6145
|
+
durationMs: Date.now() - t0,
|
|
6146
|
+
...traceId !== void 0 && { traceId }
|
|
6147
|
+
});
|
|
5192
6148
|
return parsed;
|
|
5193
6149
|
} catch {
|
|
6150
|
+
events?.emit("storage.read", {
|
|
6151
|
+
sessionId: traceId ?? "~boot~",
|
|
6152
|
+
store: "tasks",
|
|
6153
|
+
filePath,
|
|
6154
|
+
operation: "load",
|
|
6155
|
+
outcome: "failure",
|
|
6156
|
+
durationMs: Date.now() - t0,
|
|
6157
|
+
error: "parse_failed",
|
|
6158
|
+
...traceId !== void 0 && { traceId }
|
|
6159
|
+
});
|
|
5194
6160
|
return null;
|
|
5195
6161
|
}
|
|
5196
6162
|
}
|
|
5197
|
-
async function saveTasks(filePath, tasks) {
|
|
6163
|
+
async function saveTasks(filePath, tasks, events, traceId) {
|
|
6164
|
+
const t0 = Date.now();
|
|
5198
6165
|
try {
|
|
5199
6166
|
tasks.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5200
6167
|
await atomicWrite(filePath, JSON.stringify(tasks, null, 2), { mode: 384 });
|
|
6168
|
+
events?.emit("storage.write", {
|
|
6169
|
+
sessionId: traceId ?? "~boot~",
|
|
6170
|
+
store: "tasks",
|
|
6171
|
+
filePath,
|
|
6172
|
+
operation: "save",
|
|
6173
|
+
outcome: "success",
|
|
6174
|
+
durationMs: Date.now() - t0,
|
|
6175
|
+
...traceId !== void 0 && { traceId }
|
|
6176
|
+
});
|
|
5201
6177
|
} catch (err) {
|
|
6178
|
+
events?.emit("storage.error", {
|
|
6179
|
+
sessionId: traceId ?? "~boot~",
|
|
6180
|
+
store: "tasks",
|
|
6181
|
+
filePath,
|
|
6182
|
+
operation: "save",
|
|
6183
|
+
outcome: "failure",
|
|
6184
|
+
error: err instanceof Error ? err.message : String(err),
|
|
6185
|
+
recoverable: false,
|
|
6186
|
+
...traceId !== void 0 && { traceId }
|
|
6187
|
+
});
|
|
5202
6188
|
console.warn(
|
|
5203
6189
|
"[task-store] save failed:",
|
|
5204
6190
|
err instanceof Error ? err.message : String(err)
|
|
5205
6191
|
);
|
|
5206
6192
|
}
|
|
5207
6193
|
}
|
|
5208
|
-
async function mutateTasks(filePath, sessionId, fn) {
|
|
6194
|
+
async function mutateTasks(filePath, sessionId, fn, events, traceId) {
|
|
5209
6195
|
return withFileLock(filePath, async () => {
|
|
5210
|
-
const file = await loadTasks(filePath) ?? emptyTaskFile(sessionId);
|
|
6196
|
+
const file = await loadTasks(filePath, events, traceId) ?? emptyTaskFile(sessionId);
|
|
5211
6197
|
const updated = await fn(file);
|
|
5212
|
-
await saveTasks(filePath, updated);
|
|
6198
|
+
await saveTasks(filePath, updated, events, traceId);
|
|
5213
6199
|
return updated;
|
|
5214
6200
|
});
|
|
5215
6201
|
}
|
|
@@ -5491,13 +6477,32 @@ var MAX_JOURNAL_ENTRIES = 500;
|
|
|
5491
6477
|
function goalFilePath(projectRoot) {
|
|
5492
6478
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
5493
6479
|
}
|
|
5494
|
-
async function loadGoal(filePath) {
|
|
6480
|
+
async function loadGoal(filePath, events) {
|
|
6481
|
+
const t0 = Date.now();
|
|
5495
6482
|
let raw;
|
|
5496
6483
|
try {
|
|
5497
6484
|
raw = await fsp.readFile(filePath, "utf8");
|
|
5498
6485
|
} catch (err) {
|
|
5499
6486
|
const code = err.code;
|
|
5500
|
-
if (code === "ENOENT")
|
|
6487
|
+
if (code === "ENOENT") {
|
|
6488
|
+
events?.emit("storage.read", {
|
|
6489
|
+
sessionId: "~boot~",
|
|
6490
|
+
store: "goal",
|
|
6491
|
+
filePath,
|
|
6492
|
+
operation: "load",
|
|
6493
|
+
outcome: "success",
|
|
6494
|
+
durationMs: Date.now() - t0
|
|
6495
|
+
});
|
|
6496
|
+
return null;
|
|
6497
|
+
}
|
|
6498
|
+
events?.emit("storage.error", {
|
|
6499
|
+
sessionId: "~boot~",
|
|
6500
|
+
store: "goal",
|
|
6501
|
+
filePath,
|
|
6502
|
+
operation: "load",
|
|
6503
|
+
error: err instanceof Error ? err.message : String(err),
|
|
6504
|
+
recoverable: false
|
|
6505
|
+
});
|
|
5501
6506
|
throw err;
|
|
5502
6507
|
}
|
|
5503
6508
|
try {
|
|
@@ -5510,8 +6515,25 @@ async function loadGoal(filePath) {
|
|
|
5510
6515
|
message: "invalid schema \u2014 consider deleting and re-creating",
|
|
5511
6516
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5512
6517
|
}));
|
|
6518
|
+
events?.emit("storage.read", {
|
|
6519
|
+
sessionId: "~boot~",
|
|
6520
|
+
store: "goal",
|
|
6521
|
+
filePath,
|
|
6522
|
+
operation: "load",
|
|
6523
|
+
outcome: "failure",
|
|
6524
|
+
durationMs: Date.now() - t0,
|
|
6525
|
+
error: "invalid_schema"
|
|
6526
|
+
});
|
|
5513
6527
|
return null;
|
|
5514
6528
|
}
|
|
6529
|
+
events?.emit("storage.read", {
|
|
6530
|
+
sessionId: "~boot~",
|
|
6531
|
+
store: "goal",
|
|
6532
|
+
filePath,
|
|
6533
|
+
operation: "load",
|
|
6534
|
+
outcome: "success",
|
|
6535
|
+
durationMs: Date.now() - t0
|
|
6536
|
+
});
|
|
5515
6537
|
return parsed;
|
|
5516
6538
|
} catch {
|
|
5517
6539
|
console.warn(JSON.stringify({
|
|
@@ -5521,13 +6543,39 @@ async function loadGoal(filePath) {
|
|
|
5521
6543
|
message: "JSON parse failed \u2014 consider deleting and re-creating",
|
|
5522
6544
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5523
6545
|
}));
|
|
6546
|
+
events?.emit("storage.read", {
|
|
6547
|
+
sessionId: "~boot~",
|
|
6548
|
+
store: "goal",
|
|
6549
|
+
filePath,
|
|
6550
|
+
operation: "load",
|
|
6551
|
+
outcome: "failure",
|
|
6552
|
+
durationMs: Date.now() - t0,
|
|
6553
|
+
error: "parse_failed"
|
|
6554
|
+
});
|
|
5524
6555
|
return null;
|
|
5525
6556
|
}
|
|
5526
6557
|
}
|
|
5527
|
-
async function saveGoal(filePath, goal) {
|
|
6558
|
+
async function saveGoal(filePath, goal, events) {
|
|
6559
|
+
const t0 = Date.now();
|
|
5528
6560
|
try {
|
|
5529
6561
|
await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
|
|
6562
|
+
events?.emit("storage.write", {
|
|
6563
|
+
sessionId: "~boot~",
|
|
6564
|
+
store: "goal",
|
|
6565
|
+
filePath,
|
|
6566
|
+
operation: "save",
|
|
6567
|
+
outcome: "success",
|
|
6568
|
+
durationMs: Date.now() - t0
|
|
6569
|
+
});
|
|
5530
6570
|
} catch (err) {
|
|
6571
|
+
events?.emit("storage.error", {
|
|
6572
|
+
sessionId: "~boot~",
|
|
6573
|
+
store: "goal",
|
|
6574
|
+
filePath,
|
|
6575
|
+
operation: "save",
|
|
6576
|
+
error: err instanceof Error ? err.message : String(err),
|
|
6577
|
+
recoverable: false
|
|
6578
|
+
});
|
|
5531
6579
|
throw new FsError({
|
|
5532
6580
|
message: err instanceof Error ? err.message : String(err),
|
|
5533
6581
|
code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|