@wrongstack/core 0.291.1 → 0.292.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 (127) hide show
  1. package/dist/coordination/director-tools.d.ts.map +1 -1
  2. package/dist/coordination/index.js +113 -113
  3. package/dist/coordination/index.js.map +3 -3
  4. package/dist/core/agent-loop.d.ts.map +1 -1
  5. package/dist/core/agent-response.d.ts.map +1 -1
  6. package/dist/core/context.d.ts +10 -0
  7. package/dist/core/context.d.ts.map +1 -1
  8. package/dist/core/system-prompt-builder.d.ts +20 -1
  9. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  10. package/dist/defaults/index.d.ts +1 -0
  11. package/dist/defaults/index.d.ts.map +1 -1
  12. package/dist/defaults/index.js +3132 -2440
  13. package/dist/defaults/index.js.map +4 -4
  14. package/dist/execution/auto-compaction-middleware.d.ts +34 -2
  15. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  16. package/dist/execution/compaction-core.d.ts +59 -0
  17. package/dist/execution/compaction-core.d.ts.map +1 -1
  18. package/dist/execution/compactor.d.ts.map +1 -1
  19. package/dist/execution/error-handler.d.ts +7 -0
  20. package/dist/execution/error-handler.d.ts.map +1 -1
  21. package/dist/execution/index.d.ts +1 -0
  22. package/dist/execution/index.d.ts.map +1 -1
  23. package/dist/execution/index.js +4677 -3924
  24. package/dist/execution/index.js.map +4 -4
  25. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  26. package/dist/execution/model-runtime.d.ts.map +1 -1
  27. package/dist/execution/selective-compactor.d.ts +10 -0
  28. package/dist/execution/selective-compactor.d.ts.map +1 -1
  29. package/dist/execution/subagent-compaction.d.ts +24 -0
  30. package/dist/execution/subagent-compaction.d.ts.map +1 -0
  31. package/dist/hq/alerts.d.ts +5 -0
  32. package/dist/hq/alerts.d.ts.map +1 -1
  33. package/dist/hq/auth-audit.d.ts +115 -0
  34. package/dist/hq/auth-audit.d.ts.map +1 -0
  35. package/dist/hq/auth-store.d.ts +98 -2
  36. package/dist/hq/auth-store.d.ts.map +1 -1
  37. package/dist/hq/brain-bridge.d.ts +2 -8
  38. package/dist/hq/brain-bridge.d.ts.map +1 -1
  39. package/dist/hq/bridge-context.d.ts +74 -0
  40. package/dist/hq/bridge-context.d.ts.map +1 -0
  41. package/dist/hq/cost-bridge.d.ts +4 -10
  42. package/dist/hq/cost-bridge.d.ts.map +1 -1
  43. package/dist/hq/fleet-bridge.d.ts +2 -8
  44. package/dist/hq/fleet-bridge.d.ts.map +1 -1
  45. package/dist/hq/index.d.ts +2 -0
  46. package/dist/hq/index.d.ts.map +1 -1
  47. package/dist/hq/index.js +358 -222
  48. package/dist/hq/index.js.map +4 -4
  49. package/dist/hq/protocol/session.d.ts +13 -0
  50. package/dist/hq/protocol/session.d.ts.map +1 -1
  51. package/dist/hq/tool-bridge.d.ts +2 -8
  52. package/dist/hq/tool-bridge.d.ts.map +1 -1
  53. package/dist/hq/worktree-bridge.d.ts +2 -8
  54. package/dist/hq/worktree-bridge.d.ts.map +1 -1
  55. package/dist/index.d.ts +4 -2
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +6054 -4741
  58. package/dist/index.js.map +4 -4
  59. package/dist/infrastructure/index.js +32 -15
  60. package/dist/infrastructure/index.js.map +2 -2
  61. package/dist/infrastructure/provider-cache-ledger.d.ts +48 -0
  62. package/dist/infrastructure/provider-cache-ledger.d.ts.map +1 -0
  63. package/dist/infrastructure/token-counter.d.ts +4 -2
  64. package/dist/infrastructure/token-counter.d.ts.map +1 -1
  65. package/dist/kernel/events/brain-events.d.ts +2 -0
  66. package/dist/kernel/events/brain-events.d.ts.map +1 -1
  67. package/dist/kernel/events/memory-events.d.ts +102 -0
  68. package/dist/kernel/events/memory-events.d.ts.map +1 -1
  69. package/dist/kernel/events/session-events.d.ts +35 -0
  70. package/dist/kernel/events/session-events.d.ts.map +1 -1
  71. package/dist/models/index.js +161 -78
  72. package/dist/models/index.js.map +3 -3
  73. package/dist/models/llm-selector.d.ts.map +1 -1
  74. package/dist/notifications/index.d.ts +40 -0
  75. package/dist/notifications/index.d.ts.map +1 -0
  76. package/dist/notifications/notifier-impl.d.ts +34 -0
  77. package/dist/notifications/notifier-impl.d.ts.map +1 -0
  78. package/dist/notifications/notifier.d.ts +95 -0
  79. package/dist/notifications/notifier.d.ts.map +1 -0
  80. package/dist/notifications/types.d.ts +111 -0
  81. package/dist/notifications/types.d.ts.map +1 -0
  82. package/dist/plugin/api.d.ts +10 -1
  83. package/dist/plugin/api.d.ts.map +1 -1
  84. package/dist/registry/provider-registry.d.ts +1 -1
  85. package/dist/registry/provider-registry.d.ts.map +1 -1
  86. package/dist/security/index.js +15 -15
  87. package/dist/security/index.js.map +3 -3
  88. package/dist/skills/index.js +80 -80
  89. package/dist/skills/index.js.map +3 -3
  90. package/dist/storage/config-loader.d.ts +3 -5
  91. package/dist/storage/config-loader.d.ts.map +1 -1
  92. package/dist/storage/index.js +325 -259
  93. package/dist/storage/index.js.map +4 -4
  94. package/dist/storage/memory-consolidator.d.ts +14 -2
  95. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  96. package/dist/storage/queue-store.d.ts +2 -0
  97. package/dist/storage/queue-store.d.ts.map +1 -1
  98. package/dist/tools/index.js +6 -6
  99. package/dist/tools/index.js.map +3 -3
  100. package/dist/tools/mcp-control.d.ts +1 -1
  101. package/dist/types/config.d.ts +64 -20
  102. package/dist/types/config.d.ts.map +1 -1
  103. package/dist/types/index.js +60 -34
  104. package/dist/types/index.js.map +3 -3
  105. package/dist/types/plugin.d.ts +11 -0
  106. package/dist/types/plugin.d.ts.map +1 -1
  107. package/dist/types/provider.d.ts +31 -0
  108. package/dist/types/provider.d.ts.map +1 -1
  109. package/dist/types/token-counter.d.ts +8 -1
  110. package/dist/types/token-counter.d.ts.map +1 -1
  111. package/dist/utils/cache-key.d.ts +19 -0
  112. package/dist/utils/cache-key.d.ts.map +1 -0
  113. package/dist/utils/compaction-preview.d.ts +8 -0
  114. package/dist/utils/compaction-preview.d.ts.map +1 -0
  115. package/dist/utils/context-breakdown.d.ts +59 -0
  116. package/dist/utils/context-breakdown.d.ts.map +1 -0
  117. package/dist/utils/context-evidence.d.ts +22 -0
  118. package/dist/utils/context-evidence.d.ts.map +1 -1
  119. package/dist/utils/index.d.ts +2 -0
  120. package/dist/utils/index.d.ts.map +1 -1
  121. package/dist/utils/index.js +1730 -1543
  122. package/dist/utils/index.js.map +4 -4
  123. package/dist/utils/token-estimate.d.ts +27 -0
  124. package/dist/utils/token-estimate.d.ts.map +1 -1
  125. package/instructions/llm/memory-consolidator.md +19 -3
  126. package/instructions/system.md +10 -2
  127. package/package.json +2 -2
@@ -217,65 +217,6 @@ async function backupConfigFile(filePath, paths) {
217
217
  }
218
218
  }
219
219
 
220
- // src/utils/deep-merge.ts
221
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
222
- "__proto__",
223
- "constructor",
224
- "prototype",
225
- "__defineGetter__",
226
- "__defineSetter__",
227
- "__lookupGetter__",
228
- "__lookupSetter__"
229
- ]);
230
- function isPrimitiveArray(a) {
231
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
232
- }
233
- function deepMerge(base, patch, options = {}) {
234
- const {
235
- conflictResolution = "prefer-patch",
236
- arrayMode = "replace",
237
- protectProto = true,
238
- onNonPrimitiveArrayReplace
239
- } = options;
240
- if (typeof base !== "object" || base === null) {
241
- return conflictResolution === "prefer-patch" ? patch : base;
242
- }
243
- if (typeof patch !== "object" || patch === null) {
244
- return conflictResolution === "prefer-patch" ? patch : base;
245
- }
246
- if (Array.isArray(base) && Array.isArray(patch)) {
247
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
248
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
249
- }
250
- return conflictResolution === "prefer-patch" ? patch : base;
251
- }
252
- if (Array.isArray(base) || Array.isArray(patch)) {
253
- return conflictResolution === "prefer-patch" ? patch : base;
254
- }
255
- const baseObj = base;
256
- const patchObj = patch;
257
- const out = { ...baseObj };
258
- for (const [k, v] of Object.entries(patchObj)) {
259
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
260
- const existing = out[k];
261
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
262
- out[k] = deepMerge(existing, v, options);
263
- } else if (Array.isArray(v) && Array.isArray(existing)) {
264
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
265
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
266
- }
267
- out[k] = deepMerge(existing, v, options);
268
- } else if (v !== void 0) {
269
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
270
- const existingLen = Array.isArray(existing) ? existing.length : 0;
271
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
272
- }
273
- out[k] = v;
274
- }
275
- }
276
- return out;
277
- }
278
-
279
220
  // src/utils/error.ts
280
221
  function toErrorMessage(err) {
281
222
  return err instanceof Error ? err.message : String(err);
@@ -397,6 +338,68 @@ function isEmptyMessage(msg) {
397
338
  return !hasMeaningfulContent(msg.content);
398
339
  }
399
340
 
341
+ // src/types/config.ts
342
+ var DEFAULT_TUI_THINKING_WORD = "thinking";
343
+
344
+ // src/utils/deep-merge.ts
345
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
346
+ "__proto__",
347
+ "constructor",
348
+ "prototype",
349
+ "__defineGetter__",
350
+ "__defineSetter__",
351
+ "__lookupGetter__",
352
+ "__lookupSetter__"
353
+ ]);
354
+ function isPrimitiveArray(a) {
355
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
356
+ }
357
+ function deepMerge(base, patch, options = {}) {
358
+ const {
359
+ conflictResolution = "prefer-patch",
360
+ arrayMode = "replace",
361
+ protectProto = true,
362
+ onNonPrimitiveArrayReplace
363
+ } = options;
364
+ if (typeof base !== "object" || base === null) {
365
+ return conflictResolution === "prefer-patch" ? patch : base;
366
+ }
367
+ if (typeof patch !== "object" || patch === null) {
368
+ return conflictResolution === "prefer-patch" ? patch : base;
369
+ }
370
+ if (Array.isArray(base) && Array.isArray(patch)) {
371
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
372
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
373
+ }
374
+ return conflictResolution === "prefer-patch" ? patch : base;
375
+ }
376
+ if (Array.isArray(base) || Array.isArray(patch)) {
377
+ return conflictResolution === "prefer-patch" ? patch : base;
378
+ }
379
+ const baseObj = base;
380
+ const patchObj = patch;
381
+ const out = { ...baseObj };
382
+ for (const [k, v] of Object.entries(patchObj)) {
383
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
384
+ const existing = out[k];
385
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
386
+ out[k] = deepMerge(existing, v, options);
387
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
388
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
389
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
390
+ }
391
+ out[k] = deepMerge(existing, v, options);
392
+ } else if (v !== void 0) {
393
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
394
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
395
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
396
+ }
397
+ out[k] = v;
398
+ }
399
+ }
400
+ return out;
401
+ }
402
+
400
403
  // src/utils/regex-guard.ts
401
404
  var MAX_PATTERN_LEN = 512;
402
405
  var DANGEROUS_PATTERNS = [
@@ -1762,8 +1765,8 @@ function sha256(content) {
1762
1765
  return createHash2("sha256").update(content).digest("hex");
1763
1766
  }
1764
1767
  function isInside(root, target) {
1765
- const relative7 = path6.relative(root, target);
1766
- return relative7 === "" || !relative7.startsWith("..") && !path6.isAbsolute(relative7);
1768
+ const relative8 = path6.relative(root, target);
1769
+ return relative8 === "" || !relative8.startsWith("..") && !path6.isAbsolute(relative8);
1767
1770
  }
1768
1771
  function normalizeRelative(input) {
1769
1772
  if (!input || path6.isAbsolute(input)) return null;
@@ -1775,8 +1778,8 @@ function normalizeRelative(input) {
1775
1778
  function parseNulPaths(output) {
1776
1779
  return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
1777
1780
  }
1778
- function isWrongStackWorktreePath(relative7) {
1779
- return relative7 === ".wrongstack/worktrees" || relative7.startsWith(".wrongstack/worktrees/");
1781
+ function isWrongStackWorktreePath(relative8) {
1782
+ return relative8 === ".wrongstack/worktrees" || relative8.startsWith(".wrongstack/worktrees/");
1780
1783
  }
1781
1784
  var SessionCheckpointCas = class {
1782
1785
  rootDir;
@@ -1802,16 +1805,16 @@ var SessionCheckpointCas = class {
1802
1805
  }
1803
1806
  const relativePaths = [
1804
1807
  .../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
1805
- ].filter((relative7) => !isWrongStackWorktreePath(relative7)).sort();
1808
+ ].filter((relative8) => !isWrongStackWorktreePath(relative8)).sort();
1806
1809
  const unresolved = [];
1807
1810
  let scheduledBytes = 0;
1808
1811
  const captured = await mapWithConcurrency(
1809
1812
  relativePaths,
1810
1813
  CAPTURE_CONCURRENCY,
1811
- async (relative7) => {
1812
- const absolute = path6.resolve(this.projectRoot, ...relative7.split("/"));
1814
+ async (relative8) => {
1815
+ const absolute = path6.resolve(this.projectRoot, ...relative8.split("/"));
1813
1816
  if (!isInside(this.projectRoot, absolute)) {
1814
- unresolved.push({ path: relative7, reason: "path escapes project root" });
1817
+ unresolved.push({ path: relative8, reason: "path escapes project root" });
1815
1818
  return null;
1816
1819
  }
1817
1820
  try {
@@ -1821,27 +1824,27 @@ var SessionCheckpointCas = class {
1821
1824
  const resolvedLink = path6.resolve(path6.dirname(absolute), linkTarget);
1822
1825
  if (path6.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
1823
1826
  unresolved.push({
1824
- path: relative7,
1827
+ path: relative8,
1825
1828
  reason: "symlink target escapes project root"
1826
1829
  });
1827
1830
  return null;
1828
1831
  }
1829
- return { path: relative7, state: "symlink", linkTarget };
1832
+ return { path: relative8, state: "symlink", linkTarget };
1830
1833
  }
1831
1834
  if (!stat13.isFile()) {
1832
- unresolved.push({ path: relative7, reason: "changed path is not a regular file" });
1835
+ unresolved.push({ path: relative8, reason: "changed path is not a regular file" });
1833
1836
  return null;
1834
1837
  }
1835
1838
  if (stat13.size > MAX_BLOB_BYTES) {
1836
1839
  unresolved.push({
1837
- path: relative7,
1840
+ path: relative8,
1838
1841
  reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
1839
1842
  });
1840
1843
  return null;
1841
1844
  }
1842
1845
  if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
1843
1846
  unresolved.push({
1844
- path: relative7,
1847
+ path: relative8,
1845
1848
  reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
1846
1849
  });
1847
1850
  return null;
@@ -1850,12 +1853,12 @@ var SessionCheckpointCas = class {
1850
1853
  const content = await fsp2.readFile(absolute);
1851
1854
  const blobHash = sha256(content);
1852
1855
  await this.putBlob(blobHash, content);
1853
- return { path: relative7, state: "file", blobHash, mode: stat13.mode & 511 };
1856
+ return { path: relative8, state: "file", blobHash, mode: stat13.mode & 511 };
1854
1857
  } catch (err) {
1855
1858
  if (err.code === "ENOENT") {
1856
- return { path: relative7, state: "absent" };
1859
+ return { path: relative8, state: "absent" };
1857
1860
  }
1858
- unresolved.push({ path: relative7, reason: toErrorMessage(err) });
1861
+ unresolved.push({ path: relative8, reason: toErrorMessage(err) });
1859
1862
  return null;
1860
1863
  }
1861
1864
  }
@@ -2045,8 +2048,8 @@ var SessionCheckpointCas = class {
2045
2048
  }
2046
2049
  return parsed;
2047
2050
  }
2048
- async safeOutputPath(target, realTarget, relative7) {
2049
- const normalized = normalizeRelative(relative7);
2051
+ async safeOutputPath(target, realTarget, relative8) {
2052
+ const normalized = normalizeRelative(relative8);
2050
2053
  if (!normalized) throw new Error("invalid relative path");
2051
2054
  const output = path6.resolve(target, ...normalized.split("/"));
2052
2055
  if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
@@ -2132,8 +2135,8 @@ var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
2132
2135
  var VALIDATION_CONCURRENCY = 8;
2133
2136
  var NOTICE_PATH_LIMIT = 20;
2134
2137
  function isInside2(root, target) {
2135
- const relative7 = path7.relative(root, target);
2136
- return relative7 === "" || !relative7.startsWith("..") && !path7.isAbsolute(relative7);
2138
+ const relative8 = path7.relative(root, target);
2139
+ return relative8 === "" || !relative8.startsWith("..") && !path7.isAbsolute(relative8);
2137
2140
  }
2138
2141
  function errno(err) {
2139
2142
  return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
@@ -2220,8 +2223,8 @@ function formatResumeValidationNotice(validation, projectRoot) {
2220
2223
  if (validation.staleFiles.length === 0) return null;
2221
2224
  const root = path7.resolve(projectRoot);
2222
2225
  const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
2223
- const relative7 = path7.relative(root, entry.path);
2224
- const display = isInside2(root, entry.path) ? relative7 || "." : entry.path;
2226
+ const relative8 = path7.relative(root, entry.path);
2227
+ const display = isInside2(root, entry.path) ? relative8 || "." : entry.path;
2225
2228
  return `- ${JSON.stringify(display)} [${entry.status}]`;
2226
2229
  });
2227
2230
  const omitted = validation.staleFiles.length - shown.length;
@@ -4685,6 +4688,9 @@ function simpleHash(s) {
4685
4688
  return Math.abs(h).toString(36);
4686
4689
  }
4687
4690
 
4691
+ // src/storage/memory-consolidator.ts
4692
+ import * as path13 from "node:path";
4693
+
4688
4694
  // src/utils/instruction-file.ts
4689
4695
  import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
4690
4696
  import * as path12 from "node:path";
@@ -4747,6 +4753,12 @@ function toSuperMemoryKind(type) {
4747
4753
  case "convention":
4748
4754
  case "preference":
4749
4755
  case "anti_pattern":
4756
+ case "warning":
4757
+ case "workflow":
4758
+ case "bug_root_cause":
4759
+ case "file_note":
4760
+ case "symbol_note":
4761
+ case "command_note":
4750
4762
  return type;
4751
4763
  case "reference":
4752
4764
  return "file_note";
@@ -4768,7 +4780,7 @@ function importanceFromPriority(priority) {
4768
4780
  return 0.6;
4769
4781
  }
4770
4782
  }
4771
- function buildConsolidationPrompt(finalText, iterations, existingEntries) {
4783
+ function buildConsolidationPrompt(finalText, iterations, existingEntries, evidence) {
4772
4784
  const existingBlock = existingEntries.length > 0 ? `
4773
4785
 
4774
4786
  Existing memory entries:
@@ -4776,9 +4788,82 @@ ${existingEntries.map((e) => `- [${e.ts.slice(0, 10)}] ${e.text}`).join("\n")}`
4776
4788
  return renderInstructionTemplate(readBundledInstructionText("llm/memory-consolidator.md"), {
4777
4789
  iterations: String(iterations),
4778
4790
  summary: finalText.slice(0, 3e3),
4791
+ evidence,
4779
4792
  existingEntries: existingBlock
4780
4793
  });
4781
4794
  }
4795
+ function relativeEvidencePath(projectRoot, filePath) {
4796
+ const relative8 = path13.relative(projectRoot, filePath);
4797
+ return relative8 && !relative8.startsWith("..") && !path13.isAbsolute(relative8) ? relative8.replaceAll("\\", "/") : filePath.replaceAll("\\", "/");
4798
+ }
4799
+ function safeCommandEvidence(value) {
4800
+ if (typeof value !== "string") return void 0;
4801
+ const command = value.trim().replace(/\s+/g, " ").slice(0, 300);
4802
+ if (!command) return void 0;
4803
+ if (/(?:password|passwd|secret|token|api[_-]?key|authorization|bearer)\s*[:=]/i.test(command)) {
4804
+ return "[redacted sensitive command]";
4805
+ }
4806
+ return command;
4807
+ }
4808
+ function buildSessionEvidence(ctx) {
4809
+ const projectRoot = ctx.projectRoot ?? ctx.cwd ?? "";
4810
+ const read = [...ctx.readFiles ?? []].slice(-20).map((file) => relativeEvidencePath(projectRoot, file));
4811
+ const written = [...ctx.writtenFiles ?? []].slice(-20).map((file) => relativeEvidencePath(projectRoot, file));
4812
+ const commands = (ctx.sideEffects ?? []).slice(-10).map((effect) => ({
4813
+ tool: effect.toolName,
4814
+ risk: effect.risk,
4815
+ command: safeCommandEvidence(effect.input["command"]),
4816
+ outcome: effect.outcome?.slice(0, 160)
4817
+ }));
4818
+ const completedTodos = (ctx.todos ?? []).filter((todo) => todo.status === "completed").slice(-10).map((todo) => todo.content.slice(0, 240));
4819
+ return JSON.stringify(
4820
+ {
4821
+ projectRoot: projectRoot.replaceAll("\\", "/"),
4822
+ readFiles: read,
4823
+ writtenFiles: written,
4824
+ commands,
4825
+ completedTodos,
4826
+ kanban: ctx.currentKanbanTaskId ? { boardId: ctx.currentKanbanBoardId, taskId: ctx.currentKanbanTaskId } : void 0
4827
+ },
4828
+ null,
4829
+ 2
4830
+ ).slice(0, 6e3);
4831
+ }
4832
+ function normalizeConsolidatorAnchors(value) {
4833
+ if (!Array.isArray(value)) return void 0;
4834
+ const allowed = /* @__PURE__ */ new Set([
4835
+ "file",
4836
+ "directory",
4837
+ "symbol",
4838
+ "package",
4839
+ "command",
4840
+ "test",
4841
+ "git"
4842
+ ]);
4843
+ const anchors = [];
4844
+ for (const raw of value.slice(0, 5)) {
4845
+ if (!raw || typeof raw !== "object") continue;
4846
+ const candidate = raw;
4847
+ if (!allowed.has(candidate["type"])) continue;
4848
+ const anchor = {
4849
+ type: candidate["type"]
4850
+ };
4851
+ if (typeof candidate["path"] === "string" && candidate["path"].trim()) {
4852
+ anchor.path = candidate["path"].trim().slice(0, 500);
4853
+ }
4854
+ if (typeof candidate["symbol"] === "string" && candidate["symbol"].trim()) {
4855
+ anchor.symbol = candidate["symbol"].trim().slice(0, 300);
4856
+ }
4857
+ if (typeof candidate["command"] === "string" && candidate["command"].trim()) {
4858
+ anchor.command = safeCommandEvidence(candidate["command"]);
4859
+ }
4860
+ anchors.push(anchor);
4861
+ }
4862
+ return anchors.length > 0 ? anchors : void 0;
4863
+ }
4864
+ function normalizeConfidence(value) {
4865
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0.5, Math.min(1, value)) : 0.78;
4866
+ }
4782
4867
  var SessionMemoryConsolidator = class {
4783
4868
  name = "session-memory-consolidator";
4784
4869
  owner = "core";
@@ -4794,11 +4879,11 @@ var SessionMemoryConsolidator = class {
4794
4879
  this.superMemory = opts.superMemory;
4795
4880
  this.provider = opts.provider;
4796
4881
  this.model = opts.model;
4797
- this.minIterations = opts.minIterations ?? 2;
4882
+ this.minIterations = opts.minIterations ?? 1;
4798
4883
  this.maxExistingEntries = opts.maxExistingEntries ?? 15;
4799
4884
  this.oneShotOrchestrator = opts.oneShotOrchestrator;
4800
4885
  }
4801
- afterRun = (ctx, result) => {
4886
+ afterRun = async (ctx, result) => {
4802
4887
  if (result.status !== "done") return;
4803
4888
  if (!result.finalText || result.finalText.trim().length < 20) return;
4804
4889
  if (result.iterations < this.minIterations) return;
@@ -4808,87 +4893,85 @@ var SessionMemoryConsolidator = class {
4808
4893
  const _iterations = result.iterations;
4809
4894
  const _model = this.model ?? ctx.model;
4810
4895
  const _sessionId = ctx.session.id;
4811
- void (async () => {
4812
- try {
4813
- let existingEntries;
4814
- if (this.superMemory) {
4815
- const records = this.superMemory.listSuper ? await this.superMemory.listSuper(["active"]) : await this.superMemory.searchSuper?.("", {
4816
- limit: this.maxExistingEntries,
4817
- scope: "project",
4818
- includeStatuses: ["active"]
4819
- }) ?? [];
4820
- existingEntries = toPromptEntries(records, this.maxExistingEntries);
4821
- } else {
4822
- existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
4823
- }
4824
- const prompt = buildConsolidationPrompt(
4825
- _finalText,
4826
- _iterations,
4827
- existingEntries
4896
+ const _evidence = buildSessionEvidence(ctx);
4897
+ try {
4898
+ let existingEntries;
4899
+ if (this.superMemory) {
4900
+ const records = this.superMemory.listSuper ? await this.superMemory.listSuper(["active"]) : await this.superMemory.searchSuper?.("", {
4901
+ limit: this.maxExistingEntries,
4902
+ scope: "project",
4903
+ includeStatuses: ["active"]
4904
+ }) ?? [];
4905
+ existingEntries = toPromptEntries(records, this.maxExistingEntries);
4906
+ } else {
4907
+ existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
4908
+ }
4909
+ const prompt = buildConsolidationPrompt(_finalText, _iterations, existingEntries, _evidence);
4910
+ let text;
4911
+ if (this.oneShotOrchestrator) {
4912
+ const oneShotResult = await this.oneShotOrchestrator.call({
4913
+ system: prompt,
4914
+ userPrompt: "Review the session and return memory operations as JSON.",
4915
+ model: _model ?? "deepseek-chat",
4916
+ maxTokens: 500,
4917
+ timeoutMs: 15e3
4918
+ });
4919
+ text = oneShotResult.text;
4920
+ } else {
4921
+ const signal = AbortSignal.timeout(15e3);
4922
+ const response = await provider.complete(
4923
+ {
4924
+ model: _model,
4925
+ system: [{ type: "text", text: prompt }],
4926
+ messages: [
4927
+ { role: "user", content: "Review the session and return memory operations as JSON." }
4928
+ ],
4929
+ maxTokens: 500
4930
+ },
4931
+ { signal }
4828
4932
  );
4829
- let text;
4830
- if (this.oneShotOrchestrator) {
4831
- const oneShotResult = await this.oneShotOrchestrator.call({
4832
- system: prompt,
4833
- userPrompt: "Review the session and return memory operations as JSON.",
4834
- model: _model ?? "deepseek-chat",
4835
- maxTokens: 500,
4836
- timeoutMs: 15e3
4837
- });
4838
- text = oneShotResult.text;
4839
- } else {
4840
- const signal = AbortSignal.timeout(15e3);
4841
- const response = await provider.complete(
4842
- {
4843
- model: _model,
4844
- system: [{ type: "text", text: prompt }],
4845
- messages: [
4846
- { role: "user", content: "Review the session and return memory operations as JSON." }
4847
- ],
4848
- maxTokens: 500
4849
- },
4850
- { signal }
4851
- );
4852
- text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
4853
- }
4854
- if (!text) return;
4855
- const jsonMatch = text.match(/\{[\s\S]*\}/);
4856
- if (!jsonMatch) return;
4857
- const parsed = JSON.parse(jsonMatch[0]);
4858
- if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
4859
- let added = 0;
4860
- for (const op of parsed.operations) {
4861
- try {
4862
- if (!op || typeof op !== "object" || op.action !== "add") continue;
4863
- if (typeof op.text !== "string" || !op.text.trim()) continue;
4864
- const memoryText = op.text.trim();
4865
- if (this.superMemory) {
4866
- await this.superMemory.rememberSuper({
4867
- text: memoryText,
4868
- scope: "project",
4869
- kind: toSuperMemoryKind(op.type),
4870
- tags: op.tags,
4871
- importance: importanceFromPriority(op.priority),
4872
- sources: [{ type: "session", sessionId: _sessionId }]
4873
- });
4874
- } else {
4875
- await this.memoryStore.remember(memoryText, void 0, {
4876
- type: op.type,
4877
- tags: op.tags,
4878
- priority: op.priority
4879
- });
4880
- }
4881
- added++;
4882
- } catch {
4933
+ text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
4934
+ }
4935
+ if (!text) return;
4936
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
4937
+ if (!jsonMatch) return;
4938
+ const parsed = JSON.parse(jsonMatch[0]);
4939
+ if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
4940
+ let added = 0;
4941
+ for (const op of parsed.operations.slice(0, 5)) {
4942
+ try {
4943
+ if (!op || typeof op !== "object" || op.action !== "add") continue;
4944
+ if (typeof op.text !== "string" || !op.text.trim()) continue;
4945
+ const memoryText = op.text.trim();
4946
+ if (this.superMemory) {
4947
+ await this.superMemory.rememberSuper({
4948
+ text: memoryText,
4949
+ scope: "project",
4950
+ kind: toSuperMemoryKind(op.type),
4951
+ tags: op.tags,
4952
+ importance: importanceFromPriority(op.priority),
4953
+ confidence: normalizeConfidence(op.confidence),
4954
+ persistence: "long_lived",
4955
+ anchors: normalizeConsolidatorAnchors(op.anchors),
4956
+ sources: [{ type: "session", sessionId: _sessionId }]
4957
+ });
4958
+ } else {
4959
+ await this.memoryStore.remember(memoryText, void 0, {
4960
+ type: op.type,
4961
+ tags: op.tags,
4962
+ priority: op.priority
4963
+ });
4883
4964
  }
4965
+ added++;
4966
+ } catch {
4884
4967
  }
4885
- if (added > 0) {
4886
- process.stderr.write(`[memory] Session consolidation: ${added} added
4968
+ }
4969
+ if (added > 0) {
4970
+ process.stderr.write(`[memory] Session consolidation: ${added} added
4887
4971
  `);
4888
- }
4889
- } catch {
4890
4972
  }
4891
- })();
4973
+ } catch {
4974
+ }
4892
4975
  };
4893
4976
  };
4894
4977
 
@@ -4966,7 +5049,7 @@ function deepFreeze(obj) {
4966
5049
  // src/storage/provider-config-watcher.ts
4967
5050
  import * as syncFs from "node:fs";
4968
5051
  import * as fs6 from "node:fs/promises";
4969
- import * as path13 from "node:path";
5052
+ import * as path14 from "node:path";
4970
5053
 
4971
5054
  // src/security/config-secrets.ts
4972
5055
  function decryptConfigSecrets(cfg, vault, opts) {
@@ -5064,7 +5147,7 @@ function serializeSnapshot(s) {
5064
5147
  function watchProviderConfig(configPath, vault, onChange, opts = {}) {
5065
5148
  const debounceMs = opts.debounceMs ?? 200;
5066
5149
  const warn = opts.warn;
5067
- const base = path13.basename(configPath);
5150
+ const base = path14.basename(configPath);
5068
5151
  let timer;
5069
5152
  let closed = false;
5070
5153
  let lastSerialized;
@@ -5075,7 +5158,7 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
5075
5158
  });
5076
5159
  let watcher;
5077
5160
  try {
5078
- watcher = syncFs.watch(path13.dirname(configPath), { recursive: false });
5161
+ watcher = syncFs.watch(path14.dirname(configPath), { recursive: false });
5079
5162
  } catch (err) {
5080
5163
  warn?.(`Provider config watcher could not start: ${err.message}`);
5081
5164
  return { close: () => {
@@ -5119,10 +5202,7 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
5119
5202
 
5120
5203
  // src/storage/config-loader.ts
5121
5204
  import * as fs7 from "node:fs/promises";
5122
- import * as path14 from "node:path";
5123
-
5124
- // src/types/config.ts
5125
- var DEFAULT_TUI_THINKING_WORD = "thinking";
5205
+ import * as path15 from "node:path";
5126
5206
 
5127
5207
  // src/types/context-window.ts
5128
5208
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "frugal";
@@ -5256,7 +5336,12 @@ var BEHAVIOR_DEFAULTS = {
5256
5336
  modelsRegistry: true,
5257
5337
  skills: true,
5258
5338
  prompts: true,
5259
- tokenSavingMode: "off",
5339
+ // 'auto' → resolveTokenSavingTier picks a concrete tier from the model's
5340
+ // context window ONCE per session (cache-safe): lean prompt on small
5341
+ // windows (<32k medium, <96k light) where the fixed identity+tool prose is
5342
+ // a big fraction; full prompt (off) on 96k+ so nothing changes for the
5343
+ // common large-window case. Explicit tiers are respected verbatim.
5344
+ tokenSavingMode: "auto",
5260
5345
  allowOutsideProjectRoot: true
5261
5346
  },
5262
5347
  superMemory: {
@@ -5266,10 +5351,14 @@ var BEHAVIOR_DEFAULTS = {
5266
5351
  directory: ".wrongstack/memories"
5267
5352
  },
5268
5353
  inject: {
5269
- turnContext: true,
5354
+ // Keep the ordinary turn prompt clean. Project memory is surfaced
5355
+ // on-demand beside relevant tool results, where the path/query that
5356
+ // caused retrieval is known and the hint can be independently capped.
5357
+ turnContext: false,
5270
5358
  toolResults: true,
5271
- maxHintsPerTool: 4,
5272
- maxCharsPerTool: 1200,
5359
+ taskAware: true,
5360
+ maxHintsPerTool: 8,
5361
+ maxCharsPerTool: 2800,
5273
5362
  maxTurnMemories: 8,
5274
5363
  maxCharsPerTurn: 2400,
5275
5364
  minScore: 0.65,
@@ -5635,7 +5724,7 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
5635
5724
  event: "config.in_project_unsafe_fields_ignored",
5636
5725
  path: sourcePath,
5637
5726
  ignoredKeys: stripped,
5638
- message: `Ignored ${stripped.length} field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. Only a small allow-list of benign preferences (model, context, tools limits, features, \u2026) may be set by <project>/.wrongstack/config.json. Everything else must live in your personal ~/.wrongstack/config.json.`,
5727
+ message: `Ignored ${stripped.length} field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. Only a small allow-list of benign preferences (model, context, tools limits, features, \u2026) may be set by <project>/.wrongstack/config.json. Everything else must live in your active profile config.`,
5639
5728
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5640
5729
  })
5641
5730
  );
@@ -5643,8 +5732,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
5643
5732
  return out;
5644
5733
  }
5645
5734
  function samePath(a, b) {
5646
- let ra = path14.resolve(a);
5647
- let rb = path14.resolve(b);
5735
+ let ra = path15.resolve(a);
5736
+ let rb = path15.resolve(b);
5648
5737
  if (process.platform === "win32" || process.platform === "darwin") {
5649
5738
  ra = ra.toLowerCase();
5650
5739
  rb = rb.toLowerCase();
@@ -5705,11 +5794,18 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
5705
5794
  this.readJson(this.paths.projectLocalConfig),
5706
5795
  inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
5707
5796
  ]);
5708
- cfg = deepMerge2(cfg, bootstrap);
5797
+ const bootstrapMetadata = {
5798
+ ...typeof bootstrap.version === "number" ? { version: bootstrap.version } : {},
5799
+ ...typeof bootstrap.activeProfile === "string" ? { activeProfile: bootstrap.activeProfile } : {}
5800
+ };
5801
+ cfg = deepMerge2(cfg, bootstrapMetadata);
5709
5802
  const profileName = bootstrap.activeProfile ?? "default";
5710
5803
  const profileCfg = await this.readJson(this.paths.profileConfig(profileName));
5711
5804
  if (Object.keys(profileCfg).length > 0) {
5712
- cfg = deepMerge2(cfg, profileCfg);
5805
+ const profileSettings = { ...profileCfg };
5806
+ delete profileSettings["version"];
5807
+ delete profileSettings["activeProfile"];
5808
+ cfg = deepMerge2(cfg, profileSettings);
5713
5809
  }
5714
5810
  cfg = deepMerge2(cfg, local);
5715
5811
  cfg = deepMerge2(
@@ -5815,6 +5911,7 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
5815
5911
  fileExisted = false;
5816
5912
  parsed = {};
5817
5913
  }
5914
+ if (parsed["version"] !== void 0 && parsed["version"] !== 1) return;
5818
5915
  const profileName = parsed.activeProfile ?? "default";
5819
5916
  const profileFp = this.paths.profileConfig(profileName);
5820
5917
  await this.ensureProfileConfig(profileFp, parsed, fileExisted);
@@ -5871,11 +5968,9 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
5871
5968
  * with behavior defaults. Subsequent boots: fill any missing keys from
5872
5969
  * BEHAVIOR_DEFAULTS (keeping user settings intact).
5873
5970
  *
5874
- * CRITICAL SAFETY NET: when the profile already exists but the old global
5875
- * config has extra non-bootstrap keys (because WebUI/CLI persistence functions
5876
- * mistakenly wrote settings to the root config), those keys are merged into
5877
- * the profile so they are NOT silently destroyed by the subsequent bootstrap
5878
- * trim. This prevents the "config keeps getting emptied" bug.
5971
+ * Once a profile file exists, settings found in the root bootstrap are
5972
+ * ignored. This prevents stale or accidentally-written root values from
5973
+ * becoming a hidden settings source alongside profiles.
5879
5974
  */
5880
5975
  async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
5881
5976
  const t0 = Date.now();
@@ -5905,42 +6000,13 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
5905
6000
  existed = false;
5906
6001
  parsed = {};
5907
6002
  }
5908
- const profileHasContent = existed && Object.keys(parsed).some((k) => k !== "version" && k !== "activeProfile");
5909
6003
  let seed;
5910
- if (!profileHasContent && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
6004
+ if (!existed && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
5911
6005
  seed = { ...oldGlobalParsed };
5912
6006
  delete seed["activeProfile"];
5913
6007
  const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
5914
6008
  seed = filled.value;
5915
6009
  } else {
5916
- if (existed && !_DefaultConfigLoader.isBootstrapOnly(oldGlobalParsed)) {
5917
- const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
5918
- let merged = false;
5919
- for (const [k, v] of Object.entries(oldGlobalParsed)) {
5920
- if (!BOOTSTRAP_KEYS.has(k) && !(k in parsed)) {
5921
- parsed[k] = v;
5922
- merged = true;
5923
- }
5924
- }
5925
- if (merged) {
5926
- const filled2 = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
5927
- if (filled2.changed) {
5928
- seed = filled2.value;
5929
- await backupConfigFile(profileFp, this.paths);
5930
- await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
5931
- this.events?.emit("storage.write", {
5932
- sessionId: "~config~",
5933
- store: "config",
5934
- filePath: profileFp,
5935
- operation: "ensure_profile_defaults",
5936
- outcome: "success",
5937
- durationMs: Date.now() - t0,
5938
- ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5939
- });
5940
- return;
5941
- }
5942
- }
5943
- }
5944
6010
  const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
5945
6011
  if (!filled.changed) return;
5946
6012
  seed = filled.value;
@@ -6260,7 +6326,7 @@ var DEFAULT_CONFIG_MIGRATIONS = [];
6260
6326
  // src/storage/recovery-lock.ts
6261
6327
  import * as fsp7 from "node:fs/promises";
6262
6328
  import * as os2 from "node:os";
6263
- import * as path15 from "node:path";
6329
+ import * as path16 from "node:path";
6264
6330
  var LOCK_FILE = "active.json";
6265
6331
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
6266
6332
  var RecoveryLock = class {
@@ -6271,7 +6337,7 @@ var RecoveryLock = class {
6271
6337
  sessionStore;
6272
6338
  probe;
6273
6339
  constructor(opts) {
6274
- this.file = path15.join(opts.dir, LOCK_FILE);
6340
+ this.file = path16.join(opts.dir, LOCK_FILE);
6275
6341
  this.pid = opts.pid ?? process.pid;
6276
6342
  this.hostname = opts.hostname ?? os2.hostname();
6277
6343
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -6332,7 +6398,7 @@ var RecoveryLock = class {
6332
6398
  * null return before calling this.
6333
6399
  */
6334
6400
  async write(sessionId) {
6335
- await ensureDir(path15.dirname(this.file));
6401
+ await ensureDir(path16.dirname(this.file));
6336
6402
  const lock = {
6337
6403
  v: 1,
6338
6404
  sessionId,
@@ -7047,7 +7113,7 @@ var AnnotationsStore = class {
7047
7113
 
7048
7114
  // src/storage/replay-log-store.ts
7049
7115
  import * as fs10 from "node:fs/promises";
7050
- import * as path16 from "node:path";
7116
+ import * as path17 from "node:path";
7051
7117
 
7052
7118
  // src/replay/hash.ts
7053
7119
  import { createHash as createHash5 } from "node:crypto";
@@ -7285,13 +7351,13 @@ var ReplayLogStore = class {
7285
7351
  for (const entry of entries) {
7286
7352
  if (entry.name.startsWith(".")) continue;
7287
7353
  if (entry.isDirectory()) {
7288
- if (depth === 0) await scan(path16.join(dir, entry.name), entry.name, depth + 1);
7354
+ if (depth === 0) await scan(path17.join(dir, entry.name), entry.name, depth + 1);
7289
7355
  continue;
7290
7356
  }
7291
7357
  if (!entry.isFile() || !entry.name.endsWith(".replay.jsonl")) continue;
7292
7358
  const base = entry.name.slice(0, -".replay.jsonl".length);
7293
7359
  const sessionId = prefix ? `${prefix}/${base}` : base;
7294
- const fp = path16.join(dir, entry.name);
7360
+ const fp = path17.join(dir, entry.name);
7295
7361
  out.push({
7296
7362
  sessionId,
7297
7363
  entryCount: await this.countEntries(fp),
@@ -7463,7 +7529,7 @@ var ReplayLogStore = class {
7463
7529
 
7464
7530
  // src/storage/session-recovery.ts
7465
7531
  import * as fs11 from "node:fs/promises";
7466
- import * as path17 from "node:path";
7532
+ import * as path18 from "node:path";
7467
7533
  var SessionRecovery = class {
7468
7534
  constructor(dir) {
7469
7535
  this.dir = dir;
@@ -7578,7 +7644,7 @@ var SessionRecovery = class {
7578
7644
  continue;
7579
7645
  if (entry.isDirectory()) {
7580
7646
  if (depth === 0) {
7581
- await collect(path17.join(dir, entry.name), entry.name, depth + 1);
7647
+ await collect(path18.join(dir, entry.name), entry.name, depth + 1);
7582
7648
  }
7583
7649
  continue;
7584
7650
  }
@@ -8097,7 +8163,7 @@ var SessionAnalyzer = class {
8097
8163
  // src/session-registry.ts
8098
8164
  import { randomUUID as randomUUID5 } from "node:crypto";
8099
8165
  import * as fs13 from "node:fs/promises";
8100
- import * as path18 from "node:path";
8166
+ import * as path19 from "node:path";
8101
8167
  var REGISTRY_FILE = "session-registry.json";
8102
8168
  var HEARTBEAT_INTERVAL_MS = 5e3;
8103
8169
  var STALE_TIMEOUT_MS = 3e4;
@@ -8136,7 +8202,7 @@ var SessionRegistry = class {
8136
8202
  */
8137
8203
  lastEntry = null;
8138
8204
  constructor(globalRoot) {
8139
- this.filePath = path18.join(globalRoot, REGISTRY_FILE);
8205
+ this.filePath = path19.join(globalRoot, REGISTRY_FILE);
8140
8206
  }
8141
8207
  // ── Public API ──────────────────────────────────────────────────────────
8142
8208
  /**
@@ -8345,7 +8411,7 @@ var SessionRegistry = class {
8345
8411
  const retryDelayMs = 20;
8346
8412
  for (let attempt = 0; attempt < maxRetries; attempt++) {
8347
8413
  try {
8348
- await fs13.mkdir(path18.dirname(this.filePath), { recursive: true });
8414
+ await fs13.mkdir(path19.dirname(this.filePath), { recursive: true });
8349
8415
  let lockHandle = await fs13.open(lockPath, "wx").catch(() => null);
8350
8416
  if (!lockHandle) {
8351
8417
  if (await this.breakStaleLock(lockPath)) {
@@ -8409,9 +8475,9 @@ var SessionRegistry = class {
8409
8475
  await this.writeAtomicFile(registry);
8410
8476
  }
8411
8477
  async writeAtomicFile(registry) {
8412
- const tmp = path18.join(
8413
- path18.dirname(this.filePath),
8414
- `.${path18.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
8478
+ const tmp = path19.join(
8479
+ path19.dirname(this.filePath),
8480
+ `.${path19.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
8415
8481
  );
8416
8482
  try {
8417
8483
  const handle = await fs13.open(tmp, "w");
@@ -8429,21 +8495,21 @@ var SessionRegistry = class {
8429
8495
  }
8430
8496
  async pruneStaleTempFiles() {
8431
8497
  try {
8432
- const dir = path18.dirname(this.filePath);
8433
- const base = path18.basename(this.filePath);
8498
+ const dir = path19.dirname(this.filePath);
8499
+ const base = path19.basename(this.filePath);
8434
8500
  const now = Date.now();
8435
8501
  const stale = [];
8436
8502
  for (const name of await fs13.readdir(dir)) {
8437
8503
  const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
8438
8504
  if (!isTemp) continue;
8439
- const stat13 = await fs13.stat(path18.join(dir, name)).catch(() => null);
8505
+ const stat13 = await fs13.stat(path19.join(dir, name)).catch(() => null);
8440
8506
  if (!stat13) continue;
8441
8507
  if (now - stat13.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat13.mtimeMs });
8442
8508
  }
8443
8509
  stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
8444
8510
  await Promise.all(
8445
8511
  stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
8446
- await fs13.unlink(path18.join(dir, name)).catch(() => void 0);
8512
+ await fs13.unlink(path19.join(dir, name)).catch(() => void 0);
8447
8513
  })
8448
8514
  );
8449
8515
  } catch {
@@ -9179,7 +9245,7 @@ var AgentStatusTracker = class {
9179
9245
 
9180
9246
  // src/fleet-notifier.ts
9181
9247
  import * as fs14 from "node:fs/promises";
9182
- import * as path19 from "node:path";
9248
+ import * as path20 from "node:path";
9183
9249
  var INSTANCES_FILE = "webui-instances.json";
9184
9250
  var DISCOVERY_TTL_MS = 2500;
9185
9251
  var COALESCE_MS = 50;
@@ -9194,7 +9260,7 @@ function pidAlive2(pid) {
9194
9260
  }
9195
9261
  }
9196
9262
  function normRoot(root) {
9197
- const resolved = path19.resolve(root);
9263
+ const resolved = path20.resolve(root);
9198
9264
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
9199
9265
  }
9200
9266
  var FleetNotifier = class {
@@ -9241,7 +9307,7 @@ var FleetNotifier = class {
9241
9307
  }
9242
9308
  async discover() {
9243
9309
  try {
9244
- const raw = await fs14.readFile(path19.join(this.baseDir, INSTANCES_FILE), "utf8");
9310
+ const raw = await fs14.readFile(path20.join(this.baseDir, INSTANCES_FILE), "utf8");
9245
9311
  const data = JSON.parse(raw);
9246
9312
  const list = Array.isArray(data?.instances) ? data.instances : [];
9247
9313
  return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
@@ -9262,7 +9328,7 @@ async function defaultPost(url) {
9262
9328
 
9263
9329
  // src/storage/session-rewinder.ts
9264
9330
  import * as fsp8 from "node:fs/promises";
9265
- import * as path20 from "node:path";
9331
+ import * as path21 from "node:path";
9266
9332
  var DefaultSessionRewinder = class {
9267
9333
  constructor(sessionsDir, projectRoot) {
9268
9334
  this.sessionsDir = sessionsDir;
@@ -9400,10 +9466,10 @@ async function revertSnapshots(snapshots, projectRoot) {
9400
9466
  for (const snapshot of [...snapshots].reverse()) {
9401
9467
  for (const file of [...snapshot.files].reverse()) {
9402
9468
  try {
9403
- const absPath = path20.resolve(file.path);
9404
- const root = path20.resolve(projectRoot);
9405
- const rel = path20.relative(root, absPath);
9406
- if (rel.startsWith("..") || path20.isAbsolute(rel)) {
9469
+ const absPath = path21.resolve(file.path);
9470
+ const root = path21.resolve(projectRoot);
9471
+ const rel = path21.relative(root, absPath);
9472
+ if (rel.startsWith("..") || path21.isAbsolute(rel)) {
9407
9473
  errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
9408
9474
  continue;
9409
9475
  }
@@ -10929,7 +10995,7 @@ function normalizeTitle(value) {
10929
10995
  // src/storage/prompt-store.ts
10930
10996
  import { createHash as createHash7 } from "node:crypto";
10931
10997
  import * as fs15 from "node:fs/promises";
10932
- import * as path21 from "node:path";
10998
+ import * as path22 from "node:path";
10933
10999
  var SCHEMA_VERSION = 2;
10934
11000
  function promptChecksum(content) {
10935
11001
  return createHash7("sha256").update(content, "utf8").digest("hex");
@@ -10998,7 +11064,7 @@ var DefaultPromptStore = class {
10998
11064
  if (!file.endsWith(".json")) continue;
10999
11065
  try {
11000
11066
  const raw = JSON.parse(
11001
- await fs15.readFile(path21.join(this.dir, file), "utf8")
11067
+ await fs15.readFile(path22.join(this.dir, file), "utf8")
11002
11068
  );
11003
11069
  const migrated = migratePromptEntry(raw.entry);
11004
11070
  if (migrated) entries.push(migrated);
@@ -11012,7 +11078,7 @@ var DefaultPromptStore = class {
11012
11078
  );
11013
11079
  }
11014
11080
  async get(id) {
11015
- const file = path21.join(this.dir, `${id}.json`);
11081
+ const file = path22.join(this.dir, `${id}.json`);
11016
11082
  try {
11017
11083
  const raw = JSON.parse(await fs15.readFile(file, "utf8"));
11018
11084
  return migratePromptEntry(raw.entry);
@@ -11022,12 +11088,12 @@ var DefaultPromptStore = class {
11022
11088
  }
11023
11089
  async save(entry) {
11024
11090
  await ensureDir(this.dir);
11025
- const file = path21.join(this.dir, `${entry.id}.json`);
11091
+ const file = path22.join(this.dir, `${entry.id}.json`);
11026
11092
  const raw = { version: SCHEMA_VERSION, entry };
11027
11093
  await atomicWrite(file, JSON.stringify(raw, null, 2));
11028
11094
  }
11029
11095
  async delete(id) {
11030
- const file = path21.join(this.dir, `${id}.json`);
11096
+ const file = path22.join(this.dir, `${id}.json`);
11031
11097
  try {
11032
11098
  await fs15.unlink(file);
11033
11099
  return true;
@@ -11069,7 +11135,7 @@ var DefaultPromptStore = class {
11069
11135
 
11070
11136
  // src/storage/prompt-usage-store.ts
11071
11137
  import * as fs16 from "node:fs/promises";
11072
- import * as path22 from "node:path";
11138
+ import * as path23 from "node:path";
11073
11139
  var PromptUsageStore = class {
11074
11140
  constructor(file) {
11075
11141
  this.file = file;
@@ -11090,7 +11156,7 @@ var PromptUsageStore = class {
11090
11156
  const prev = usage[slug];
11091
11157
  const next = { count: (prev?.count ?? 0) + 1, lastUsedAt: at };
11092
11158
  usage[slug] = next;
11093
- await ensureDir(path22.dirname(this.file));
11159
+ await ensureDir(path23.dirname(this.file));
11094
11160
  await atomicWrite(this.file, JSON.stringify({ version: 1, usage }, null, 2));
11095
11161
  return next;
11096
11162
  }
@@ -11115,7 +11181,7 @@ var PromptUsageStore = class {
11115
11181
 
11116
11182
  // src/storage/input-history-store.ts
11117
11183
  import * as fs17 from "node:fs/promises";
11118
- import * as path23 from "node:path";
11184
+ import * as path24 from "node:path";
11119
11185
  var INPUT_HISTORY_DEFAULT_MAX = 100;
11120
11186
  var InputHistoryStore = class {
11121
11187
  /**
@@ -11154,7 +11220,7 @@ var InputHistoryStore = class {
11154
11220
  */
11155
11221
  async save(entries) {
11156
11222
  const cleaned = this.scrubAndFilter(entries);
11157
- await ensureDir(path23.dirname(this.file));
11223
+ await ensureDir(path24.dirname(this.file));
11158
11224
  const payload = {
11159
11225
  version: 1,
11160
11226
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11164,7 +11230,7 @@ var InputHistoryStore = class {
11164
11230
  }
11165
11231
  /** Truncate the file to an empty entry list (used by /clear). */
11166
11232
  async clear() {
11167
- await ensureDir(path23.dirname(this.file));
11233
+ await ensureDir(path24.dirname(this.file));
11168
11234
  const payload = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: [] };
11169
11235
  await atomicWrite(this.file, JSON.stringify(payload, null, 2));
11170
11236
  }
@@ -11195,7 +11261,7 @@ var InputHistoryStore = class {
11195
11261
 
11196
11262
  // src/storage/cloud-sync.ts
11197
11263
  import * as fs18 from "node:fs/promises";
11198
- import * as path24 from "node:path";
11264
+ import * as path25 from "node:path";
11199
11265
  import { createHash as createHash8 } from "node:crypto";
11200
11266
  var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
11201
11267
  var CloudSync = class {
@@ -11203,7 +11269,7 @@ var CloudSync = class {
11203
11269
  this.paths = paths;
11204
11270
  this.getConfig = getConfig;
11205
11271
  this.setConfig = setConfig;
11206
- this.statePath = path24.join(paths.globalRoot, "sync-state.json");
11272
+ this.statePath = path25.join(paths.globalRoot, "sync-state.json");
11207
11273
  }
11208
11274
  paths;
11209
11275
  getConfig;
@@ -11310,7 +11376,7 @@ var CloudSync = class {
11310
11376
  const rel = segments.slice(2).join("/");
11311
11377
  const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
11312
11378
  const blobData = await this.getBlob(token, owner, repoName, entry.sha);
11313
- await fs18.mkdir(path24.dirname(destPath), { recursive: true });
11379
+ await fs18.mkdir(path25.dirname(destPath), { recursive: true });
11314
11380
  await fs18.writeFile(destPath, Buffer.from(blobData, "base64"));
11315
11381
  }
11316
11382
  const localRev = await this.hashLocalCategories(cfg.categories);
@@ -11422,7 +11488,7 @@ var CloudSync = class {
11422
11488
  const files = await this.walkDir(localPath, localPath);
11423
11489
  for (const file of files) {
11424
11490
  const content = await fs18.readFile(file, "utf8");
11425
- const rel = path24.relative(localPath, file).replace(/\\/g, "/");
11491
+ const rel = path25.relative(localPath, file).replace(/\\/g, "/");
11426
11492
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
11427
11493
  hashes.push(content);
11428
11494
  }
@@ -11480,7 +11546,7 @@ var CloudSync = class {
11480
11546
  const results = [];
11481
11547
  const entries = await fs18.readdir(dir, { withFileTypes: true });
11482
11548
  for (const entry of entries) {
11483
- const full = path24.join(dir, entry.name);
11549
+ const full = path25.join(dir, entry.name);
11484
11550
  if (entry.isDirectory()) {
11485
11551
  results.push(...await this.walkDir(full, base));
11486
11552
  } else {
@@ -11502,9 +11568,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
11502
11568
  return localPath;
11503
11569
  }
11504
11570
  if (!rel) return localPath;
11505
- const normalizedRel = path24.normalize(rel);
11506
- const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path24.sep}`);
11507
- if (path24.isAbsolute(normalizedRel) || traversesUp) {
11571
+ const normalizedRel = path25.normalize(rel);
11572
+ const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path25.sep}`);
11573
+ if (path25.isAbsolute(normalizedRel) || traversesUp) {
11508
11574
  throw new FsError({
11509
11575
  message: `Refusing CloudSync path traversal: ${remotePath}`,
11510
11576
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -11512,10 +11578,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
11512
11578
  context: { reason: "path_traversal", normalizedRel }
11513
11579
  });
11514
11580
  }
11515
- const dest = path24.resolve(localPath, normalizedRel);
11516
- const root = path24.resolve(localPath);
11517
- const relative7 = path24.relative(root, dest);
11518
- if (relative7.startsWith("..") || path24.isAbsolute(relative7)) {
11581
+ const dest = path25.resolve(localPath, normalizedRel);
11582
+ const root = path25.resolve(localPath);
11583
+ const relative8 = path25.relative(root, dest);
11584
+ if (relative8.startsWith("..") || path25.isAbsolute(relative8)) {
11519
11585
  throw new FsError({
11520
11586
  message: `Refusing CloudSync path outside category root: ${remotePath}`,
11521
11587
  code: ERROR_CODES.FS_DELETE_FAILED,