@wrongstack/core 0.260.0 → 0.264.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/{agent-bridge-BbskZ7HH.d.ts → agent-bridge-D8sa1vtv.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-c9DLkaas.d.ts} +11 -9
  3. package/dist/{brain-C2yDd7Lw.d.ts → brain-O1IdKPaK.d.ts} +2 -2
  4. package/dist/{compactor-t0R_AIt_.d.ts → compactor-BBy0rCtB.d.ts} +1 -1
  5. package/dist/{config-FG6As4H5.d.ts → config-Dz2F3H2K.d.ts} +7 -1
  6. package/dist/{context-JFOVvu6z.d.ts → context-BGSpZNSE.d.ts} +11 -0
  7. package/dist/coordination/index.d.ts +1681 -15
  8. package/dist/coordination/index.js +2648 -388
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +1414 -1387
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
  14. package/dist/execution/index.d.ts +15 -15
  15. package/dist/execution/index.js +410 -388
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/execution/prompt-enhancer.d.ts +2 -2
  18. package/dist/execution/prompt-enhancer.js +7 -1
  19. package/dist/execution/prompt-enhancer.js.map +1 -1
  20. package/dist/extension/index.d.ts +6 -6
  21. package/dist/extension/index.js.map +1 -1
  22. package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DzjFuN3p.d.ts} +21 -9
  23. package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-CxWmCGbH.d.ts} +1 -1
  24. package/dist/{index-CebbJB94.d.ts → index-CYIQrXVF.d.ts} +8 -8
  25. package/dist/{index-BPcg4N3M.d.ts → index-CbLSI66_.d.ts} +5 -5
  26. package/dist/index.d.ts +45 -91
  27. package/dist/index.js +14976 -12551
  28. package/dist/index.js.map +1 -1
  29. package/dist/infrastructure/index.d.ts +6 -6
  30. package/dist/kernel/index.d.ts +9 -9
  31. package/dist/kernel/index.js +6 -1
  32. package/dist/kernel/index.js.map +1 -1
  33. package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-DzxuZnNz.d.ts} +2 -2
  34. package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-DC4QRPUI.d.ts} +3 -3
  35. package/dist/models/index.d.ts +5 -5
  36. package/dist/models/index.js +6 -1
  37. package/dist/models/index.js.map +1 -1
  38. package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-B_siPxqN.d.ts} +1 -1
  39. package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-CK5Jdj9K.d.ts} +2 -2
  40. package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-DgvD4SCO.d.ts} +6 -6
  41. package/dist/observability/index.d.ts +2 -2
  42. package/dist/observability/index.js +8 -3
  43. package/dist/observability/index.js.map +1 -1
  44. package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-bK0JQBR_.d.ts} +9 -9
  45. package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-BPEDlN38.d.ts} +3 -3
  46. package/dist/{permission-BJ7eO9Vl.d.ts → permission-4yvGmMRB.d.ts} +1 -1
  47. package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-C6XpsBOy.d.ts} +2 -2
  48. package/dist/{pipeline-zflkI2dp.d.ts → pipeline-CXCeMz8J.d.ts} +58 -3
  49. package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-BvzRBkJc.d.ts} +5 -5
  50. package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-C5aQpDWE.d.ts} +3 -3
  51. package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CFhdtRzz.d.ts} +1 -1
  52. package/dist/sdd/index.d.ts +8 -8
  53. package/dist/sdd/index.js +39 -29
  54. package/dist/sdd/index.js.map +1 -1
  55. package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-CxiVLbt1.d.ts} +1 -1
  56. package/dist/security/index.d.ts +4 -4
  57. package/dist/security/index.js +208 -203
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-B7AivHsu.d.ts → selector-gIuhRTkN.d.ts} +1 -1
  60. package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-DkvvrpDt.d.ts} +8 -2
  61. package/dist/{session-reader-DtofsB-2.d.ts → session-reader-KdfVwkKP.d.ts} +1 -1
  62. package/dist/skills/index.js +67 -64
  63. package/dist/skills/index.js.map +1 -1
  64. package/dist/storage/index.d.ts +31 -12
  65. package/dist/storage/index.js +441 -360
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +57 -0
  68. package/dist/tools/index.js +411 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +703 -694
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/error.d.ts +7 -0
  74. package/dist/utils/error.js +8 -0
  75. package/dist/utils/error.js.map +1 -0
  76. package/dist/utils/index.d.ts +7 -67
  77. package/dist/utils/index.js +17 -5
  78. package/dist/utils/index.js.map +1 -1
  79. package/package.json +5 -1
  80. package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
@@ -1,9 +1,9 @@
1
1
  import { randomBytes, randomUUID, createHash } from 'crypto';
2
2
  import * as fsp from 'fs/promises';
3
- import * as path13 from 'path';
4
- import 'fs';
3
+ import * as path2 from 'path';
5
4
  import * as os from 'os';
6
5
  import { hostname } from 'os';
6
+ import 'fs';
7
7
 
8
8
  // src/utils/expect-defined.ts
9
9
  function expectDefined(value, label) {
@@ -15,9 +15,9 @@ function expectDefined(value, label) {
15
15
  return value;
16
16
  }
17
17
  async function atomicWrite(targetPath, content, opts = {}) {
18
- const dir = path13.dirname(targetPath);
18
+ const dir = path2.dirname(targetPath);
19
19
  await fsp.mkdir(dir, { recursive: true });
20
- const tmp = path13.join(dir, `.${path13.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
20
+ const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
21
21
  try {
22
22
  if (typeof content === "string") {
23
23
  await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -35,8 +35,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
35
35
  }
36
36
  let mode;
37
37
  try {
38
- const stat5 = await fsp.stat(targetPath);
39
- mode = stat5.mode & 511;
38
+ const stat6 = await fsp.stat(targetPath);
39
+ mode = stat6.mode & 511;
40
40
  } catch {
41
41
  mode = opts.mode;
42
42
  }
@@ -56,9 +56,9 @@ async function ensureDir(dir) {
56
56
  await fsp.mkdir(dir, { recursive: true });
57
57
  }
58
58
  async function withFileLock(targetPath, fn, opts = {}) {
59
- const dir = path13.dirname(targetPath);
59
+ const dir = path2.dirname(targetPath);
60
60
  await fsp.mkdir(dir, { recursive: true });
61
- const lockPath = path13.join(dir, `.${path13.basename(targetPath)}.lock`);
61
+ const lockPath = path2.join(dir, `.${path2.basename(targetPath)}.lock`);
62
62
  const timeoutMs = opts.timeoutMs ?? 5e3;
63
63
  const staleMs = opts.staleMs ?? 3e4;
64
64
  const started = Date.now();
@@ -69,10 +69,15 @@ async function withFileLock(targetPath, fn, opts = {}) {
69
69
  await handle.writeFile(`${process.pid}:${Date.now()}`);
70
70
  break;
71
71
  } catch (err) {
72
- if (err.code !== "EEXIST") throw err;
72
+ const code = err.code;
73
+ if (code === "ENOENT") {
74
+ await fsp.mkdir(dir, { recursive: true });
75
+ continue;
76
+ }
77
+ if (code !== "EEXIST") throw err;
73
78
  try {
74
- const stat5 = await fsp.stat(lockPath);
75
- if (Date.now() - stat5.mtimeMs > staleMs) {
79
+ const stat6 = await fsp.stat(lockPath);
80
+ if (Date.now() - stat6.mtimeMs > staleMs) {
76
81
  await fsp.unlink(lockPath);
77
82
  continue;
78
83
  }
@@ -214,6 +219,215 @@ function isEmptyMessage(msg) {
214
219
  return msg.content.length === 0;
215
220
  }
216
221
 
222
+ // src/utils/error.ts
223
+ function toErrorMessage(err) {
224
+ return err instanceof Error ? err.message : String(err);
225
+ }
226
+
227
+ // src/utils/safe-json.ts
228
+ function safeParse(input, maxBytes = 5e6) {
229
+ if (input.length > maxBytes) {
230
+ return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
231
+ }
232
+ try {
233
+ return { ok: true, value: JSON.parse(input) };
234
+ } catch (err) {
235
+ return {
236
+ ok: false,
237
+ error: toErrorMessage(err)
238
+ };
239
+ }
240
+ }
241
+
242
+ // src/utils/term.ts
243
+ var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
244
+ function isStdoutTTY() {
245
+ return hasStdout() && Boolean(process.stdout.isTTY);
246
+ }
247
+
248
+ // src/utils/color.ts
249
+ var isColorTty = () => {
250
+ if (envFlag(process.env.NO_COLOR)) return false;
251
+ if (envFlag(process.env.FORCE_COLOR)) return true;
252
+ return isStdoutTTY();
253
+ };
254
+ function envFlag(value) {
255
+ if (value === void 0) return false;
256
+ if (value.trim() === "") return false;
257
+ return !/^(0|false|no|off)$/i.test(value.trim());
258
+ }
259
+ var COLOR = isColorTty();
260
+ var wrap = (open6, close) => (s) => COLOR ? `\x1B[${open6}m${s}\x1B[${close}m` : s;
261
+ var color = {
262
+ reset: wrap("0", "0"),
263
+ bold: wrap("1", "22"),
264
+ dim: wrap("2", "22"),
265
+ italic: wrap("3", "23"),
266
+ underline: wrap("4", "24"),
267
+ red: wrap("31", "39"),
268
+ green: wrap("32", "39"),
269
+ yellow: wrap("33", "39"),
270
+ blue: wrap("34", "39"),
271
+ magenta: wrap("35", "39"),
272
+ cyan: wrap("36", "39"),
273
+ gray: wrap("90", "39"),
274
+ amber: wrap("38;5;214", "39"),
275
+ pink: wrap("38;5;205", "39"),
276
+ bgRed: wrap("41", "49"),
277
+ bgGreen: wrap("42", "49")
278
+ };
279
+ function projectHash(absRoot) {
280
+ return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
281
+ }
282
+ function projectSlug(absRoot) {
283
+ const base = slugify(path2.basename(absRoot));
284
+ const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
285
+ return `${base}-${hash}`;
286
+ }
287
+ function slugify(name) {
288
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
289
+ }
290
+ function wstackGlobalRoot() {
291
+ const fromEnv = process.env["WRONGSTACK_HOME"];
292
+ if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
293
+ return path2.join(os.homedir(), ".wrongstack");
294
+ }
295
+ function resolveWstackPaths(opts) {
296
+ const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
297
+ const hash = projectHash(opts.projectRoot);
298
+ const slug = projectSlug(opts.projectRoot);
299
+ const projectDir = path2.join(globalRoot, "projects", slug);
300
+ return {
301
+ globalRoot,
302
+ configDir: globalRoot,
303
+ globalConfig: path2.join(globalRoot, "config.json"),
304
+ secretsKey: path2.join(globalRoot, ".key"),
305
+ globalMemory: path2.join(globalRoot, "memory.md"),
306
+ globalSkills: path2.join(globalRoot, "skills"),
307
+ globalPrompts: path2.join(globalRoot, "prompts"),
308
+ cacheDir: path2.join(globalRoot, "cache"),
309
+ modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
310
+ modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
311
+ historyFile: path2.join(globalRoot, "history"),
312
+ logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
313
+ projectDir,
314
+ projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
315
+ projectMemory: path2.join(projectDir, "memory.md"),
316
+ projectSessions: path2.join(projectDir, "sessions"),
317
+ projectTrust: path2.join(projectDir, "trust.json"),
318
+ projectMeta: path2.join(projectDir, "meta.json"),
319
+ projectLocalConfig: path2.join(projectDir, "config.local.json"),
320
+ inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
321
+ inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
322
+ inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
323
+ inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
324
+ projectHash: hash,
325
+ projectSlug: slug,
326
+ projectGoal: path2.join(projectDir, "goal.json"),
327
+ projectSpecs: path2.join(projectDir, "specs"),
328
+ projectTaskGraphs: path2.join(projectDir, "task-graphs"),
329
+ projectSddSession: path2.join(projectDir, "sdd-session.json"),
330
+ projectPlan: path2.join(projectDir, "plan.json"),
331
+ projectAutophase: path2.join(projectDir, "autophase"),
332
+ syncConfig: path2.join(globalRoot, "sync.json")
333
+ };
334
+ }
335
+
336
+ // src/utils/deep-merge.ts
337
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
338
+ "__proto__",
339
+ "constructor",
340
+ "prototype",
341
+ "__defineGetter__",
342
+ "__defineSetter__",
343
+ "__lookupGetter__",
344
+ "__lookupSetter__"
345
+ ]);
346
+ function isPrimitiveArray(a) {
347
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
348
+ }
349
+ function deepMerge(base, patch, options = {}) {
350
+ const {
351
+ conflictResolution = "prefer-patch",
352
+ arrayMode = "replace",
353
+ protectProto = true,
354
+ onNonPrimitiveArrayReplace
355
+ } = options;
356
+ if (typeof base !== "object" || base === null) {
357
+ return conflictResolution === "prefer-patch" ? patch : base;
358
+ }
359
+ if (typeof patch !== "object" || patch === null) {
360
+ return conflictResolution === "prefer-patch" ? patch : base;
361
+ }
362
+ if (Array.isArray(base) && Array.isArray(patch)) {
363
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
364
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
365
+ }
366
+ return conflictResolution === "prefer-patch" ? patch : base;
367
+ }
368
+ if (Array.isArray(base) || Array.isArray(patch)) {
369
+ return conflictResolution === "prefer-patch" ? patch : base;
370
+ }
371
+ const baseObj = base;
372
+ const patchObj = patch;
373
+ const out = { ...baseObj };
374
+ for (const [k, v] of Object.entries(patchObj)) {
375
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
376
+ const existing = out[k];
377
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
378
+ out[k] = deepMerge(existing, v, options);
379
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
380
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
381
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
382
+ }
383
+ out[k] = deepMerge(existing, v, options);
384
+ } else if (v !== void 0) {
385
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
386
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
387
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
388
+ }
389
+ out[k] = v;
390
+ }
391
+ }
392
+ return out;
393
+ }
394
+
395
+ // src/utils/regex-guard.ts
396
+ var MAX_PATTERN_LEN = 512;
397
+ var DANGEROUS_PATTERNS = [
398
+ /(\([^)]*[+*][^)]*\))[+*]/,
399
+ // (a+)+, (.*)+, etc
400
+ /(\(\?:[^)]*[+*][^)]*\))[+*]/
401
+ // same, with non-capturing group
402
+ ];
403
+ function compileUserRegex(pattern, flags) {
404
+ if (typeof pattern !== "string") {
405
+ return { ok: false, reason: "pattern must be a string" };
406
+ }
407
+ if (pattern.length === 0) {
408
+ return { ok: false, reason: "pattern is empty" };
409
+ }
410
+ if (pattern.length > MAX_PATTERN_LEN) {
411
+ return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
412
+ }
413
+ for (const rx of DANGEROUS_PATTERNS) {
414
+ if (rx.test(pattern)) {
415
+ return {
416
+ ok: false,
417
+ reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
418
+ };
419
+ }
420
+ }
421
+ try {
422
+ return { ok: true, regex: new RegExp(pattern, flags) };
423
+ } catch (err) {
424
+ return {
425
+ ok: false,
426
+ reason: err instanceof Error ? err.message : "invalid regex"
427
+ };
428
+ }
429
+ }
430
+
217
431
  // src/storage/session-store.ts
218
432
  function sanitizeModel(model) {
219
433
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
@@ -270,11 +484,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
270
484
  }
271
485
  /** Absolute path to the session index file. */
272
486
  get indexFile() {
273
- return path13.join(this.dir, "_index.jsonl");
487
+ return path2.join(this.dir, "_index.jsonl");
274
488
  }
275
489
  /** Join session ID to its absolute path within the store directory. */
276
490
  sessionPath(id, ext) {
277
- return path13.join(this.dir, `${id}${ext}`);
491
+ return path2.join(this.dir, `${id}${ext}`);
278
492
  }
279
493
  /**
280
494
  * Ensure the directory implied by the session ID exists. When the ID
@@ -282,7 +496,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
282
496
  * subdirectory so sessions group naturally by day.
283
497
  */
284
498
  async ensureShardDir(id) {
285
- const dirPath = path13.dirname(path13.join(this.dir, id));
499
+ const dirPath = path2.dirname(path2.join(this.dir, id));
286
500
  await ensureDir(dirPath);
287
501
  return dirPath;
288
502
  }
@@ -290,15 +504,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
290
504
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
291
505
  const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
292
506
  const shardDir = await this.ensureShardDir(id);
293
- const file = path13.join(shardDir, `${path13.basename(id)}.jsonl`);
507
+ const file = path2.join(shardDir, `${path2.basename(id)}.jsonl`);
294
508
  const t0 = Date.now();
295
509
  let handle;
296
510
  try {
297
511
  handle = await fsp.open(file, "a", 384);
298
512
  } catch (err) {
299
- this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), false);
513
+ this.emitError(id, file, "create", toErrorMessage(err), false);
300
514
  throw new Error(
301
- `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
515
+ `Failed to open session file: ${toErrorMessage(err)}`,
302
516
  { cause: err }
303
517
  );
304
518
  }
@@ -318,7 +532,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
318
532
  message: e instanceof Error ? e.message : String(e),
319
533
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
320
534
  })));
321
- this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), true);
535
+ this.emitError(id, file, "create", toErrorMessage(err), true);
322
536
  throw err;
323
537
  }
324
538
  }
@@ -330,9 +544,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
330
544
  try {
331
545
  handle = await fsp.open(file, "a", 384);
332
546
  } catch (err) {
333
- this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), false);
547
+ this.emitError(id, file, "resume", toErrorMessage(err), false);
334
548
  throw new Error(
335
- `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
549
+ `Failed to open session "${id}" for append: ${toErrorMessage(err)}`,
336
550
  { cause: err }
337
551
  );
338
552
  }
@@ -352,7 +566,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
352
566
  // Shard directory (sessions/<date>/) — must match create() so the
353
567
  // .summary.json sidecar lands next to the JSONL instead of the
354
568
  // sessions root (where summaryFor() would never find it).
355
- dir: path13.dirname(file),
569
+ dir: path2.dirname(file),
356
570
  filePath: file,
357
571
  secretScrubber: this.secretScrubber,
358
572
  onClose: (s) => this.appendToIndex(s)
@@ -367,7 +581,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
367
581
  message: e instanceof Error ? e.message : String(e),
368
582
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
369
583
  })));
370
- this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), true);
584
+ this.emitError(id, file, "resume", toErrorMessage(err), true);
371
585
  throw err;
372
586
  }
373
587
  }
@@ -395,7 +609,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
395
609
  return { metadata: meta, events, messages, usage, toolCallEnds };
396
610
  } catch (err) {
397
611
  outcome = "failure";
398
- errorMsg = err instanceof Error ? err.message : String(err);
612
+ errorMsg = toErrorMessage(err);
399
613
  throw err;
400
614
  } finally {
401
615
  this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
@@ -478,7 +692,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
478
692
  await fsp.rename(tmp, this.indexFile);
479
693
  } catch (err) {
480
694
  outcome = "failure";
481
- errorMsg = err instanceof Error ? err.message : String(err);
695
+ errorMsg = toErrorMessage(err);
482
696
  } finally {
483
697
  this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
484
698
  }
@@ -546,7 +760,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
546
760
  continue;
547
761
  if (entry.isDirectory()) {
548
762
  const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
549
- ids.push(...await this.collectSessionIds(path13.join(dir, entry.name), childPrefix, depth + 1));
763
+ ids.push(...await this.collectSessionIds(path2.join(dir, entry.name), childPrefix, depth + 1));
550
764
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
551
765
  if (entry.name === "_index.jsonl") continue;
552
766
  const base = entry.name.replace(/\.jsonl$/, "");
@@ -566,10 +780,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
566
780
  return JSON.parse(raw);
567
781
  } catch {
568
782
  const full = this.sessionPath(id, ".jsonl");
569
- const stat5 = await fsp.stat(full);
570
- const summary = await this.summarize(id, stat5.mtime.toISOString());
783
+ const stat6 = await fsp.stat(full);
784
+ const summary = await this.summarize(id, stat6.mtime.toISOString());
571
785
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
572
- const msg = err instanceof Error ? err.message : String(err);
786
+ const msg = toErrorMessage(err);
573
787
  this.emitError(id, manifest, "summary_fallback", msg, true);
574
788
  console.warn(JSON.stringify({
575
789
  level: "warn",
@@ -597,14 +811,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
597
811
  async deleteSession(id) {
598
812
  const jsonlPath = this.sessionPath(id, ".jsonl");
599
813
  const summaryPath = this.sessionPath(id, ".summary.json");
600
- const shardDir = path13.dirname(path13.join(this.dir, id));
601
- const base = path13.basename(id);
602
- const sessDir = path13.join(shardDir, base);
814
+ const shardDir = path2.dirname(path2.join(this.dir, id));
815
+ const base = path2.basename(id);
816
+ const sessDir = path2.join(shardDir, base);
603
817
  const deletions = [
604
818
  fsp.unlink(jsonlPath),
605
819
  fsp.unlink(summaryPath),
606
- fsp.unlink(path13.join(shardDir, `${base}.plan.json`)),
607
- fsp.unlink(path13.join(shardDir, `${base}.todos.json`))
820
+ fsp.unlink(path2.join(shardDir, `${base}.plan.json`)),
821
+ fsp.unlink(path2.join(shardDir, `${base}.todos.json`))
608
822
  ];
609
823
  const results = await Promise.allSettled(deletions);
610
824
  for (const r of results) {
@@ -626,7 +840,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
626
840
  level: "warn",
627
841
  event: "session_store.rmdir_failed",
628
842
  sessionId: id,
629
- message: err instanceof Error ? err.message : String(err),
843
+ message: toErrorMessage(err),
630
844
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
631
845
  }));
632
846
  });
@@ -640,17 +854,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
640
854
  let deleted = 0;
641
855
  let activeSessionId = null;
642
856
  try {
643
- const raw = await fsp.readFile(path13.join(this.dir, "active.json"), "utf8");
857
+ const raw = await fsp.readFile(path2.join(this.dir, "active.json"), "utf8");
644
858
  const active = JSON.parse(raw);
645
859
  activeSessionId = active.sessionId ?? null;
646
860
  } catch {
647
861
  }
648
862
  const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
649
863
  const pruneFile = async (dir, name, prefix) => {
650
- const jsonlPath = path13.join(dir, name);
864
+ const jsonlPath = path2.join(dir, name);
651
865
  try {
652
- const stat5 = await fsp.stat(jsonlPath);
653
- if (stat5.mtimeMs >= cutoff) return;
866
+ const stat6 = await fsp.stat(jsonlPath);
867
+ if (stat6.mtimeMs >= cutoff) return;
654
868
  } catch {
655
869
  return;
656
870
  }
@@ -667,7 +881,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
667
881
  continue;
668
882
  }
669
883
  if (!entry.isDirectory()) continue;
670
- const dateDir = path13.join(this.dir, entry.name);
884
+ const dateDir = path2.join(this.dir, entry.name);
671
885
  const files = await fsp.readdir(dateDir, { withFileTypes: true }).catch(() => []);
672
886
  for (const file of files) {
673
887
  if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
@@ -679,7 +893,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
679
893
  }
680
894
  for (const entry of entries) {
681
895
  if (!entry.isDirectory()) continue;
682
- const dateDir = path13.join(this.dir, entry.name);
896
+ const dateDir = path2.join(this.dir, entry.name);
683
897
  try {
684
898
  const remaining = await fsp.readdir(dateDir);
685
899
  if (remaining.length === 0) {
@@ -854,7 +1068,7 @@ var FileSessionWriter = class _FileSessionWriter {
854
1068
  this.meta = meta;
855
1069
  this.events = events;
856
1070
  this.resumed = opts.resumed ?? false;
857
- this.manifestFile = opts.dir ? path13.join(opts.dir, `${path13.basename(id)}.summary.json`) : "";
1071
+ this.manifestFile = opts.dir ? path2.join(opts.dir, `${path2.basename(id)}.summary.json`) : "";
858
1072
  this.filePath = opts.filePath ?? "";
859
1073
  this.secretScrubber = opts.secretScrubber;
860
1074
  this.onCloseCb = opts.onClose;
@@ -1061,7 +1275,7 @@ var FileSessionWriter = class _FileSessionWriter {
1061
1275
  await this.enqueueWrite(batch);
1062
1276
  } catch (err) {
1063
1277
  outcome = "failure";
1064
- errorMsg = err instanceof Error ? err.message : String(err);
1278
+ errorMsg = toErrorMessage(err);
1065
1279
  this.appendFailCount += eventCount;
1066
1280
  const now = Date.now();
1067
1281
  if (now - this.lastAppendWarnAt > 5e3) {
@@ -1069,7 +1283,7 @@ var FileSessionWriter = class _FileSessionWriter {
1069
1283
  const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
1070
1284
  console.warn(
1071
1285
  "[session] flush failed:",
1072
- err instanceof Error ? err.message : String(err),
1286
+ toErrorMessage(err),
1073
1287
  tail
1074
1288
  );
1075
1289
  this.lastAppendWarnAt = now;
@@ -1159,7 +1373,7 @@ var FileSessionWriter = class _FileSessionWriter {
1159
1373
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
1160
1374
  } catch (err) {
1161
1375
  outcome = "failure";
1162
- errorMsg = err instanceof Error ? err.message : String(err);
1376
+ errorMsg = toErrorMessage(err);
1163
1377
  } finally {
1164
1378
  this.events?.emit("storage.write", {
1165
1379
  sessionId: this.id,
@@ -1180,7 +1394,7 @@ var FileSessionWriter = class _FileSessionWriter {
1180
1394
  await this.onCloseCb?.(this.summary);
1181
1395
  } catch (err) {
1182
1396
  idxOutcome = "failure";
1183
- idxError = err instanceof Error ? err.message : String(err);
1397
+ idxError = toErrorMessage(err);
1184
1398
  } finally {
1185
1399
  this.events?.emit("storage.write", {
1186
1400
  sessionId: this.summary.id,
@@ -1355,7 +1569,7 @@ var QueueStore = class {
1355
1569
  events;
1356
1570
  traceId;
1357
1571
  constructor(opts) {
1358
- this.file = path13.join(opts.dir, "queue.json");
1572
+ this.file = path2.join(opts.dir, "queue.json");
1359
1573
  this.events = opts.events;
1360
1574
  this.traceId = opts.traceId;
1361
1575
  }
@@ -1383,7 +1597,7 @@ var QueueStore = class {
1383
1597
  filePath: this.file,
1384
1598
  operation: "write",
1385
1599
  outcome: "failure",
1386
- error: err instanceof Error ? err.message : String(err),
1600
+ error: toErrorMessage(err),
1387
1601
  recoverable: false,
1388
1602
  ...this.traceId !== void 0 && { traceId: this.traceId }
1389
1603
  });
@@ -1391,7 +1605,7 @@ var QueueStore = class {
1391
1605
  level: "warn",
1392
1606
  event: "queue_store.write_failed",
1393
1607
  path: this.file,
1394
- message: err instanceof Error ? err.message : String(err),
1608
+ message: toErrorMessage(err),
1395
1609
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1396
1610
  }));
1397
1611
  }
@@ -1421,7 +1635,7 @@ var QueueStore = class {
1421
1635
  filePath: this.file,
1422
1636
  operation: "read",
1423
1637
  outcome: "failure",
1424
- error: err instanceof Error ? err.message : String(err),
1638
+ error: toErrorMessage(err),
1425
1639
  recoverable: true,
1426
1640
  ...this.traceId !== void 0 && { traceId: this.traceId }
1427
1641
  });
@@ -1429,7 +1643,7 @@ var QueueStore = class {
1429
1643
  level: "warn",
1430
1644
  event: "queue_store.read_failed",
1431
1645
  path: this.file,
1432
- message: err instanceof Error ? err.message : String(err),
1646
+ message: toErrorMessage(err),
1433
1647
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1434
1648
  }));
1435
1649
  return [];
@@ -1500,7 +1714,7 @@ var QueueStore = class {
1500
1714
  filePath: this.file,
1501
1715
  operation: "clear",
1502
1716
  outcome: "failure",
1503
- error: err instanceof Error ? err.message : String(err),
1717
+ error: toErrorMessage(err),
1504
1718
  recoverable: true,
1505
1719
  ...this.traceId !== void 0 && { traceId: this.traceId }
1506
1720
  });
@@ -1539,7 +1753,7 @@ var DefaultAttachmentStore = class {
1539
1753
  let data = input.data;
1540
1754
  if (this.spoolDir && bytes >= this.spoolThreshold) {
1541
1755
  await fsp.mkdir(this.spoolDir, { recursive: true });
1542
- spooledPath = path13.join(this.spoolDir, `${id}.bin`);
1756
+ spooledPath = path2.join(this.spoolDir, `${id}.bin`);
1543
1757
  await atomicWrite(spooledPath, input.data, {
1544
1758
  encoding: input.kind === "image" ? "base64" : "utf8"
1545
1759
  });
@@ -1750,7 +1964,7 @@ var FileMemoryBackend = class {
1750
1964
  }
1751
1965
  async remember(scope, entry, filePath) {
1752
1966
  const file = this.resolveFile(filePath, scope);
1753
- await ensureDir(path13.dirname(file));
1967
+ await ensureDir(path2.dirname(file));
1754
1968
  let existing = "";
1755
1969
  try {
1756
1970
  existing = await fsp.readFile(file, "utf8");
@@ -1978,7 +2192,7 @@ ${body.trim()}`);
1978
2192
  operation: "readAll",
1979
2193
  outcome: "failure",
1980
2194
  durationMs: dur,
1981
- error: err instanceof Error ? err.message : String(err),
2195
+ error: toErrorMessage(err),
1982
2196
  ...this.traceId !== void 0 && { traceId: this.traceId }
1983
2197
  });
1984
2198
  throw err;
@@ -2011,7 +2225,7 @@ ${body.trim()}`);
2011
2225
  operation: "read",
2012
2226
  outcome: "failure",
2013
2227
  durationMs: dur,
2014
- error: err instanceof Error ? err.message : String(err),
2228
+ error: toErrorMessage(err),
2015
2229
  ...this.traceId !== void 0 && { traceId: this.traceId }
2016
2230
  });
2017
2231
  throw err;
@@ -2064,7 +2278,7 @@ ${body.trim()}`);
2064
2278
  operation: "remember",
2065
2279
  outcome: "failure",
2066
2280
  durationMs: dur,
2067
- error: err instanceof Error ? err.message : String(err),
2281
+ error: toErrorMessage(err),
2068
2282
  ...this.traceId !== void 0 && { traceId: this.traceId }
2069
2283
  });
2070
2284
  throw err;
@@ -2220,7 +2434,7 @@ ${body.trim()}`);
2220
2434
  operation: "forget",
2221
2435
  outcome: "failure",
2222
2436
  durationMs: dur,
2223
- error: err instanceof Error ? err.message : String(err),
2437
+ error: toErrorMessage(err),
2224
2438
  ...this.traceId !== void 0 && { traceId: this.traceId }
2225
2439
  });
2226
2440
  throw err;
@@ -2262,7 +2476,7 @@ ${body.trim()}`);
2262
2476
  operation: "consolidate",
2263
2477
  outcome: "failure",
2264
2478
  durationMs: dur,
2265
- error: err instanceof Error ? err.message : String(err),
2479
+ error: toErrorMessage(err),
2266
2480
  ...this.traceId !== void 0 && { traceId: this.traceId }
2267
2481
  });
2268
2482
  throw err;
@@ -2302,7 +2516,7 @@ ${body.trim()}`);
2302
2516
  operation: "clear",
2303
2517
  outcome: "failure",
2304
2518
  durationMs: dur,
2305
- error: err instanceof Error ? err.message : String(err),
2519
+ error: toErrorMessage(err),
2306
2520
  ...this.traceId !== void 0 && { traceId: this.traceId }
2307
2521
  });
2308
2522
  throw err;
@@ -2338,7 +2552,7 @@ ${body.trim()}`);
2338
2552
  operation: "clear",
2339
2553
  outcome: "failure",
2340
2554
  durationMs: dur,
2341
- error: err instanceof Error ? err.message : String(err),
2555
+ error: toErrorMessage(err),
2342
2556
  ...this.traceId !== void 0 && { traceId: this.traceId }
2343
2557
  });
2344
2558
  throw err;
@@ -2899,7 +3113,7 @@ var DefaultConfigStore = class {
2899
3113
  console.error(JSON.stringify({
2900
3114
  level: "error",
2901
3115
  event: "config_store.watcher_threw",
2902
- message: err instanceof Error ? err.message : String(err),
3116
+ message: toErrorMessage(err),
2903
3117
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2904
3118
  }));
2905
3119
  }
@@ -2923,67 +3137,6 @@ function deepFreeze(obj) {
2923
3137
  }
2924
3138
  return Object.freeze(obj);
2925
3139
  }
2926
-
2927
- // src/utils/deep-merge.ts
2928
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
2929
- "__proto__",
2930
- "constructor",
2931
- "prototype",
2932
- "__defineGetter__",
2933
- "__defineSetter__",
2934
- "__lookupGetter__",
2935
- "__lookupSetter__"
2936
- ]);
2937
- function isPrimitiveArray(a) {
2938
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
2939
- }
2940
- function deepMerge(base, patch, options = {}) {
2941
- const {
2942
- conflictResolution = "prefer-patch",
2943
- arrayMode = "replace",
2944
- protectProto = true,
2945
- onNonPrimitiveArrayReplace
2946
- } = options;
2947
- if (typeof base !== "object" || base === null) {
2948
- return conflictResolution === "prefer-patch" ? patch : base;
2949
- }
2950
- if (typeof patch !== "object" || patch === null) {
2951
- return conflictResolution === "prefer-patch" ? patch : base;
2952
- }
2953
- if (Array.isArray(base) && Array.isArray(patch)) {
2954
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
2955
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
2956
- }
2957
- return conflictResolution === "prefer-patch" ? patch : base;
2958
- }
2959
- if (Array.isArray(base) || Array.isArray(patch)) {
2960
- return conflictResolution === "prefer-patch" ? patch : base;
2961
- }
2962
- const baseObj = base;
2963
- const patchObj = patch;
2964
- const out = { ...baseObj };
2965
- for (const [k, v] of Object.entries(patchObj)) {
2966
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
2967
- const existing = out[k];
2968
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
2969
- out[k] = deepMerge(existing, v, options);
2970
- } else if (Array.isArray(v) && Array.isArray(existing)) {
2971
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
2972
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
2973
- }
2974
- out[k] = deepMerge(existing, v, options);
2975
- } else if (v !== void 0) {
2976
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
2977
- const existingLen = Array.isArray(existing) ? existing.length : 0;
2978
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
2979
- }
2980
- out[k] = v;
2981
- }
2982
- }
2983
- return out;
2984
- }
2985
-
2986
- // src/security/secret-vault.ts
2987
3140
  function decryptConfigSecrets(cfg, vault, opts) {
2988
3141
  const warn = ((msg) => console.warn(msg));
2989
3142
  return walk(cfg, vault, (v, key) => {
@@ -3074,21 +3227,6 @@ function isContextWindowModeId(id) {
3074
3227
  return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
3075
3228
  }
3076
3229
 
3077
- // src/utils/safe-json.ts
3078
- function safeParse(input, maxBytes = 5e6) {
3079
- if (input.length > maxBytes) {
3080
- return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
3081
- }
3082
- try {
3083
- return { ok: true, value: JSON.parse(input) };
3084
- } catch (err) {
3085
- return {
3086
- ok: false,
3087
- error: err instanceof Error ? err.message : String(err)
3088
- };
3089
- }
3090
- }
3091
-
3092
3230
  // src/types/default-config.ts
3093
3231
  var DEFAULT_TOOLS_CONFIG = Object.freeze({
3094
3232
  defaultExecutionStrategy: "smart",
@@ -3261,7 +3399,7 @@ var DefaultConfigLoader = class {
3261
3399
  level: "warn",
3262
3400
  event: "config.source_load_failed",
3263
3401
  source: src.name,
3264
- message: err instanceof Error ? err.message : String(err),
3402
+ message: toErrorMessage(err),
3265
3403
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3266
3404
  }));
3267
3405
  }
@@ -3398,7 +3536,7 @@ var DefaultConfigLoader = class {
3398
3536
  console.warn(JSON.stringify({
3399
3537
  level: "warn",
3400
3538
  event: "config.sync_load_failed",
3401
- message: err instanceof Error ? err.message : String(err),
3539
+ message: toErrorMessage(err),
3402
3540
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3403
3541
  }));
3404
3542
  return null;
@@ -3425,7 +3563,7 @@ var DefaultConfigLoader = class {
3425
3563
  level: "warn",
3426
3564
  event: "config.read_failed",
3427
3565
  path: file,
3428
- message: err instanceof Error ? err.message : String(err),
3566
+ message: toErrorMessage(err),
3429
3567
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3430
3568
  }));
3431
3569
  }
@@ -3576,7 +3714,7 @@ var RecoveryLock = class {
3576
3714
  sessionStore;
3577
3715
  probe;
3578
3716
  constructor(opts) {
3579
- this.file = path13.join(opts.dir, LOCK_FILE);
3717
+ this.file = path2.join(opts.dir, LOCK_FILE);
3580
3718
  this.pid = opts.pid ?? process.pid;
3581
3719
  this.hostname = opts.hostname ?? os.hostname();
3582
3720
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -3637,7 +3775,7 @@ var RecoveryLock = class {
3637
3775
  * null return before calling this.
3638
3776
  */
3639
3777
  async write(sessionId) {
3640
- await ensureDir(path13.dirname(this.file));
3778
+ await ensureDir(path2.dirname(this.file));
3641
3779
  const lock = {
3642
3780
  v: 1,
3643
3781
  sessionId,
@@ -3704,42 +3842,6 @@ function defaultIsPidAlive(pid) {
3704
3842
  }
3705
3843
  }
3706
3844
 
3707
- // src/utils/regex-guard.ts
3708
- var MAX_PATTERN_LEN = 512;
3709
- var DANGEROUS_PATTERNS = [
3710
- /(\([^)]*[+*][^)]*\))[+*]/,
3711
- // (a+)+, (.*)+, etc
3712
- /(\(\?:[^)]*[+*][^)]*\))[+*]/
3713
- // same, with non-capturing group
3714
- ];
3715
- function compileUserRegex(pattern, flags) {
3716
- if (typeof pattern !== "string") {
3717
- return { ok: false, reason: "pattern must be a string" };
3718
- }
3719
- if (pattern.length === 0) {
3720
- return { ok: false, reason: "pattern is empty" };
3721
- }
3722
- if (pattern.length > MAX_PATTERN_LEN) {
3723
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
3724
- }
3725
- for (const rx of DANGEROUS_PATTERNS) {
3726
- if (rx.test(pattern)) {
3727
- return {
3728
- ok: false,
3729
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
3730
- };
3731
- }
3732
- }
3733
- try {
3734
- return { ok: true, regex: new RegExp(pattern, flags) };
3735
- } catch (err) {
3736
- return {
3737
- ok: false,
3738
- reason: err instanceof Error ? err.message : "invalid regex"
3739
- };
3740
- }
3741
- }
3742
-
3743
3845
  // src/storage/session-reader.ts
3744
3846
  var DefaultSessionReader = class {
3745
3847
  store;
@@ -4019,9 +4121,9 @@ function sessionScopedPath(dir, sessionId, suffix) {
4019
4121
  if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
4020
4122
  throw invalid(sessionId);
4021
4123
  }
4022
- const resolved = path13.resolve(dir, `${sessionId}${suffix}`);
4023
- const rel = path13.relative(path13.resolve(dir), resolved);
4024
- if (rel.startsWith("..") || path13.isAbsolute(rel)) {
4124
+ const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
4125
+ const rel = path2.relative(path2.resolve(dir), resolved);
4126
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
4025
4127
  throw invalid(sessionId);
4026
4128
  }
4027
4129
  return resolved;
@@ -4083,7 +4185,7 @@ var AnnotationsStore = class {
4083
4185
  operation: "list",
4084
4186
  outcome: "failure",
4085
4187
  durationMs: Date.now() - t0,
4086
- error: err instanceof Error ? err.message : String(err),
4188
+ error: toErrorMessage(err),
4087
4189
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4088
4190
  });
4089
4191
  return [];
@@ -4188,7 +4290,7 @@ var AnnotationsStore = class {
4188
4290
  filePath: fp,
4189
4291
  operation: "add",
4190
4292
  outcome: "failure",
4191
- error: err instanceof Error ? err.message : String(err),
4293
+ error: toErrorMessage(err),
4192
4294
  recoverable: false,
4193
4295
  durationMs: Date.now() - t0,
4194
4296
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
@@ -4244,7 +4346,7 @@ var AnnotationsStore = class {
4244
4346
  filePath: fp,
4245
4347
  operation: "resolve",
4246
4348
  outcome: "failure",
4247
- error: err instanceof Error ? err.message : String(err),
4349
+ error: toErrorMessage(err),
4248
4350
  recoverable: false,
4249
4351
  durationMs: Date.now() - t0,
4250
4352
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
@@ -4364,22 +4466,40 @@ var ReplayLogStore = class {
4364
4466
  const t0 = Date.now();
4365
4467
  try {
4366
4468
  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;
4469
+ await withFileLock(fp, async () => {
4470
+ const cache = await this.ensureCache(input.sessionId);
4471
+ if (cache.has(hash)) return;
4370
4472
  const entry = {
4371
4473
  hash,
4372
4474
  ts: (/* @__PURE__ */ new Date()).toISOString(),
4373
4475
  request: input.request,
4374
4476
  response: input.response
4375
4477
  };
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");
4478
+ const currentCount = this.diskCount.get(input.sessionId) ?? 0;
4479
+ const willEvict = currentCount + 1 > this.maxEntries;
4480
+ if (!willEvict) {
4481
+ await fsp.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
4482
+ cache.set(hash, entry);
4483
+ this.diskCount.set(input.sessionId, currentCount + 1);
4484
+ this.events?.emit("storage.write", {
4485
+ sessionId: input.sessionId,
4486
+ store: "replay",
4487
+ filePath: fp,
4488
+ operation: "record",
4489
+ outcome: "success",
4490
+ durationMs: Date.now() - t0,
4491
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4492
+ });
4493
+ return;
4494
+ }
4495
+ const all = await this.readAll(input.sessionId);
4496
+ all.push(entry);
4497
+ const keep = all.slice(-this.maxEntries);
4498
+ const refreshed = /* @__PURE__ */ new Map();
4499
+ for (const e of keep) refreshed.set(e.hash, e);
4500
+ this.cache.set(input.sessionId, refreshed);
4501
+ this.diskCount.set(input.sessionId, keep.length);
4502
+ await this.writeAll(input.sessionId, keep, "compact");
4383
4503
  });
4384
4504
  });
4385
4505
  return hash;
@@ -4482,7 +4602,7 @@ var ReplayLogStore = class {
4482
4602
  level: "warn",
4483
4603
  event: "replay_log_store.list_readdir_failed",
4484
4604
  dir,
4485
- message: err instanceof Error ? err.message : String(err),
4605
+ message: toErrorMessage(err),
4486
4606
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4487
4607
  }));
4488
4608
  }
@@ -4491,7 +4611,7 @@ var ReplayLogStore = class {
4491
4611
  for (const entry of entries) {
4492
4612
  if (entry.name.startsWith(".")) continue;
4493
4613
  if (entry.isDirectory()) {
4494
- if (depth === 0) await scan(path13.join(dir, entry.name), entry.name, depth + 1);
4614
+ if (depth === 0) await scan(path2.join(dir, entry.name), entry.name, depth + 1);
4495
4615
  continue;
4496
4616
  }
4497
4617
  if (!entry.isFile() || !entry.name.endsWith(".replay.jsonl")) continue;
@@ -4501,7 +4621,7 @@ var ReplayLogStore = class {
4501
4621
  out.push({
4502
4622
  sessionId,
4503
4623
  entryCount: all.length,
4504
- path: path13.join(dir, entry.name)
4624
+ path: path2.join(dir, entry.name)
4505
4625
  });
4506
4626
  }
4507
4627
  };
@@ -4590,15 +4710,15 @@ var SessionRecovery = class {
4590
4710
  async detectStale(sessionId) {
4591
4711
  const fp = this.filePath(sessionId);
4592
4712
  const TAIL_SIZE = 8192;
4593
- let stat5;
4713
+ let stat6;
4594
4714
  try {
4595
- stat5 = await fsp.stat(fp);
4715
+ stat6 = await fsp.stat(fp);
4596
4716
  } catch (err) {
4597
4717
  if (err.code === "ENOENT") return null;
4598
4718
  return null;
4599
4719
  }
4600
- if (stat5.size === 0) return null;
4601
- const position = Math.max(0, stat5.size - TAIL_SIZE);
4720
+ if (stat6.size === 0) return null;
4721
+ const position = Math.max(0, stat6.size - TAIL_SIZE);
4602
4722
  const buf = Buffer.alloc(TAIL_SIZE);
4603
4723
  let fh;
4604
4724
  try {
@@ -4702,7 +4822,7 @@ var SessionRecovery = class {
4702
4822
  continue;
4703
4823
  if (entry.isDirectory()) {
4704
4824
  if (depth === 0) {
4705
- await collect(path13.join(dir, entry.name), entry.name, depth + 1);
4825
+ await collect(path2.join(dir, entry.name), entry.name, depth + 1);
4706
4826
  }
4707
4827
  continue;
4708
4828
  }
@@ -4734,6 +4854,13 @@ var ToolAuditLog = class {
4734
4854
  tailHash = /* @__PURE__ */ new Map();
4735
4855
  /** In-memory counter for entry indices — avoids re-reading the file on every write. */
4736
4856
  tailIndex = /* @__PURE__ */ new Map();
4857
+ /**
4858
+ * File mtime+size recorded after our last write, per session. Used to
4859
+ * detect cross-process writes (session handoff, recovery) that would
4860
+ * invalidate the in-memory tail cache: if the stat no longer matches
4861
+ * we re-read the file to re-establish the chain tip before appending.
4862
+ */
4863
+ tailStat = /* @__PURE__ */ new Map();
4737
4864
  /** Tracks writes since last fsync, per session. */
4738
4865
  unSyncedWrites = /* @__PURE__ */ new Map();
4739
4866
  writeChains = /* @__PURE__ */ new Map();
@@ -4757,10 +4884,9 @@ var ToolAuditLog = class {
4757
4884
  try {
4758
4885
  await this.enqueue(input.sessionId, async () => {
4759
4886
  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;
4887
+ const tip = await this._resolveChainTip(input.sessionId, fp);
4888
+ const prevHash = tip.prevHash;
4889
+ const index = tip.nextIndex;
4764
4890
  const id = randomUUID();
4765
4891
  const ts = (/* @__PURE__ */ new Date()).toISOString();
4766
4892
  const content = {
@@ -4787,10 +4913,15 @@ var ToolAuditLog = class {
4787
4913
  isError: input.isError,
4788
4914
  index
4789
4915
  };
4790
- entries.push(entry);
4791
- await this.writeAll(input.sessionId, entries);
4916
+ await fsp.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
4917
+ try {
4918
+ const st = await fsp.stat(fp);
4919
+ this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
4920
+ } catch {
4921
+ }
4792
4922
  this.tailHash.set(input.sessionId, hash);
4793
4923
  this.tailIndex.set(input.sessionId, index + 1);
4924
+ await this._trackUnsynced(input.sessionId, fp);
4794
4925
  const durationMs = Date.now() - t0;
4795
4926
  this.events?.emit("storage.write", {
4796
4927
  sessionId: input.sessionId,
@@ -4811,7 +4942,7 @@ var ToolAuditLog = class {
4811
4942
  filePath: fp,
4812
4943
  operation: "record",
4813
4944
  outcome: "failure",
4814
- error: err instanceof Error ? err.message : String(err),
4945
+ error: toErrorMessage(err),
4815
4946
  recoverable: false,
4816
4947
  durationMs: Date.now() - t0,
4817
4948
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
@@ -4819,6 +4950,45 @@ var ToolAuditLog = class {
4819
4950
  throw err;
4820
4951
  }
4821
4952
  }
4953
+ /**
4954
+ * Resolve the chain tip (previous hash + next index) for an append.
4955
+ * Uses the in-memory `tailHash`/`tailIndex` cache when the file's
4956
+ * stat matches our last known write; falls back to a full read on
4957
+ * cache miss or when an external writer has extended the file.
4958
+ */
4959
+ async _resolveChainTip(sessionId, fp) {
4960
+ const cachedHash = this.tailHash.get(sessionId);
4961
+ const cachedIndex = this.tailIndex.get(sessionId);
4962
+ const cachedStat = this.tailStat.get(sessionId);
4963
+ if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
4964
+ try {
4965
+ const st = await fsp.stat(fp);
4966
+ if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
4967
+ return { prevHash: cachedHash, nextIndex: cachedIndex };
4968
+ }
4969
+ } catch (err) {
4970
+ if (err.code === "ENOENT") {
4971
+ this.tailHash.delete(sessionId);
4972
+ this.tailIndex.delete(sessionId);
4973
+ this.tailStat.delete(sessionId);
4974
+ return { prevHash: GENESIS_PREV, nextIndex: 0 };
4975
+ }
4976
+ throw err;
4977
+ }
4978
+ }
4979
+ const entries = await this.readAll(sessionId);
4980
+ const prev = entries.at(-1);
4981
+ const prevHash = prev?.hash ?? GENESIS_PREV;
4982
+ const nextIndex = prev ? prev.index + 1 : 0;
4983
+ this.tailHash.set(sessionId, prevHash);
4984
+ this.tailIndex.set(sessionId, nextIndex);
4985
+ try {
4986
+ const st = await fsp.stat(fp);
4987
+ this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
4988
+ } catch {
4989
+ }
4990
+ return { prevHash, nextIndex };
4991
+ }
4822
4992
  /**
4823
4993
  * Walk the chain and verify every entry's hash and prevHash.
4824
4994
  * Returns a structured verdict — never throws.
@@ -4837,7 +5007,7 @@ var ToolAuditLog = class {
4837
5007
  operation: "verify",
4838
5008
  outcome: "failure",
4839
5009
  durationMs: Date.now() - t0,
4840
- error: err instanceof Error ? err.message : String(err),
5010
+ error: toErrorMessage(err),
4841
5011
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4842
5012
  });
4843
5013
  return { ok: true, entries: 0 };
@@ -4921,7 +5091,7 @@ var ToolAuditLog = class {
4921
5091
  operation: "load",
4922
5092
  outcome: "failure",
4923
5093
  durationMs,
4924
- error: err instanceof Error ? err.message : String(err),
5094
+ error: toErrorMessage(err),
4925
5095
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4926
5096
  });
4927
5097
  throw err;
@@ -4950,10 +5120,12 @@ var ToolAuditLog = class {
4950
5120
  throw err;
4951
5121
  }
4952
5122
  }
4953
- async writeAll(sessionId, entries) {
4954
- const fp = this.filePath(sessionId);
4955
- const line = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
4956
- await atomicWrite(fp, line, { mode: 384 });
5123
+ /**
5124
+ * Tracks writes since last fsync and triggers periodic fsync.
5125
+ * Called after each O(1) append to maintain the same durability
5126
+ * guarantees as the old writeAll approach.
5127
+ */
5128
+ async _trackUnsynced(sessionId, fp) {
4957
5129
  const count = (this.unSyncedWrites.get(sessionId) ?? 0) + 1;
4958
5130
  this.unSyncedWrites.set(sessionId, count);
4959
5131
  if (this.fsyncEvery !== Number.POSITIVE_INFINITY && count % this.fsyncEvery === 0) {
@@ -5122,7 +5294,7 @@ var SessionRegistry = class {
5122
5294
  heartbeatTimer = null;
5123
5295
  currentSessionId = null;
5124
5296
  constructor(globalRoot) {
5125
- this.filePath = path13.join(globalRoot, REGISTRY_FILE);
5297
+ this.filePath = path2.join(globalRoot, REGISTRY_FILE);
5126
5298
  }
5127
5299
  // ── Public API ──────────────────────────────────────────────────────────
5128
5300
  /**
@@ -5289,7 +5461,7 @@ var SessionRegistry = class {
5289
5461
  const retryDelayMs = 20;
5290
5462
  for (let attempt = 0; attempt < maxRetries; attempt++) {
5291
5463
  try {
5292
- await fsp.mkdir(path13.dirname(this.filePath), { recursive: true });
5464
+ await fsp.mkdir(path2.dirname(this.filePath), { recursive: true });
5293
5465
  const lockHandle = await fsp.open(lockPath, "wx").catch(() => null);
5294
5466
  if (!lockHandle) {
5295
5467
  await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
@@ -5502,7 +5674,7 @@ var DefaultSessionRewinder = class {
5502
5674
  sessionsDir;
5503
5675
  projectRoot;
5504
5676
  async listCheckpoints(sessionId) {
5505
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5677
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
5506
5678
  const raw = await fsp.readFile(file, "utf8");
5507
5679
  const events = parseEvents(raw);
5508
5680
  const fileCountMap = /* @__PURE__ */ new Map();
@@ -5527,7 +5699,7 @@ var DefaultSessionRewinder = class {
5527
5699
  return checkpoints;
5528
5700
  }
5529
5701
  async rewindToCheckpoint(sessionId, checkpointIndex) {
5530
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5702
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
5531
5703
  const raw = await fsp.readFile(file, "utf8");
5532
5704
  const events = parseEvents(raw);
5533
5705
  let targetIdx = -1;
@@ -5566,7 +5738,7 @@ var DefaultSessionRewinder = class {
5566
5738
  return { ...result, toPromptIndex: checkpointIndex, removedEvents };
5567
5739
  }
5568
5740
  async rewindLastN(sessionId, n) {
5569
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5741
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
5570
5742
  const raw = await fsp.readFile(file, "utf8");
5571
5743
  const events = parseEvents(raw);
5572
5744
  const checkpoints = [];
@@ -5595,7 +5767,7 @@ var DefaultSessionRewinder = class {
5595
5767
  return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
5596
5768
  }
5597
5769
  async rewindToStart(sessionId) {
5598
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5770
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
5599
5771
  const raw = await fsp.readFile(file, "utf8");
5600
5772
  const events = parseEvents(raw);
5601
5773
  const allSnapshots = [];
@@ -5631,10 +5803,10 @@ async function revertSnapshots(snapshots, projectRoot) {
5631
5803
  for (const snapshot of snapshots) {
5632
5804
  for (const file of snapshot.files) {
5633
5805
  try {
5634
- const absPath = path13.resolve(file.path);
5635
- const root = path13.resolve(projectRoot);
5636
- const rel = path13.relative(root, absPath);
5637
- if (rel.startsWith("..") || path13.isAbsolute(rel)) {
5806
+ const absPath = path2.resolve(file.path);
5807
+ const root = path2.resolve(projectRoot);
5808
+ const rel = path2.relative(root, absPath);
5809
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
5638
5810
  errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
5639
5811
  continue;
5640
5812
  }
@@ -5653,7 +5825,7 @@ async function revertSnapshots(snapshots, projectRoot) {
5653
5825
  }
5654
5826
  }
5655
5827
  } catch (err) {
5656
- errors.push(`${file.path}: ${err instanceof Error ? err.message : String(err)}`);
5828
+ errors.push(`${file.path}: ${toErrorMessage(err)}`);
5657
5829
  }
5658
5830
  }
5659
5831
  }
@@ -5671,7 +5843,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
5671
5843
  filePath,
5672
5844
  operation: "load",
5673
5845
  outcome: "failure",
5674
- error: err instanceof Error ? err.message : String(err),
5846
+ error: toErrorMessage(err),
5675
5847
  recoverable: true
5676
5848
  });
5677
5849
  return null;
@@ -5743,13 +5915,13 @@ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)
5743
5915
  filePath,
5744
5916
  operation: "save",
5745
5917
  outcome: "failure",
5746
- error: err instanceof Error ? err.message : String(err),
5918
+ error: toErrorMessage(err),
5747
5919
  recoverable: false
5748
5920
  });
5749
5921
  console.warn(JSON.stringify({
5750
5922
  level: "warn",
5751
5923
  event: "todos_checkpoint.save_failed",
5752
- message: err instanceof Error ? err.message : String(err),
5924
+ message: toErrorMessage(err),
5753
5925
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5754
5926
  }));
5755
5927
  }
@@ -5760,7 +5932,7 @@ function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
5760
5932
  let writeChain = Promise.resolve();
5761
5933
  const enqueueWrite = (todos) => {
5762
5934
  writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
5763
- const msg = err instanceof Error ? err.message : String(err);
5935
+ const msg = toErrorMessage(err);
5764
5936
  console.error(JSON.stringify({
5765
5937
  level: "error",
5766
5938
  event: "todos_checkpoint.write_chain_failed",
@@ -5809,7 +5981,7 @@ async function loadPlan(filePath, events) {
5809
5981
  store: "plan",
5810
5982
  filePath,
5811
5983
  operation: "load",
5812
- error: err instanceof Error ? err.message : String(err),
5984
+ error: toErrorMessage(err),
5813
5985
  recoverable: true
5814
5986
  });
5815
5987
  return null;
@@ -5868,12 +6040,12 @@ async function savePlan(filePath, plan, events) {
5868
6040
  store: "plan",
5869
6041
  filePath,
5870
6042
  operation: "save",
5871
- error: err instanceof Error ? err.message : String(err),
6043
+ error: toErrorMessage(err),
5872
6044
  recoverable: false
5873
6045
  });
5874
6046
  console.warn(
5875
6047
  "[plan-store] save failed:",
5876
- err instanceof Error ? err.message : String(err)
6048
+ toErrorMessage(err)
5877
6049
  );
5878
6050
  }
5879
6051
  }
@@ -6116,7 +6288,7 @@ async function loadTasks(filePath, events, traceId) {
6116
6288
  filePath,
6117
6289
  operation: "load",
6118
6290
  outcome: "failure",
6119
- error: err instanceof Error ? err.message : String(err),
6291
+ error: toErrorMessage(err),
6120
6292
  recoverable: true
6121
6293
  });
6122
6294
  return null;
@@ -6181,13 +6353,13 @@ async function saveTasks(filePath, tasks, events, traceId) {
6181
6353
  filePath,
6182
6354
  operation: "save",
6183
6355
  outcome: "failure",
6184
- error: err instanceof Error ? err.message : String(err),
6356
+ error: toErrorMessage(err),
6185
6357
  recoverable: false,
6186
6358
  ...traceId !== void 0 && { traceId }
6187
6359
  });
6188
6360
  console.warn(
6189
6361
  "[task-store] save failed:",
6190
- err instanceof Error ? err.message : String(err)
6362
+ toErrorMessage(err)
6191
6363
  );
6192
6364
  }
6193
6365
  }
@@ -6366,7 +6538,7 @@ var DirectorStateCheckpoint = class {
6366
6538
  } catch (err) {
6367
6539
  console.warn(
6368
6540
  "[director-state] checkpoint write failed:",
6369
- err instanceof Error ? err.message : String(err)
6541
+ toErrorMessage(err)
6370
6542
  );
6371
6543
  } finally {
6372
6544
  this.writing = false;
@@ -6377,102 +6549,6 @@ var DirectorStateCheckpoint = class {
6377
6549
  }
6378
6550
  }
6379
6551
  };
6380
-
6381
- // src/utils/term.ts
6382
- var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
6383
- function isStdoutTTY() {
6384
- return hasStdout() && Boolean(process.stdout.isTTY);
6385
- }
6386
-
6387
- // src/utils/color.ts
6388
- var isColorTty = () => {
6389
- if (envFlag(process.env.NO_COLOR)) return false;
6390
- if (envFlag(process.env.FORCE_COLOR)) return true;
6391
- return isStdoutTTY();
6392
- };
6393
- function envFlag(value) {
6394
- if (value === void 0) return false;
6395
- if (value.trim() === "") return false;
6396
- return !/^(0|false|no|off)$/i.test(value.trim());
6397
- }
6398
- var COLOR = isColorTty();
6399
- var wrap = (open6, close) => (s) => COLOR ? `\x1B[${open6}m${s}\x1B[${close}m` : s;
6400
- var color = {
6401
- reset: wrap("0", "0"),
6402
- bold: wrap("1", "22"),
6403
- dim: wrap("2", "22"),
6404
- italic: wrap("3", "23"),
6405
- underline: wrap("4", "24"),
6406
- red: wrap("31", "39"),
6407
- green: wrap("32", "39"),
6408
- yellow: wrap("33", "39"),
6409
- blue: wrap("34", "39"),
6410
- magenta: wrap("35", "39"),
6411
- cyan: wrap("36", "39"),
6412
- gray: wrap("90", "39"),
6413
- amber: wrap("38;5;214", "39"),
6414
- pink: wrap("38;5;205", "39"),
6415
- bgRed: wrap("41", "49"),
6416
- bgGreen: wrap("42", "49")
6417
- };
6418
- function projectHash(absRoot) {
6419
- return createHash("sha256").update(path13.resolve(absRoot)).digest("hex").slice(0, 12);
6420
- }
6421
- function projectSlug(absRoot) {
6422
- const base = slugify(path13.basename(absRoot));
6423
- const hash = createHash("sha256").update(path13.resolve(absRoot)).digest("hex").slice(0, 6);
6424
- return `${base}-${hash}`;
6425
- }
6426
- function slugify(name) {
6427
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
6428
- }
6429
- function wstackGlobalRoot() {
6430
- const fromEnv = process.env["WRONGSTACK_HOME"];
6431
- if (fromEnv && fromEnv.trim().length > 0) return path13.resolve(fromEnv);
6432
- return path13.join(os.homedir(), ".wrongstack");
6433
- }
6434
- function resolveWstackPaths(opts) {
6435
- const globalRoot = opts.globalRoot ?? (opts.userHome ? path13.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
6436
- const hash = projectHash(opts.projectRoot);
6437
- const slug = projectSlug(opts.projectRoot);
6438
- const projectDir = path13.join(globalRoot, "projects", slug);
6439
- return {
6440
- globalRoot,
6441
- configDir: globalRoot,
6442
- globalConfig: path13.join(globalRoot, "config.json"),
6443
- secretsKey: path13.join(globalRoot, ".key"),
6444
- globalMemory: path13.join(globalRoot, "memory.md"),
6445
- globalSkills: path13.join(globalRoot, "skills"),
6446
- globalPrompts: path13.join(globalRoot, "prompts"),
6447
- cacheDir: path13.join(globalRoot, "cache"),
6448
- modelsCache: path13.join(globalRoot, "cache", "models.dev.json"),
6449
- modelsOverlayCache: path13.join(globalRoot, "cache", "models-overlay.json"),
6450
- historyFile: path13.join(globalRoot, "history"),
6451
- logFile: path13.join(globalRoot, "logs", "wrongstack.log"),
6452
- projectDir,
6453
- projectCodebaseIndex: path13.join(projectDir, "codebase-index"),
6454
- projectMemory: path13.join(projectDir, "memory.md"),
6455
- projectSessions: path13.join(projectDir, "sessions"),
6456
- projectTrust: path13.join(projectDir, "trust.json"),
6457
- projectMeta: path13.join(projectDir, "meta.json"),
6458
- projectLocalConfig: path13.join(projectDir, "config.local.json"),
6459
- inProjectConfig: path13.join(opts.projectRoot, ".wrongstack", "config.json"),
6460
- inProjectAgentsFile: path13.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
6461
- inProjectSkills: path13.join(opts.projectRoot, ".wrongstack", "skills"),
6462
- inProjectWorktrees: path13.join(opts.projectRoot, ".wrongstack", "worktrees"),
6463
- projectHash: hash,
6464
- projectSlug: slug,
6465
- projectGoal: path13.join(projectDir, "goal.json"),
6466
- projectSpecs: path13.join(projectDir, "specs"),
6467
- projectTaskGraphs: path13.join(projectDir, "task-graphs"),
6468
- projectSddSession: path13.join(projectDir, "sdd-session.json"),
6469
- projectPlan: path13.join(projectDir, "plan.json"),
6470
- projectAutophase: path13.join(projectDir, "autophase"),
6471
- syncConfig: path13.join(globalRoot, "sync.json")
6472
- };
6473
- }
6474
-
6475
- // src/storage/goal-store.ts
6476
6552
  var MAX_JOURNAL_ENTRIES = 500;
6477
6553
  function goalFilePath(projectRoot) {
6478
6554
  return resolveWstackPaths({ projectRoot }).projectGoal;
@@ -6500,7 +6576,7 @@ async function loadGoal(filePath, events) {
6500
6576
  store: "goal",
6501
6577
  filePath,
6502
6578
  operation: "load",
6503
- error: err instanceof Error ? err.message : String(err),
6579
+ error: toErrorMessage(err),
6504
6580
  recoverable: false
6505
6581
  });
6506
6582
  throw err;
@@ -6573,11 +6649,11 @@ async function saveGoal(filePath, goal, events) {
6573
6649
  store: "goal",
6574
6650
  filePath,
6575
6651
  operation: "save",
6576
- error: err instanceof Error ? err.message : String(err),
6652
+ error: toErrorMessage(err),
6577
6653
  recoverable: false
6578
6654
  });
6579
6655
  throw new FsError({
6580
- message: err instanceof Error ? err.message : String(err),
6656
+ message: toErrorMessage(err),
6581
6657
  code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
6582
6658
  path: filePath,
6583
6659
  cause: err
@@ -6739,7 +6815,7 @@ var DefaultPromptStore = class {
6739
6815
  if (!file.endsWith(".json")) continue;
6740
6816
  try {
6741
6817
  const raw = JSON.parse(
6742
- await fsp.readFile(path13.join(this.dir, file), "utf8")
6818
+ await fsp.readFile(path2.join(this.dir, file), "utf8")
6743
6819
  );
6744
6820
  entries.push(raw.entry);
6745
6821
  } catch {
@@ -6752,7 +6828,7 @@ var DefaultPromptStore = class {
6752
6828
  );
6753
6829
  }
6754
6830
  async get(id) {
6755
- const file = path13.join(this.dir, `${id}.json`);
6831
+ const file = path2.join(this.dir, `${id}.json`);
6756
6832
  try {
6757
6833
  const raw = JSON.parse(await fsp.readFile(file, "utf8"));
6758
6834
  return raw.entry;
@@ -6762,12 +6838,12 @@ var DefaultPromptStore = class {
6762
6838
  }
6763
6839
  async save(entry) {
6764
6840
  await ensureDir(this.dir);
6765
- const file = path13.join(this.dir, `${entry.id}.json`);
6841
+ const file = path2.join(this.dir, `${entry.id}.json`);
6766
6842
  const raw = { version: 1, entry };
6767
6843
  await atomicWrite(file, JSON.stringify(raw, null, 2));
6768
6844
  }
6769
6845
  async delete(id) {
6770
- const file = path13.join(this.dir, `${id}.json`);
6846
+ const file = path2.join(this.dir, `${id}.json`);
6771
6847
  try {
6772
6848
  await fsp.unlink(file);
6773
6849
  return true;
@@ -6801,7 +6877,7 @@ var CloudSync = class {
6801
6877
  this.paths = paths;
6802
6878
  this.getConfig = getConfig;
6803
6879
  this.setConfig = setConfig;
6804
- this.statePath = path13.join(paths.globalRoot, "sync-state.json");
6880
+ this.statePath = path2.join(paths.globalRoot, "sync-state.json");
6805
6881
  }
6806
6882
  paths;
6807
6883
  getConfig;
@@ -6908,7 +6984,7 @@ var CloudSync = class {
6908
6984
  const rel = segments.slice(2).join("/");
6909
6985
  const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
6910
6986
  const blobData = await this.getBlob(token, owner, repoName, entry.sha);
6911
- await fsp.mkdir(path13.dirname(destPath), { recursive: true });
6987
+ await fsp.mkdir(path2.dirname(destPath), { recursive: true });
6912
6988
  await fsp.writeFile(destPath, Buffer.from(blobData, "base64"));
6913
6989
  }
6914
6990
  const localRev = await this.hashLocalCategories(cfg.categories);
@@ -7015,12 +7091,12 @@ var CloudSync = class {
7015
7091
  const localPath = this.categoryToPath(cat);
7016
7092
  if (!localPath) continue;
7017
7093
  try {
7018
- const stat5 = await fsp.stat(localPath);
7019
- if (stat5.isDirectory()) {
7094
+ const stat6 = await fsp.stat(localPath);
7095
+ if (stat6.isDirectory()) {
7020
7096
  const files = await this.walkDir(localPath, localPath);
7021
7097
  for (const file of files) {
7022
7098
  const content = await fsp.readFile(file, "utf8");
7023
- const rel = path13.relative(localPath, file).replace(/\\/g, "/");
7099
+ const rel = path2.relative(localPath, file).replace(/\\/g, "/");
7024
7100
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
7025
7101
  hashes.push(content);
7026
7102
  }
@@ -7041,8 +7117,8 @@ var CloudSync = class {
7041
7117
  const localPath = this.categoryToPath(cat);
7042
7118
  if (!localPath) continue;
7043
7119
  try {
7044
- const stat5 = await fsp.stat(localPath);
7045
- if (stat5.isDirectory()) {
7120
+ const stat6 = await fsp.stat(localPath);
7121
+ if (stat6.isDirectory()) {
7046
7122
  const files = await this.walkDir(localPath, localPath);
7047
7123
  for (const file of files) {
7048
7124
  const content = await fsp.readFile(file);
@@ -7077,7 +7153,7 @@ var CloudSync = class {
7077
7153
  const results = [];
7078
7154
  const entries = await fsp.readdir(dir, { withFileTypes: true });
7079
7155
  for (const entry of entries) {
7080
- const full = path13.join(dir, entry.name);
7156
+ const full = path2.join(dir, entry.name);
7081
7157
  if (entry.isDirectory()) {
7082
7158
  results.push(...await this.walkDir(full, base));
7083
7159
  } else {
@@ -7099,9 +7175,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
7099
7175
  return localPath;
7100
7176
  }
7101
7177
  if (!rel) return localPath;
7102
- const normalizedRel = path13.normalize(rel);
7103
- const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path13.sep}`);
7104
- if (path13.isAbsolute(normalizedRel) || traversesUp) {
7178
+ const normalizedRel = path2.normalize(rel);
7179
+ const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path2.sep}`);
7180
+ if (path2.isAbsolute(normalizedRel) || traversesUp) {
7105
7181
  throw new FsError({
7106
7182
  message: `Refusing CloudSync path traversal: ${remotePath}`,
7107
7183
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -7109,10 +7185,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
7109
7185
  context: { reason: "path_traversal", normalizedRel }
7110
7186
  });
7111
7187
  }
7112
- const dest = path13.resolve(localPath, normalizedRel);
7113
- const root = path13.resolve(localPath);
7114
- const relative4 = path13.relative(root, dest);
7115
- if (relative4.startsWith("..") || path13.isAbsolute(relative4)) {
7188
+ const dest = path2.resolve(localPath, normalizedRel);
7189
+ const root = path2.resolve(localPath);
7190
+ const relative4 = path2.relative(root, dest);
7191
+ if (relative4.startsWith("..") || path2.isAbsolute(relative4)) {
7116
7192
  throw new FsError({
7117
7193
  message: `Refusing CloudSync path outside category root: ${remotePath}`,
7118
7194
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -7172,7 +7248,7 @@ function isAllowed(type, level) {
7172
7248
  return true;
7173
7249
  }
7174
7250
  function createSessionEventBridge(writer, level = "standard", options = {}) {
7175
- const normalizedLevel = level ?? "standard";
7251
+ let currentLevel = level ?? "standard";
7176
7252
  const resolveWriter = typeof writer === "function" ? writer : () => writer;
7177
7253
  const progressCounters = /* @__PURE__ */ new Map();
7178
7254
  const toolProgressConfig = options.sampling?.toolProgress ?? {};
@@ -7193,14 +7269,19 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
7193
7269
  return true;
7194
7270
  }
7195
7271
  return {
7196
- level: normalizedLevel,
7272
+ get level() {
7273
+ return currentLevel;
7274
+ },
7275
+ setAuditLevel(next) {
7276
+ currentLevel = next ?? "standard";
7277
+ },
7197
7278
  allows(type) {
7198
- return isAllowed(type, normalizedLevel);
7279
+ return isAllowed(type, currentLevel);
7199
7280
  },
7200
7281
  async append(event) {
7201
7282
  const target = resolveWriter();
7202
7283
  if (!target) return;
7203
- if (!isAllowed(event.type, normalizedLevel)) return;
7284
+ if (!isAllowed(event.type, currentLevel)) return;
7204
7285
  if (!shouldSample(event)) return;
7205
7286
  try {
7206
7287
  await target.append(event);
@@ -7211,7 +7292,7 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
7211
7292
  const target = resolveWriter();
7212
7293
  if (!target || events.length === 0) return;
7213
7294
  const allowed = events.filter(
7214
- (e) => isAllowed(e.type, normalizedLevel) && shouldSample(e)
7295
+ (e) => isAllowed(e.type, currentLevel) && shouldSample(e)
7215
7296
  );
7216
7297
  if (allowed.length === 0) return;
7217
7298
  try {