@wrongstack/core 0.272.0 → 0.272.1

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 (47) hide show
  1. package/dist/{agent-bridge-jVSZiygR.d.ts → agent-bridge-DFQYEeXf.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DOLIwBRo.d.ts → agent-subagent-runner-BZa_IEcd.d.ts} +4 -4
  3. package/dist/{brain-CdbbJWi3.d.ts → brain-etbcbRwV.d.ts} +24 -1
  4. package/dist/{config-D2DGoGSQ.d.ts → config-rRS8yorV.d.ts} +43 -1
  5. package/dist/coordination/index.d.ts +74 -14
  6. package/dist/coordination/index.js +285 -97
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +16 -16
  9. package/dist/defaults/index.js +209 -140
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/execution/index.d.ts +9 -9
  12. package/dist/extension/index.d.ts +4 -4
  13. package/dist/{global-mailbox-CQj_C9Dp.d.ts → global-mailbox-DJ4EoRr0.d.ts} +7 -3
  14. package/dist/{goal-preamble-ZXDjjR1y.d.ts → goal-preamble-hM8BH7TK.d.ts} +5 -5
  15. package/dist/{goal-store-CcJBd-g1.d.ts → goal-store-CWlbT0TO.d.ts} +1 -1
  16. package/dist/hq/index.d.ts +6 -4
  17. package/dist/hq/index.js +14 -6
  18. package/dist/hq/index.js.map +1 -1
  19. package/dist/{index-Qo4kTzgw.d.ts → index-DWm_PE9L.d.ts} +3 -3
  20. package/dist/{index-BL7BAx0p.d.ts → index-DqW4o62H.d.ts} +4 -4
  21. package/dist/index.d.ts +27 -27
  22. package/dist/index.js +571 -215
  23. package/dist/index.js.map +1 -1
  24. package/dist/infrastructure/index.d.ts +4 -4
  25. package/dist/kernel/index.d.ts +5 -5
  26. package/dist/kernel/index.js.map +1 -1
  27. package/dist/{mcp-servers-DS-YUXvF.d.ts → mcp-servers-BpWHTKlE.d.ts} +1 -1
  28. package/dist/models/index.d.ts +3 -3
  29. package/dist/{models-registry-DP6pGHet.d.ts → models-registry-CXQFUn5t.d.ts} +1 -1
  30. package/dist/{multi-agent-coordinator-BvbdNQ14.d.ts → multi-agent-coordinator-jyimfo7D.d.ts} +1 -1
  31. package/dist/{null-fleet-bus-BxTfXBKo.d.ts → null-fleet-bus-DOGQcvrY.d.ts} +5 -5
  32. package/dist/observability/index.d.ts +1 -1
  33. package/dist/{parallel-eternal-engine-Cf-GTegR.d.ts → parallel-eternal-engine-rItJBYp9.d.ts} +6 -6
  34. package/dist/{path-resolver-DztfnFcv.d.ts → path-resolver-DrpF5MGK.d.ts} +2 -2
  35. package/dist/{pipeline-sNIkhXeB.d.ts → pipeline-Ckkn3AOA.d.ts} +1 -1
  36. package/dist/{plan-templates-DYiKFmEb.d.ts → plan-templates-BvHw5Znw.d.ts} +24 -6
  37. package/dist/{provider-model-resolve-dYAbTs_i.d.ts → provider-model-resolve-nZqnCeaR.d.ts} +1 -1
  38. package/dist/{provider-runner-Dw8x0F7u.d.ts → provider-runner-zVOn1p67.d.ts} +1 -1
  39. package/dist/sdd/index.d.ts +5 -5
  40. package/dist/storage/index.d.ts +12 -7
  41. package/dist/storage/index.js +250 -111
  42. package/dist/storage/index.js.map +1 -1
  43. package/dist/tools/index.d.ts +1 -1
  44. package/dist/types/index.d.ts +13 -13
  45. package/dist/types/index.js.map +1 -1
  46. package/dist/utils/index.d.ts +1 -1
  47. package/package.json +1 -1
@@ -80,7 +80,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
80
80
  if (Date.now() - started >= timeoutMs) {
81
81
  throw new Error(`Timed out waiting for file lock: ${targetPath}`);
82
82
  }
83
- await new Promise((resolve6) => setTimeout(resolve6, 25));
83
+ await new Promise((resolve7) => setTimeout(resolve7, 25));
84
84
  }
85
85
  }
86
86
  try {
@@ -114,7 +114,7 @@ async function renameWithRetry(from, to) {
114
114
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
115
115
  throw err;
116
116
  }
117
- await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
117
+ await new Promise((resolve7) => setTimeout(resolve7, delays[i]));
118
118
  }
119
119
  }
120
120
  throw lastErr;
@@ -477,7 +477,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
477
477
  this._loadCache.clear();
478
478
  }
479
479
  }
480
- // ── Storage event helpers ───────────────────────────────────────────────────
480
+ // ── Storage event helpers ───────────────────────────────────────────────────
481
481
  emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
482
482
  this.events?.emit("storage.read", {
483
483
  sessionId,
@@ -592,7 +592,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
592
592
  this.events,
593
593
  {
594
594
  resumed: true,
595
- // Shard directory (sessions/<date>/) must match create() so the
595
+ // Shard directory (sessions/<date>/) — must match create() so the
596
596
  // .summary.json sidecar lands next to the JSONL instead of the
597
597
  // sessions root (where summaryFor() would never find it).
598
598
  dir: path2.dirname(file),
@@ -633,19 +633,93 @@ var DefaultSessionStore = class _DefaultSessionStore {
633
633
  const raw = await fsp.readFile(file, "utf8");
634
634
  const lines = raw.split("\n").filter((l) => l.trim());
635
635
  const events = [];
636
+ let sessionStartEvent;
637
+ let sessionEndEvent;
638
+ let sessionModel;
639
+ let sessionProvider;
640
+ let sessionPendingToolUses;
641
+ const messages = [];
642
+ const openToolUses = /* @__PURE__ */ new Set();
643
+ let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
636
644
  for (const line of lines) {
637
645
  try {
638
646
  const parsed = JSON.parse(line);
639
647
  if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
640
- events.push(parsed);
648
+ const ev = parsed;
649
+ events.push(ev);
650
+ if (ev.type === "session_start" && !sessionStartEvent) {
651
+ sessionStartEvent = ev;
652
+ sessionModel = ev.model;
653
+ sessionProvider = ev.provider;
654
+ }
655
+ if (ev.type === "session_end") {
656
+ sessionEndEvent = ev;
657
+ sessionPendingToolUses = ev.pendingToolUses;
658
+ }
659
+ if (ev.type === "user_input") {
660
+ openToolUses.clear();
661
+ messages.push({ role: "user", content: ev.content, ts: ev.ts });
662
+ } else if (ev.type === "llm_response") {
663
+ messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
664
+ for (const b of ev.content) {
665
+ if (b.type === "tool_use") openToolUses.add(b.id);
666
+ }
667
+ usage = {
668
+ input: usage.input + (ev.usage.input ?? 0),
669
+ output: usage.output + (ev.usage.output ?? 0),
670
+ cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
671
+ cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
672
+ };
673
+ } else if (ev.type === "tool_result") {
674
+ if (!openToolUses.has(ev.id)) {
675
+ this.events?.emit("session.damaged", {
676
+ sessionId: id,
677
+ detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
678
+ });
679
+ continue;
680
+ }
681
+ openToolUses.delete(ev.id);
682
+ const resultBlock = {
683
+ type: "tool_result",
684
+ tool_use_id: ev.id,
685
+ content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
686
+ is_error: ev.isError
687
+ };
688
+ const last = messages[messages.length - 1];
689
+ const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
690
+ if (lastIsToolResultUser && Array.isArray(last.content)) {
691
+ last.content.push(resultBlock);
692
+ } else {
693
+ messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
694
+ }
695
+ }
641
696
  }
642
697
  } catch {
643
698
  }
644
699
  }
645
- const meta = this.metaFromEvents(id, events);
646
- const { messages, usage } = this.replay(events, id);
700
+ if (openToolUses.size > 0) {
701
+ this.events?.emit("session.damaged", {
702
+ sessionId: id,
703
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
704
+ });
705
+ }
706
+ const repaired = repairToolUseAdjacency(messages);
707
+ if (repaired.report.changed) {
708
+ this.events?.emit("session.damaged", {
709
+ sessionId: id,
710
+ detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
711
+ });
712
+ }
713
+ const meta = {
714
+ id,
715
+ startedAt: sessionStartEvent?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
716
+ endedAt: sessionEndEvent?.ts,
717
+ model: sessionModel,
718
+ provider: sessionProvider,
719
+ pendingToolUses: sessionPendingToolUses
720
+ };
647
721
  const toolCallEnds = extractToolCallEnds(events);
648
- const data = { metadata: meta, events, messages, usage, toolCallEnds };
722
+ const data = { metadata: meta, events, messages: repaired.messages, usage, toolCallEnds };
649
723
  if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
650
724
  const oldest = this._loadCache.keys().next().value;
651
725
  if (oldest !== void 0) {
@@ -688,7 +762,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
688
762
  return [];
689
763
  }
690
764
  }
691
- // ── Session index (_index.jsonl) ─────────────────────────────────────────
765
+ // ── Session index (_index.jsonl) ─────────────────────────────────────────
692
766
  //
693
767
  // One JSON line per closed session, appended atomically on close().
694
768
  // When a session is deleted, a tombstone {action:"delete",id:"..."} is
@@ -864,7 +938,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
864
938
  return [...childFileArrays.flat(), ...files];
865
939
  }
866
940
  /** Recursively collect session IDs from date-shard subdirectories.
867
- * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_").
941
+ * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
868
942
  * Skips `.jsonl`/`.summary.json` root files, dot-files, and
869
943
  * sub-directories that belong to fleet/subagent sessions. */
870
944
  async collectSessionIds(dir, prefix = "", depth = 0) {
@@ -919,7 +993,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
919
993
  }));
920
994
  });
921
995
  outcome = "failure";
922
- errorMsg = "summary fallback \u2014 manifest rebuilt";
996
+ errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
923
997
  this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
924
998
  return summary;
925
999
  } catch (err) {
@@ -1211,76 +1285,6 @@ var DefaultSessionStore = class _DefaultSessionStore {
1211
1285
  stream.destroy();
1212
1286
  }
1213
1287
  }
1214
- metaFromEvents(id, events) {
1215
- const start = events.find((e) => e.type === "session_start");
1216
- const end = events.findLast((e) => e.type === "session_end");
1217
- return {
1218
- id,
1219
- startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1220
- endedAt: end?.ts,
1221
- model: start?.model,
1222
- provider: start?.provider,
1223
- pendingToolUses: end?.pendingToolUses
1224
- };
1225
- }
1226
- replay(events, sessionId = "unknown") {
1227
- const messages = [];
1228
- let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
1229
- const openToolUses = /* @__PURE__ */ new Set();
1230
- for (const e of events) {
1231
- if (e.type === "user_input") {
1232
- openToolUses.clear();
1233
- messages.push({ role: "user", content: e.content, ts: e.ts });
1234
- } else if (e.type === "llm_response") {
1235
- messages.push({ role: "assistant", content: e.content, ts: e.ts });
1236
- for (const b of e.content) {
1237
- if (b.type === "tool_use") openToolUses.add(b.id);
1238
- }
1239
- usage = {
1240
- input: usage.input + (e.usage.input ?? 0),
1241
- output: usage.output + (e.usage.output ?? 0),
1242
- cacheRead: (usage.cacheRead ?? 0) + (e.usage.cacheRead ?? 0),
1243
- cacheWrite: (usage.cacheWrite ?? 0) + (e.usage.cacheWrite ?? 0)
1244
- };
1245
- } else if (e.type === "tool_result") {
1246
- if (!openToolUses.has(e.id)) {
1247
- this.events?.emit("session.damaged", {
1248
- sessionId,
1249
- detail: `Orphan tool_result "${e.id}" has no matching tool_use`
1250
- });
1251
- continue;
1252
- }
1253
- openToolUses.delete(e.id);
1254
- const resultBlock = {
1255
- type: "tool_result",
1256
- tool_use_id: e.id,
1257
- content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
1258
- is_error: e.isError
1259
- };
1260
- const last = messages[messages.length - 1];
1261
- const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
1262
- if (lastIsToolResultUser && Array.isArray(last.content)) {
1263
- last.content.push(resultBlock);
1264
- } else {
1265
- messages.push({ role: "user", content: [resultBlock], ts: e.ts });
1266
- }
1267
- }
1268
- }
1269
- if (openToolUses.size > 0) {
1270
- this.events?.emit("session.damaged", {
1271
- sessionId,
1272
- detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
1273
- });
1274
- }
1275
- const repaired = repairToolUseAdjacency(messages);
1276
- if (repaired.report.changed) {
1277
- this.events?.emit("session.damaged", {
1278
- sessionId,
1279
- detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
1280
- });
1281
- }
1282
- return { messages: repaired.messages, usage };
1283
- }
1284
1288
  };
1285
1289
  function extractToolCallEnds(events) {
1286
1290
  const result = [];
@@ -1339,7 +1343,7 @@ var FileSessionWriter = class _FileSessionWriter {
1339
1343
  /**
1340
1344
  * Lazy session_start/session_resumed init, shared by all appenders.
1341
1345
  * A single promise (not a boolean) so a second append racing the first
1342
- * can't push its event into the buffer BEFORE the first append's event
1346
+ * can't push its event into the buffer BEFORE the first append's event —
1343
1347
  * every appender awaits the same init and resumes in FIFO call order.
1344
1348
  */
1345
1349
  initPromise = null;
@@ -1352,24 +1356,24 @@ var FileSessionWriter = class _FileSessionWriter {
1352
1356
  lastAppendWarnAt = 0;
1353
1357
  secretScrubber;
1354
1358
  onCloseCb;
1355
- /** Implements SessionWriter.traceId propagated from ContextInit.traceId. */
1359
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
1356
1360
  traceId;
1357
- // ── Write buffer batches events to reduce per-event disk I/O ─────────
1361
+ // ── Write buffer — batches events to reduce per-event disk I/O ─────────
1358
1362
  //
1359
1363
  // Every append() pushes the scrubbed event into an in-memory buffer instead
1360
1364
  // of calling handle.appendFile() synchronously. The buffer flushes to disk
1361
1365
  // when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.
1362
1366
  // This cuts the number of disk writes by ~95% without changing the on-disk
1363
- // format the JSONL is still one JSON object per line.
1367
+ // format — the JSONL is still one JSON object per line.
1364
1368
  writeBuffer = [];
1365
1369
  flushTimer = null;
1366
1370
  static FLUSH_INTERVAL_MS = 500;
1367
1371
  static FLUSH_SIZE = 50;
1368
- // ── Write serialization ─────────────────────────────────────────────────
1372
+ // ── Write serialization ─────────────────────────────────────────────────
1369
1373
  //
1370
1374
  // All disk writes are funneled through a FIFO promise chain. Without it,
1371
1375
  // a timer-driven flush racing an explicit flush()/close() issues two
1372
- // concurrent appendFile() calls on the shared O_APPEND handle the kernel
1376
+ // concurrent appendFile() calls on the shared O_APPEND handle — the kernel
1373
1377
  // may complete them out of order (chronology breaks) or, for large
1374
1378
  // batches, interleave partial writes (torn JSONL lines). The chain keeps
1375
1379
  // exactly one write in flight; failures don't break the chain.
@@ -1383,7 +1387,7 @@ var FileSessionWriter = class _FileSessionWriter {
1383
1387
  );
1384
1388
  return write;
1385
1389
  }
1386
- // ── Enriched summary tracking ──────────────────────────────────────────
1390
+ // ── Enriched summary tracking ──────────────────────────────────────────
1387
1391
  iterationCount = 0;
1388
1392
  toolCallCount = 0;
1389
1393
  toolErrorCount = 0;
@@ -1475,7 +1479,7 @@ var FileSessionWriter = class _FileSessionWriter {
1475
1479
  * (user_input, llm_response) call this so they survive SIGKILL/crash
1476
1480
  * instead of sitting in the in-memory buffer for up to 500ms.
1477
1481
  *
1478
- * Idempotent cancels any pending timer and writes whatever has
1482
+ * Idempotent — cancels any pending timer and writes whatever has
1479
1483
  * accumulated in the buffer. Safe to call even when the buffer
1480
1484
  * is empty (no-op).
1481
1485
  */
@@ -1498,7 +1502,7 @@ var FileSessionWriter = class _FileSessionWriter {
1498
1502
  /**
1499
1503
  * Flush all buffered events to disk as a single appendFile call.
1500
1504
  * Errors use the same throttled-warning pattern the old per-event
1501
- * append path used one warning every 5s with a suppressed count.
1505
+ * append path used — one warning every 5s with a suppressed count.
1502
1506
  * On failure the buffer is cleared (events are best-effort, same as
1503
1507
  * the old per-event path where a failed write was silently dropped).
1504
1508
  */
@@ -1681,7 +1685,7 @@ var FileSessionWriter = class _FileSessionWriter {
1681
1685
  /**
1682
1686
  * Truncate the session file to the checkpoint with the given promptIndex,
1683
1687
  * removing all events that follow it. Uses a single-pass byte-offset scan
1684
- * so post-checkpoint content is never read or parsed O(1) memory instead
1688
+ * so post-checkpoint content is never read or parsed — O(1) memory instead
1685
1689
  * of O(N) JSON.parse calls over the full file.
1686
1690
  */
1687
1691
  async truncateToCheckpoint(targetPromptIndex) {
@@ -1838,7 +1842,7 @@ var FileSessionWriter = class _FileSessionWriter {
1838
1842
  await fsp.writeFile(this.filePath, record, "utf8");
1839
1843
  }
1840
1844
  /**
1841
- * Idea #1 write an in-flight marker. The agent loop should call
1845
+ * Idea #1 — write an in-flight marker. The agent loop should call
1842
1846
  * this at the start of each long-running operation; a matching
1843
1847
  * `clearInFlightMarker` follows on clean exit. A stale marker
1844
1848
  * (no end) is what `SessionRecovery.detectStale` looks for.
@@ -1855,9 +1859,9 @@ var FileSessionWriter = class _FileSessionWriter {
1855
1859
  this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
1856
1860
  }
1857
1861
  /**
1858
- * Idea #1 close the in-flight marker. Idempotent in spirit
1862
+ * Idea #1 — close the in-flight marker. Idempotent in spirit
1859
1863
  * (you can call it after a successful iteration even if you
1860
- * didn't open one this round) but the session log records
1864
+ * didn't open one this round) — but the session log records
1861
1865
  * every call so postmortem tooling can see "the agent finished
1862
1866
  * cleanly X times, then died without finishing Y".
1863
1867
  */
@@ -2475,6 +2479,20 @@ var DefaultMemoryStore = class {
2475
2479
  */
2476
2480
  persistBackup;
2477
2481
  backupDir;
2482
+ /**
2483
+ * Per-scope tracked byte sizes — incremented on `remember()`, decremented on
2484
+ * `forget()`, recalculated after `consolidate()`. Eliminates the redundant
2485
+ * readAll() call that previously checked the file size after every write.
2486
+ */
2487
+ _trackedByteSizes = {};
2488
+ /** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
2489
+ _scoreCache = /* @__PURE__ */ new Map();
2490
+ /**
2491
+ * Per-entry cached lowercase strings — computed once per scoreRelevant() call,
2492
+ * stored here so repeated scoring of the same entries avoids re-computation.
2493
+ * Cleared on every mutation (remember/forget/consolidate/clear).
2494
+ */
2495
+ _cachedLower = null;
2478
2496
  constructor(opts) {
2479
2497
  this.files = {
2480
2498
  "project-agents": opts.paths.inProjectAgentsFile,
@@ -2508,6 +2526,20 @@ var DefaultMemoryStore = class {
2508
2526
  }
2509
2527
  }
2510
2528
  }
2529
+ /**
2530
+ * Recalculate the tracked byte size for a scope by re-reading the file and
2531
+ * summing the serialized byte length of each line. Called after consolidate()
2532
+ * (which modifies the file) to keep the tracker accurate.
2533
+ */
2534
+ async _recalcTrackedByteSize(scope) {
2535
+ const raw = await this.backend.readAll(scope, this.files[scope]);
2536
+ let total = 0;
2537
+ for (const line of raw.split("\n")) {
2538
+ if (line.trim()) total += Buffer.byteLength(line, "utf8");
2539
+ }
2540
+ this._trackedByteSizes[scope] = total;
2541
+ return total;
2542
+ }
2511
2543
  async readAll() {
2512
2544
  const parts = [];
2513
2545
  for (const scope of ["project-agents", "project-memory", "user-memory"]) {
@@ -2608,6 +2640,7 @@ ${body.trim()}`);
2608
2640
  const t0 = Date.now();
2609
2641
  try {
2610
2642
  await this.backend.remember(scope, entry, filePath);
2643
+ this._scoreCache.clear();
2611
2644
  const dur = Date.now() - t0;
2612
2645
  this.events?.emit("storage.write", {
2613
2646
  sessionId: "~memory~",
@@ -2632,16 +2665,21 @@ ${body.trim()}`);
2632
2665
  });
2633
2666
  throw err;
2634
2667
  }
2635
- const raw = await this.backend.readAll(scope, this.files[scope]);
2636
- if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
2668
+ let trackedSize = this._trackedByteSizes[scope];
2669
+ if (trackedSize === void 0) {
2670
+ trackedSize = await this._recalcTrackedByteSize(scope);
2671
+ }
2672
+ if (trackedSize > MAX_BYTES_TOTAL) {
2637
2673
  const removed = await this.backend.consolidate(scope, this.files[scope]);
2638
2674
  if (removed > 0) {
2639
2675
  this.events?.emit("memory.consolidated", {
2640
2676
  scope,
2641
2677
  removed
2642
2678
  });
2679
+ await this._recalcTrackedByteSize(scope);
2643
2680
  }
2644
2681
  }
2682
+ this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
2645
2683
  await this.mirrorBackup(scope);
2646
2684
  this.events?.emit("memory.remembered", {
2647
2685
  scope,
@@ -2660,16 +2698,30 @@ ${body.trim()}`);
2660
2698
  async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
2661
2699
  const all = await this.list(scope);
2662
2700
  if (all.length === 0) return [];
2701
+ const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
2702
+ const now = Date.now();
2703
+ const TTL_MS = 3e4;
2704
+ const cached = this._scoreCache.get(ctxHash);
2705
+ if (cached && cached.expiresAt > now && cached.entries === all) {
2706
+ return cached.scored.slice(0, Math.min(limit, 15));
2707
+ }
2663
2708
  const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
2664
2709
  const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
2665
2710
  const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
2666
- const now = Date.now();
2711
+ this._cachedLower = /* @__PURE__ */ new WeakMap();
2667
2712
  const scored = [];
2668
2713
  for (const entry of all) {
2669
2714
  let score = 0;
2670
2715
  const reasons = [];
2671
- const textLower = entry.text.toLowerCase();
2672
- const tagsLower = (entry.tags ?? []).map((t) => t.toLowerCase());
2716
+ let cachedLower = this._cachedLower.get(entry);
2717
+ if (!cachedLower) {
2718
+ cachedLower = {
2719
+ textLower: entry.text.toLowerCase(),
2720
+ tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
2721
+ };
2722
+ this._cachedLower.set(entry, cachedLower);
2723
+ }
2724
+ const { textLower, tagsLower } = cachedLower;
2673
2725
  let taskHits = 0;
2674
2726
  for (const w of taskWords) {
2675
2727
  if (textLower.includes(w)) {
@@ -2755,6 +2807,7 @@ ${body.trim()}`);
2755
2807
  const relevant = scored.filter(
2756
2808
  (s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
2757
2809
  );
2810
+ this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
2758
2811
  return relevant.slice(0, Math.min(limit, 15));
2759
2812
  }
2760
2813
  async forget(query, scope = "project-memory") {
@@ -2764,6 +2817,7 @@ ${body.trim()}`);
2764
2817
  let removed = 0;
2765
2818
  try {
2766
2819
  removed = await this.backend.forget(scope, query, filePath);
2820
+ this._scoreCache.clear();
2767
2821
  const dur = Date.now() - t0;
2768
2822
  this.events?.emit("storage.write", {
2769
2823
  sessionId: "~memory~",
@@ -2795,6 +2849,7 @@ ${body.trim()}`);
2795
2849
  removed
2796
2850
  });
2797
2851
  await this.mirrorBackup(scope);
2852
+ await this._recalcTrackedByteSize(scope);
2798
2853
  }
2799
2854
  return removed;
2800
2855
  });
@@ -2806,6 +2861,7 @@ ${body.trim()}`);
2806
2861
  let removed = 0;
2807
2862
  try {
2808
2863
  removed = await this.backend.consolidate(scope, filePath);
2864
+ this._scoreCache.clear();
2809
2865
  const dur = Date.now() - t0;
2810
2866
  this.events?.emit("storage.write", {
2811
2867
  sessionId: "~memory~",
@@ -2836,6 +2892,7 @@ ${body.trim()}`);
2836
2892
  removed
2837
2893
  });
2838
2894
  await this.mirrorBackup(scope);
2895
+ await this._recalcTrackedByteSize(scope);
2839
2896
  }
2840
2897
  });
2841
2898
  }
@@ -2846,6 +2903,7 @@ ${body.trim()}`);
2846
2903
  const t0 = Date.now();
2847
2904
  try {
2848
2905
  await this.backend.clear(scope, filePath);
2906
+ this._scoreCache.clear();
2849
2907
  const dur = Date.now() - t0;
2850
2908
  this.events?.emit("storage.write", {
2851
2909
  sessionId: "~memory~",
@@ -2872,6 +2930,7 @@ ${body.trim()}`);
2872
2930
  }
2873
2931
  this.events?.emit("memory.cleared", { scope });
2874
2932
  await this.mirrorBackup(scope);
2933
+ this._trackedByteSizes[scope] = 0;
2875
2934
  });
2876
2935
  return;
2877
2936
  }
@@ -3763,6 +3822,15 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
3763
3822
  }
3764
3823
  return out;
3765
3824
  }
3825
+ function samePath(a, b) {
3826
+ let ra = path2.resolve(a);
3827
+ let rb = path2.resolve(b);
3828
+ if (process.platform === "win32" || process.platform === "darwin") {
3829
+ ra = ra.toLowerCase();
3830
+ rb = rb.toLowerCase();
3831
+ }
3832
+ return ra === rb;
3833
+ }
3766
3834
  function deepMerge2(base, patch) {
3767
3835
  const opts = { arrayMode: "concat-primitives" };
3768
3836
  if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
@@ -3791,10 +3859,11 @@ var DefaultConfigLoader = class {
3791
3859
  }
3792
3860
  async load(opts = {}) {
3793
3861
  let cfg = { ...BEHAVIOR_DEFAULTS };
3862
+ const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
3794
3863
  const [global, local, inProject] = await Promise.all([
3795
3864
  this.readJson(this.paths.globalConfig),
3796
3865
  this.readJson(this.paths.projectLocalConfig),
3797
- this.readJson(this.paths.inProjectConfig)
3866
+ inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
3798
3867
  ]);
3799
3868
  cfg = deepMerge2(cfg, global);
3800
3869
  cfg = deepMerge2(cfg, local);
@@ -6077,6 +6146,7 @@ var AgentStatusTracker = class {
6077
6146
  leaderCtxPct;
6078
6147
  leaderModel;
6079
6148
  leaderPartialText = "";
6149
+ leaderStartedAt;
6080
6150
  unsubscribers = [];
6081
6151
  onUpdate;
6082
6152
  sweepTimer = null;
@@ -6093,7 +6163,11 @@ var AgentStatusTracker = class {
6093
6163
  }
6094
6164
  start() {
6095
6165
  this.unsubscribers.push(
6096
- this.events.onPattern("agent.run.started", () => {
6166
+ this.events.onPattern("agent.run.started", (_event, payload) => {
6167
+ const p = payload;
6168
+ this.markLeaderStarted(p?.at);
6169
+ this.captureLeaderContext(p?.ctx);
6170
+ if (p?.model) this.leaderModel = p.model;
6097
6171
  this.leaderStatus = "running";
6098
6172
  this.leaderIterations++;
6099
6173
  this.flush();
@@ -6101,25 +6175,36 @@ var AgentStatusTracker = class {
6101
6175
  );
6102
6176
  this.unsubscribers.push(
6103
6177
  this.events.onPattern("iteration.started", (_e, payload) => {
6104
- const ctx = payload?.ctx;
6105
- if (!ctx) return;
6106
- if (ctx.model) this.leaderModel = ctx.model;
6107
- if (typeof ctx.tokenCount === "number" && typeof ctx.maxContext === "number" && ctx.maxContext > 0) {
6108
- this.leaderCtxPct = Math.round(ctx.tokenCount / ctx.maxContext * 100);
6178
+ const p = payload;
6179
+ const ctx = p?.ctx;
6180
+ this.markLeaderStarted();
6181
+ this.leaderStatus = "running";
6182
+ if (typeof p?.index === "number") {
6183
+ this.leaderIterations = Math.max(this.leaderIterations, p.index + 1);
6109
6184
  }
6185
+ if (!ctx) {
6186
+ this.flush();
6187
+ return;
6188
+ }
6189
+ this.captureLeaderContext(ctx);
6110
6190
  this.flush();
6111
6191
  })
6112
6192
  );
6113
6193
  this.unsubscribers.push(
6114
- this.events.onPattern("agent.run.completed", () => {
6115
- this.leaderStatus = "idle";
6194
+ this.events.onPattern("agent.run.completed", (_event, payload) => {
6195
+ const p = payload;
6196
+ this.captureLeaderContext(p?.ctx);
6197
+ this.leaderStatus = p?.status === "failed" ? "error" : "idle";
6116
6198
  this.leaderCurrentTool = void 0;
6117
6199
  this.leaderPartialText = "";
6200
+ if (this.leaderStatus === "idle") this.leaderStartedAt = void 0;
6118
6201
  this.flush();
6119
6202
  })
6120
6203
  );
6121
6204
  this.unsubscribers.push(
6122
- this.events.onPattern("agent.run.error", () => {
6205
+ this.events.onPattern("agent.run.error", (_event, payload) => {
6206
+ const p = payload;
6207
+ this.captureLeaderContext(p?.ctx);
6123
6208
  this.leaderStatus = "error";
6124
6209
  this.leaderCurrentTool = void 0;
6125
6210
  this.leaderPartialText = "";
@@ -6130,6 +6215,7 @@ var AgentStatusTracker = class {
6130
6215
  this.events.onPattern("tool.started", (_event, payload) => {
6131
6216
  const p = payload;
6132
6217
  if (p?.name) {
6218
+ this.markLeaderStarted();
6133
6219
  this.leaderCurrentTool = p.name;
6134
6220
  this.leaderToolCalls++;
6135
6221
  }
@@ -6145,12 +6231,14 @@ var AgentStatusTracker = class {
6145
6231
  );
6146
6232
  this.unsubscribers.push(
6147
6233
  this.events.onPattern("brain.ask_human", () => {
6234
+ this.markLeaderStarted();
6148
6235
  this.leaderStatus = "waiting_user";
6149
6236
  this.flush();
6150
6237
  })
6151
6238
  );
6152
6239
  this.unsubscribers.push(
6153
6240
  this.events.onPattern("llm.stream_started", () => {
6241
+ this.markLeaderStarted();
6154
6242
  this.leaderStatus = "streaming";
6155
6243
  this.leaderPartialText = "";
6156
6244
  this.flush();
@@ -6158,14 +6246,42 @@ var AgentStatusTracker = class {
6158
6246
  );
6159
6247
  this.unsubscribers.push(
6160
6248
  this.events.onPattern("provider.text_delta", (_e, payload) => {
6161
- const text = payload?.text;
6249
+ const p = payload;
6250
+ const text = p?.text;
6162
6251
  if (!text) return;
6252
+ this.markLeaderStarted();
6253
+ this.captureLeaderContext(p?.ctx);
6163
6254
  this.leaderStatus = "streaming";
6164
6255
  const next = this.leaderPartialText + text;
6165
6256
  this.leaderPartialText = next.length > PARTIAL_TEXT_CAP ? next.slice(next.length - PARTIAL_TEXT_CAP) : next;
6166
6257
  this.schedulePartialFlush();
6167
6258
  })
6168
6259
  );
6260
+ this.unsubscribers.push(
6261
+ this.events.onPattern("provider.response", (_e, payload) => {
6262
+ const p = payload;
6263
+ this.captureLeaderContext(p?.ctx);
6264
+ this.flush();
6265
+ })
6266
+ );
6267
+ this.unsubscribers.push(
6268
+ this.events.onPattern("provider.fallback", (_e, payload) => {
6269
+ const p = payload;
6270
+ if (p?.to?.model) {
6271
+ this.leaderModel = p.to.providerId ? `${p.to.providerId}/${p.to.model}` : p.to.model;
6272
+ this.flush();
6273
+ }
6274
+ })
6275
+ );
6276
+ this.unsubscribers.push(
6277
+ this.events.onPattern("ctx.pct", (_e, payload) => {
6278
+ const p = payload;
6279
+ if (typeof p?.load === "number" && Number.isFinite(p.load)) {
6280
+ this.leaderCtxPct = Math.round(p.load * 100);
6281
+ this.flush();
6282
+ }
6283
+ })
6284
+ );
6169
6285
  this.unsubscribers.push(
6170
6286
  this.events.onPattern("token.accounted", (_e, payload) => {
6171
6287
  const p = payload;
@@ -6179,7 +6295,8 @@ var AgentStatusTracker = class {
6179
6295
  const touch = (id) => {
6180
6296
  let entry = this.agents.get(id);
6181
6297
  if (!entry) {
6182
- entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() };
6298
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6299
+ entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, startedAt: now, lastActivityAt: now };
6183
6300
  this.agents.set(id, entry);
6184
6301
  }
6185
6302
  entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -6192,6 +6309,7 @@ var AgentStatusTracker = class {
6192
6309
  const entry = touch(p.subagentId);
6193
6310
  entry.name = p.name?.trim() || entry.name;
6194
6311
  if (p.model) entry.model = p.model;
6312
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
6195
6313
  entry.status = "running";
6196
6314
  this.flush();
6197
6315
  })
@@ -6211,6 +6329,7 @@ var AgentStatusTracker = class {
6211
6329
  if (!p?.subagentId) return;
6212
6330
  const entry = touch(p.subagentId);
6213
6331
  entry.status = "running";
6332
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
6214
6333
  entry.iterations++;
6215
6334
  this.flush();
6216
6335
  })
@@ -6221,6 +6340,7 @@ var AgentStatusTracker = class {
6221
6340
  if (!p?.subagentId) return;
6222
6341
  const entry = touch(p.subagentId);
6223
6342
  entry.status = "running";
6343
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
6224
6344
  entry.currentTool = p.name;
6225
6345
  entry.toolCalls++;
6226
6346
  this.flush();
@@ -6232,6 +6352,7 @@ var AgentStatusTracker = class {
6232
6352
  if (!p?.subagentId) return;
6233
6353
  const entry = touch(p.subagentId);
6234
6354
  entry.status = "running";
6355
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
6235
6356
  if (typeof p.iteration === "number") entry.iterations = p.iteration;
6236
6357
  if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
6237
6358
  if (typeof p.costUsd === "number") entry.costUsd = p.costUsd;
@@ -6319,6 +6440,7 @@ var AgentStatusTracker = class {
6319
6440
  const leaderEntry = {
6320
6441
  id: "leader",
6321
6442
  name: this.leaderName,
6443
+ startedAt: this.leaderStartedAt,
6322
6444
  status: this.leaderStatus,
6323
6445
  currentTool: this.leaderCurrentTool,
6324
6446
  iterations: this.leaderIterations,
@@ -6344,6 +6466,23 @@ var AgentStatusTracker = class {
6344
6466
  }
6345
6467
  }).catch(() => void 0);
6346
6468
  }
6469
+ markLeaderStarted(startedAt) {
6470
+ if (this.leaderStartedAt && (this.leaderStatus === "running" || this.leaderStatus === "streaming" || this.leaderStatus === "waiting_user")) {
6471
+ return;
6472
+ }
6473
+ this.leaderStartedAt = startedAt ?? (/* @__PURE__ */ new Date()).toISOString();
6474
+ }
6475
+ captureLeaderContext(ctx) {
6476
+ if (typeof ctx !== "object" || ctx === null) return;
6477
+ const c = ctx;
6478
+ if (typeof c.model === "string" && c.model.length > 0) this.leaderModel = c.model;
6479
+ const metaLimit = c.meta?.["effectiveMaxContext"];
6480
+ const providerMax = c.provider?.capabilities?.maxContext;
6481
+ const maxContext = typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : void 0;
6482
+ if (typeof c.lastRequestTokens === "number" && c.lastRequestTokens > 0 && maxContext !== void 0) {
6483
+ this.leaderCtxPct = Math.round(c.lastRequestTokens / maxContext * 100);
6484
+ }
6485
+ }
6347
6486
  };
6348
6487
  var INSTANCES_FILE = "webui-instances.json";
6349
6488
  var DISCOVERY_TTL_MS = 2500;