@wrongstack/core 0.270.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 (76) hide show
  1. package/dist/{agent-bridge-PcHQl_UQ.d.ts → agent-bridge-DFQYEeXf.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-BZa_IEcd.d.ts} +7 -7
  3. package/dist/{brain-BYcK__Ym.d.ts → brain-etbcbRwV.d.ts} +95 -2
  4. package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
  5. package/dist/{config-C_ae2k86.d.ts → config-rRS8yorV.d.ts} +71 -2
  6. package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
  7. package/dist/coordination/index.d.ts +181 -17
  8. package/dist/coordination/index.js +1018 -166
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +804 -222
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +23 -18
  14. package/dist/execution/index.js +136 -41
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +36 -6
  17. package/dist/execution/prompt-enhancer.js +35 -9
  18. package/dist/execution/prompt-enhancer.js.map +1 -1
  19. package/dist/extension/index.d.ts +6 -6
  20. package/dist/{global-mailbox-Bvrz1P3f.d.ts → global-mailbox-DJ4EoRr0.d.ts} +145 -5
  21. package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-hM8BH7TK.d.ts} +9 -9
  22. package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CWlbT0TO.d.ts} +1 -1
  23. package/dist/hq/index.d.ts +95 -6
  24. package/dist/hq/index.js +628 -50
  25. package/dist/hq/index.js.map +1 -1
  26. package/dist/{index-whDfTANu.d.ts → index-2Lhk5v0o.d.ts} +2 -2
  27. package/dist/{index-W4VJCzHa.d.ts → index-DWm_PE9L.d.ts} +5 -5
  28. package/dist/{index-CZQ6Pwbs.d.ts → index-DqW4o62H.d.ts} +8 -8
  29. package/dist/index.d.ts +96 -56
  30. package/dist/index.js +2464 -519
  31. package/dist/index.js.map +1 -1
  32. package/dist/infrastructure/index.d.ts +6 -6
  33. package/dist/infrastructure/index.js +5 -3
  34. package/dist/infrastructure/index.js.map +1 -1
  35. package/dist/kernel/index.d.ts +9 -9
  36. package/dist/kernel/index.js.map +1 -1
  37. package/dist/{mcp-servers-DJdZiRcv.d.ts → mcp-servers-BpWHTKlE.d.ts} +3 -3
  38. package/dist/models/index.d.ts +5 -5
  39. package/dist/models/index.js +28 -5
  40. package/dist/models/index.js.map +1 -1
  41. package/dist/{models-registry-C3a-2-Yd.d.ts → models-registry-CXQFUn5t.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-jyimfo7D.d.ts} +1 -1
  43. package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-DOGQcvrY.d.ts} +6 -6
  44. package/dist/observability/index.d.ts +2 -2
  45. package/dist/{parallel-eternal-engine-D9y5Pkcc.d.ts → parallel-eternal-engine-rItJBYp9.d.ts} +9 -9
  46. package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DrpF5MGK.d.ts} +3 -3
  47. package/dist/{permission-CvYQNUqZ.d.ts → permission-CC7XFYWG.d.ts} +1 -1
  48. package/dist/{permission-policy-D5Ss8j4B.d.ts → permission-policy-cYR4RJmw.d.ts} +2 -2
  49. package/dist/{pipeline-l_zzFRh3.d.ts → pipeline-Ckkn3AOA.d.ts} +2 -2
  50. package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-BvHw5Znw.d.ts} +33 -9
  51. package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-nZqnCeaR.d.ts} +3 -3
  52. package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-zVOn1p67.d.ts} +3 -3
  53. package/dist/{retry-policy-CtFhfwa8.d.ts → retry-policy-BV7nzeAd.d.ts} +1 -1
  54. package/dist/sdd/index.d.ts +8 -8
  55. package/dist/sdd/index.js +2 -0
  56. package/dist/sdd/index.js.map +1 -1
  57. package/dist/{secret-vault-BLsVmTIK.d.ts → secret-vault-eMBKfheR.d.ts} +9 -1
  58. package/dist/security/index.d.ts +5 -5
  59. package/dist/security/index.js +137 -10
  60. package/dist/security/index.js.map +1 -1
  61. package/dist/{selector-CXl2_y9W.d.ts → selector-C4ORTOid.d.ts} +1 -1
  62. package/dist/{session-event-bridge-Ccud20CC.d.ts → session-event-bridge-CeNpUL9w.d.ts} +1 -1
  63. package/dist/{session-reader-ZeXQmsmE.d.ts → session-reader-BepLSnGL.d.ts} +1 -1
  64. package/dist/storage/index.d.ts +50 -13
  65. package/dist/storage/index.js +620 -220
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +2 -2
  68. package/dist/tools/index.js +9 -2
  69. package/dist/tools/index.js.map +1 -1
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +202 -41
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/index.d.ts +17 -4
  74. package/dist/utils/index.js +48 -9
  75. package/dist/utils/index.js.map +1 -1
  76. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  import * as crypto2 from 'crypto';
2
- import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
2
+ import { randomBytes, createCipheriv, createDecipheriv, randomUUID, scryptSync, createHash } from 'crypto';
3
3
  import * as fsp2 from 'fs/promises';
4
4
  import * as path4 from 'path';
5
5
  import { isAbsolute, resolve } from 'path';
6
6
  import * as fs4 from 'fs';
7
+ import { createReadStream } from 'fs';
8
+ import { createInterface } from 'readline';
7
9
  import * as os from 'os';
8
10
  import { hostname } from 'os';
9
11
  import { execFile } from 'child_process';
@@ -105,7 +107,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
105
107
  if (Date.now() - started >= timeoutMs) {
106
108
  throw new Error(`Timed out waiting for file lock: ${targetPath}`);
107
109
  }
108
- await new Promise((resolve6) => setTimeout(resolve6, 25));
110
+ await new Promise((resolve7) => setTimeout(resolve7, 25));
109
111
  }
110
112
  }
111
113
  try {
@@ -138,7 +140,7 @@ async function renameWithRetry(from, to) {
138
140
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
139
141
  throw err;
140
142
  }
141
- await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
143
+ await new Promise((resolve7) => setTimeout(resolve7, delays[i]));
142
144
  }
143
145
  }
144
146
  throw lastErr;
@@ -706,6 +708,7 @@ function escapeRegex(s) {
706
708
  }
707
709
  var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
708
710
  var CACHE_MAX_SIZE = 2e3;
711
+ var NEVER_MATCH = /[^\s\S]/;
709
712
  function getCachedGlob(pattern) {
710
713
  const cached = COMPILED_GLOB_CACHE.get(pattern);
711
714
  if (cached) return cached;
@@ -715,7 +718,12 @@ function getCachedGlob(pattern) {
715
718
  COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
716
719
  }
717
720
  }
718
- const re = compileGlob(pattern);
721
+ let re;
722
+ try {
723
+ re = compileGlob(pattern);
724
+ } catch {
725
+ re = NEVER_MATCH;
726
+ }
719
727
  COMPILED_GLOB_CACHE.set(pattern, re);
720
728
  return re;
721
729
  }
@@ -874,11 +882,11 @@ function validateAgainstSchema(value, schema) {
874
882
  walk(value, schema, "", errors);
875
883
  return { ok: errors.length === 0, errors };
876
884
  }
877
- function walk(value, schema, path21, errors) {
885
+ function walk(value, schema, path22, errors) {
878
886
  if (schema.enum !== void 0) {
879
887
  if (!schema.enum.some((e) => deepEqual(e, value))) {
880
888
  errors.push({
881
- path: path21 || "<root>",
889
+ path: path22 || "<root>",
882
890
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
883
891
  });
884
892
  return;
@@ -887,7 +895,7 @@ function walk(value, schema, path21, errors) {
887
895
  if (typeof schema.type === "string") {
888
896
  if (!checkType(value, schema.type)) {
889
897
  errors.push({
890
- path: path21 || "<root>",
898
+ path: path22 || "<root>",
891
899
  message: `expected ${schema.type}, got ${describeType(value)}`
892
900
  });
893
901
  return;
@@ -897,20 +905,20 @@ function walk(value, schema, path21, errors) {
897
905
  const obj = value;
898
906
  for (const req of schema.required ?? []) {
899
907
  if (!(req in obj)) {
900
- errors.push({ path: joinPath(path21, req), message: "required property missing" });
908
+ errors.push({ path: joinPath(path22, req), message: "required property missing" });
901
909
  }
902
910
  }
903
911
  if (schema.properties) {
904
912
  for (const [key, subSchema] of Object.entries(schema.properties)) {
905
913
  if (key in obj) {
906
- walk(obj[key], subSchema, joinPath(path21, key), errors);
914
+ walk(obj[key], subSchema, joinPath(path22, key), errors);
907
915
  }
908
916
  }
909
917
  }
910
918
  }
911
919
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
912
920
  for (let i = 0; i < value.length; i++) {
913
- walk(value[i], schema.items, `${path21}[${i}]`, errors);
921
+ walk(value[i], schema.items, `${path22}[${i}]`, errors);
914
922
  }
915
923
  }
916
924
  }
@@ -1080,7 +1088,7 @@ function safeParse(input, maxBytes = 5e6) {
1080
1088
 
1081
1089
  // src/utils/sleep.ts
1082
1090
  function sleep(ms) {
1083
- return new Promise((resolve6) => setTimeout(resolve6, ms));
1091
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
1084
1092
  }
1085
1093
 
1086
1094
  // src/utils/string.ts
@@ -1218,18 +1226,20 @@ var MODEL_FAMILY_RATIO = {
1218
1226
  deepseek: 3.5
1219
1227
  };
1220
1228
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
1229
+ var _estimateCacheOrder = [];
1221
1230
  var ESTIMATE_CACHE_MAX_SIZE = 5e4;
1222
1231
  function getCachedEstimate(key, compute) {
1223
1232
  const existing = ESTIMATE_CACHE.get(key);
1224
1233
  if (existing !== void 0) return existing;
1225
1234
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1226
- for (const k of ESTIMATE_CACHE.keys()) {
1227
- if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
1228
- ESTIMATE_CACHE.delete(k);
1235
+ while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
1236
+ const oldest = _estimateCacheOrder.shift();
1237
+ if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
1229
1238
  }
1230
1239
  }
1231
1240
  const estimate = compute(key);
1232
1241
  ESTIMATE_CACHE.set(key, estimate);
1242
+ _estimateCacheOrder.push(key);
1233
1243
  return estimate;
1234
1244
  }
1235
1245
  function estimateToolInputTokens(input) {
@@ -2073,8 +2083,6 @@ function resolveWstackPaths(opts) {
2073
2083
  projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
2074
2084
  };
2075
2085
  }
2076
-
2077
- // src/storage/session-store.ts
2078
2086
  function sanitizeModel(model) {
2079
2087
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
2080
2088
  }
@@ -2085,6 +2093,8 @@ function generateSessionId(startedAt, model) {
2085
2093
  const modelPart = model ? `_${sanitizeModel(model)}` : "";
2086
2094
  return `${date}/${time}Z${modelPart}_${suffix}`;
2087
2095
  }
2096
+
2097
+ // src/storage/session-store.ts
2088
2098
  var DefaultSessionStore = class _DefaultSessionStore {
2089
2099
  dir;
2090
2100
  events;
@@ -2102,6 +2112,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2102
2112
  _loadCache = /* @__PURE__ */ new Map();
2103
2113
  _indexCache = null;
2104
2114
  static LOAD_CACHE_MAX_ENTRIES = 50;
2115
+ static LIST_SCAN_CONCURRENCY = 32;
2105
2116
  constructor(opts) {
2106
2117
  this.dir = opts.dir;
2107
2118
  this.events = opts.events;
@@ -2118,7 +2129,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2118
2129
  this._loadCache.clear();
2119
2130
  }
2120
2131
  }
2121
- // ── Storage event helpers ───────────────────────────────────────────────────
2132
+ // ── Storage event helpers ───────────────────────────────────────────────────
2122
2133
  emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
2123
2134
  this.events?.emit("storage.read", {
2124
2135
  sessionId,
@@ -2233,7 +2244,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2233
2244
  this.events,
2234
2245
  {
2235
2246
  resumed: true,
2236
- // Shard directory (sessions/<date>/) must match create() so the
2247
+ // Shard directory (sessions/<date>/) — must match create() so the
2237
2248
  // .summary.json sidecar lands next to the JSONL instead of the
2238
2249
  // sessions root (where summaryFor() would never find it).
2239
2250
  dir: path4.dirname(file),
@@ -2274,19 +2285,93 @@ var DefaultSessionStore = class _DefaultSessionStore {
2274
2285
  const raw = await fsp2.readFile(file, "utf8");
2275
2286
  const lines = raw.split("\n").filter((l) => l.trim());
2276
2287
  const events = [];
2288
+ let sessionStartEvent;
2289
+ let sessionEndEvent;
2290
+ let sessionModel;
2291
+ let sessionProvider;
2292
+ let sessionPendingToolUses;
2293
+ const messages = [];
2294
+ const openToolUses = /* @__PURE__ */ new Set();
2295
+ let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
2277
2296
  for (const line of lines) {
2278
2297
  try {
2279
2298
  const parsed = JSON.parse(line);
2280
2299
  if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
2281
- events.push(parsed);
2300
+ const ev = parsed;
2301
+ events.push(ev);
2302
+ if (ev.type === "session_start" && !sessionStartEvent) {
2303
+ sessionStartEvent = ev;
2304
+ sessionModel = ev.model;
2305
+ sessionProvider = ev.provider;
2306
+ }
2307
+ if (ev.type === "session_end") {
2308
+ sessionEndEvent = ev;
2309
+ sessionPendingToolUses = ev.pendingToolUses;
2310
+ }
2311
+ if (ev.type === "user_input") {
2312
+ openToolUses.clear();
2313
+ messages.push({ role: "user", content: ev.content, ts: ev.ts });
2314
+ } else if (ev.type === "llm_response") {
2315
+ messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
2316
+ for (const b of ev.content) {
2317
+ if (b.type === "tool_use") openToolUses.add(b.id);
2318
+ }
2319
+ usage = {
2320
+ input: usage.input + (ev.usage.input ?? 0),
2321
+ output: usage.output + (ev.usage.output ?? 0),
2322
+ cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
2323
+ cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
2324
+ };
2325
+ } else if (ev.type === "tool_result") {
2326
+ if (!openToolUses.has(ev.id)) {
2327
+ this.events?.emit("session.damaged", {
2328
+ sessionId: id,
2329
+ detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
2330
+ });
2331
+ continue;
2332
+ }
2333
+ openToolUses.delete(ev.id);
2334
+ const resultBlock = {
2335
+ type: "tool_result",
2336
+ tool_use_id: ev.id,
2337
+ content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
2338
+ is_error: ev.isError
2339
+ };
2340
+ const last = messages[messages.length - 1];
2341
+ const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
2342
+ if (lastIsToolResultUser && Array.isArray(last.content)) {
2343
+ last.content.push(resultBlock);
2344
+ } else {
2345
+ messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
2346
+ }
2347
+ }
2282
2348
  }
2283
2349
  } catch {
2284
2350
  }
2285
2351
  }
2286
- const meta = this.metaFromEvents(id, events);
2287
- const { messages, usage } = this.replay(events, id);
2352
+ if (openToolUses.size > 0) {
2353
+ this.events?.emit("session.damaged", {
2354
+ sessionId: id,
2355
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
2356
+ });
2357
+ }
2358
+ const repaired = repairToolUseAdjacency(messages);
2359
+ if (repaired.report.changed) {
2360
+ this.events?.emit("session.damaged", {
2361
+ sessionId: id,
2362
+ detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
2363
+ });
2364
+ }
2365
+ const meta = {
2366
+ id,
2367
+ startedAt: sessionStartEvent?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
2368
+ endedAt: sessionEndEvent?.ts,
2369
+ model: sessionModel,
2370
+ provider: sessionProvider,
2371
+ pendingToolUses: sessionPendingToolUses
2372
+ };
2288
2373
  const toolCallEnds = extractToolCallEnds(events);
2289
- const data = { metadata: meta, events, messages, usage, toolCallEnds };
2374
+ const data = { metadata: meta, events, messages: repaired.messages, usage, toolCallEnds };
2290
2375
  if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
2291
2376
  const oldest = this._loadCache.keys().next().value;
2292
2377
  if (oldest !== void 0) {
@@ -2324,20 +2409,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
2324
2409
  });
2325
2410
  return indexed.slice(0, limit);
2326
2411
  }
2327
- const ids = await this.collectSessionIds(this.dir);
2328
- const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
2329
- const out = sessions.filter((s) => s !== null);
2330
- out.sort((a, b) => {
2331
- if (a.startedAt < b.startedAt) return 1;
2332
- if (a.startedAt > b.startedAt) return -1;
2333
- return a.id.localeCompare(b.id);
2334
- });
2335
- return out.slice(0, limit);
2412
+ return await this.listFromDirectoryScan(limit);
2336
2413
  } catch {
2337
2414
  return [];
2338
2415
  }
2339
2416
  }
2340
- // ── Session index (_index.jsonl) ─────────────────────────────────────────
2417
+ // ── Session index (_index.jsonl) ─────────────────────────────────────────
2341
2418
  //
2342
2419
  // One JSON line per closed session, appended atomically on close().
2343
2420
  // When a session is deleted, a tombstone {action:"delete",id:"..."} is
@@ -2457,43 +2534,102 @@ var DefaultSessionStore = class _DefaultSessionStore {
2457
2534
  this._indexCache = null;
2458
2535
  return valid.length;
2459
2536
  }
2537
+ async listFromDirectoryScan(limit) {
2538
+ const refs = await this.collectSessionFiles(this.dir);
2539
+ const candidates = await mapWithConcurrency(
2540
+ refs,
2541
+ _DefaultSessionStore.LIST_SCAN_CONCURRENCY,
2542
+ async (ref) => {
2543
+ const manifest = await this.readSummaryManifest(ref.id);
2544
+ if (manifest) return { summary: manifest, needsBackfill: false };
2545
+ const summary = await this.summaryHeaderFor(ref);
2546
+ return summary ? { summary, needsBackfill: true } : null;
2547
+ }
2548
+ );
2549
+ const out = candidates.filter((s) => s !== null);
2550
+ out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
2551
+ const selected = out.slice(0, limit);
2552
+ const summaries = await mapWithConcurrency(
2553
+ selected,
2554
+ Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
2555
+ async (candidate) => {
2556
+ if (!candidate.needsBackfill) return candidate.summary;
2557
+ return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
2558
+ }
2559
+ );
2560
+ return summaries.filter((s) => s !== null);
2561
+ }
2562
+ async collectSessionFiles(dir, prefix = "", depth = 0) {
2563
+ let entries;
2564
+ try {
2565
+ entries = await fsp2.readdir(dir, { withFileTypes: true });
2566
+ } catch {
2567
+ return [];
2568
+ }
2569
+ const dirEntries = [];
2570
+ const files = [];
2571
+ for (const entry of entries) {
2572
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
2573
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
2574
+ continue;
2575
+ if (entry.isDirectory()) {
2576
+ dirEntries.push(entry);
2577
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
2578
+ if (entry.name === "_index.jsonl") continue;
2579
+ const base = entry.name.replace(/\.jsonl$/, "");
2580
+ const id = prefix ? `${prefix}/${base}` : base;
2581
+ files.push({ id, filePath: path4.join(dir, entry.name) });
2582
+ }
2583
+ }
2584
+ const childFileArrays = await Promise.all(
2585
+ dirEntries.map((entry) => {
2586
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
2587
+ return this.collectSessionFiles(path4.join(dir, entry.name), childPrefix, depth + 1);
2588
+ })
2589
+ );
2590
+ return [...childFileArrays.flat(), ...files];
2591
+ }
2460
2592
  /** Recursively collect session IDs from date-shard subdirectories.
2461
- * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_").
2593
+ * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
2462
2594
  * Skips `.jsonl`/`.summary.json` root files, dot-files, and
2463
2595
  * sub-directories that belong to fleet/subagent sessions. */
2464
2596
  async collectSessionIds(dir, prefix = "", depth = 0) {
2465
- const ids = [];
2466
2597
  let entries;
2467
2598
  try {
2468
2599
  entries = await fsp2.readdir(dir, { withFileTypes: true });
2469
2600
  } catch {
2470
- return ids;
2601
+ return [];
2471
2602
  }
2603
+ const dirEntries = [];
2604
+ const fileIds = [];
2472
2605
  for (const entry of entries) {
2473
2606
  if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
2474
2607
  if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
2475
2608
  continue;
2476
2609
  if (entry.isDirectory()) {
2477
- const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
2478
- ids.push(...await this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1));
2610
+ dirEntries.push(entry);
2479
2611
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
2480
2612
  if (entry.name === "_index.jsonl") continue;
2481
2613
  const base = entry.name.replace(/\.jsonl$/, "");
2482
- ids.push(prefix ? `${prefix}/${base}` : base);
2614
+ fileIds.push(prefix ? `${prefix}/${base}` : base);
2483
2615
  }
2484
2616
  }
2485
- return ids;
2617
+ const childIdArrays = await Promise.all(
2618
+ dirEntries.map((entry) => {
2619
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
2620
+ return this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1);
2621
+ })
2622
+ );
2623
+ return [...childIdArrays.flat(), ...fileIds];
2486
2624
  }
2487
2625
  async summaryFor(id) {
2488
2626
  const manifest = this.sessionPath(id, ".summary.json");
2489
2627
  const t0 = Date.now();
2490
2628
  let outcome = "success";
2491
2629
  let errorMsg;
2630
+ const fromManifest = await this.readSummaryManifest(id, t0);
2631
+ if (fromManifest) return fromManifest;
2492
2632
  try {
2493
- const raw = await fsp2.readFile(manifest, "utf8");
2494
- this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
2495
- return JSON.parse(raw);
2496
- } catch {
2497
2633
  const full = this.sessionPath(id, ".jsonl");
2498
2634
  const stat6 = await fsp2.stat(full);
2499
2635
  const summary = await this.summarize(id, stat6.mtime.toISOString());
@@ -2509,9 +2645,81 @@ var DefaultSessionStore = class _DefaultSessionStore {
2509
2645
  }));
2510
2646
  });
2511
2647
  outcome = "failure";
2512
- errorMsg = "summary fallback \u2014 manifest rebuilt";
2648
+ errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
2513
2649
  this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
2514
2650
  return summary;
2651
+ } catch (err) {
2652
+ outcome = "failure";
2653
+ errorMsg = toErrorMessage(err);
2654
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
2655
+ return {
2656
+ id,
2657
+ title: "(damaged)",
2658
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2659
+ model: "unknown",
2660
+ provider: "unknown",
2661
+ tokenTotal: 0
2662
+ };
2663
+ }
2664
+ }
2665
+ async readSummaryManifest(id, startTime = Date.now()) {
2666
+ const manifest = this.sessionPath(id, ".summary.json");
2667
+ try {
2668
+ const raw = await fsp2.readFile(manifest, "utf8");
2669
+ this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
2670
+ return JSON.parse(raw);
2671
+ } catch {
2672
+ return null;
2673
+ }
2674
+ }
2675
+ async summaryHeaderFor(ref) {
2676
+ let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
2677
+ try {
2678
+ const stat6 = await fsp2.stat(ref.filePath);
2679
+ if (!stat6.isFile()) {
2680
+ return {
2681
+ id: ref.id,
2682
+ title: "(damaged)",
2683
+ startedAt: stat6.mtime.toISOString(),
2684
+ model: "unknown",
2685
+ provider: "unknown",
2686
+ tokenTotal: 0
2687
+ };
2688
+ }
2689
+ mtime = stat6.mtime.toISOString();
2690
+ } catch {
2691
+ return null;
2692
+ }
2693
+ try {
2694
+ for await (const event of this.iterSessionEvents(ref.filePath)) {
2695
+ if (event.type === "session_start") {
2696
+ return {
2697
+ id: ref.id,
2698
+ title: "(empty session)",
2699
+ startedAt: event.ts,
2700
+ model: event.model ?? "unknown",
2701
+ provider: event.provider ?? "unknown",
2702
+ tokenTotal: 0
2703
+ };
2704
+ }
2705
+ }
2706
+ return {
2707
+ id: ref.id,
2708
+ title: "(empty session)",
2709
+ startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2710
+ model: "unknown",
2711
+ provider: "unknown",
2712
+ tokenTotal: 0
2713
+ };
2714
+ } catch {
2715
+ return {
2716
+ id: ref.id,
2717
+ title: "(damaged)",
2718
+ startedAt: mtime,
2719
+ model: "unknown",
2720
+ provider: "unknown",
2721
+ tokenTotal: 0
2722
+ };
2515
2723
  }
2516
2724
  }
2517
2725
  /**
@@ -2636,39 +2844,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
2636
2844
  }
2637
2845
  async summarize(id, mtime) {
2638
2846
  try {
2639
- const data = await this.load(id);
2640
- const firstUser = data.events.find((e) => e.type === "user_input");
2641
- const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
2847
+ const file = this.sessionPath(id, ".jsonl");
2848
+ let title = "(empty session)";
2849
+ let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
2850
+ let endedAt;
2851
+ let model = "unknown";
2852
+ let provider = "unknown";
2853
+ let tokenIn = 0;
2854
+ let tokenOut = 0;
2642
2855
  let iterationCount = 0;
2643
2856
  let toolCallCount = 0;
2644
2857
  let toolErrorCount = 0;
2645
2858
  let fileChangeCount = 0;
2646
2859
  const toolBreakdown = {};
2647
2860
  let outcome;
2648
- const lastEvent = data.events[data.events.length - 1];
2649
- for (const e of data.events) {
2650
- if (e.type === "in_flight_start") iterationCount++;
2861
+ let lastEventType;
2862
+ let hasError = false;
2863
+ let sawStart = false;
2864
+ for await (const e of this.iterSessionEvents(file)) {
2865
+ lastEventType = e.type;
2866
+ if (e.type === "session_start") {
2867
+ if (!sawStart) {
2868
+ sawStart = true;
2869
+ startedAt = e.ts;
2870
+ model = e.model ?? "unknown";
2871
+ provider = e.provider ?? "unknown";
2872
+ }
2873
+ } else if (e.type === "session_end") {
2874
+ endedAt = e.ts;
2875
+ } else if (e.type === "user_input") {
2876
+ if (title === "(empty session)") title = userInputTitle(e.content);
2877
+ } else if (e.type === "llm_response") {
2878
+ tokenIn += e.usage.input ?? 0;
2879
+ tokenOut += e.usage.output ?? 0;
2880
+ } else if (e.type === "in_flight_start") iterationCount++;
2651
2881
  else if (e.type === "tool_call_start") {
2652
2882
  toolCallCount++;
2653
2883
  toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
2654
2884
  } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
2655
2885
  else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
2886
+ else if (e.type === "error" || e.type === "provider_error") hasError = true;
2656
2887
  }
2657
- if (lastEvent?.type === "session_end") {
2888
+ if (lastEventType === "session_end") {
2658
2889
  outcome = "completed";
2659
- } else if (lastEvent?.type === "in_flight_start") {
2890
+ } else if (lastEventType === "in_flight_start") {
2660
2891
  outcome = "aborted";
2661
- } else if (data.events.some((e) => e.type === "error")) {
2892
+ } else if (hasError) {
2662
2893
  outcome = "error";
2663
2894
  }
2664
2895
  return {
2665
2896
  id,
2666
2897
  title,
2667
- startedAt: data.metadata.startedAt,
2668
- endedAt: data.metadata.endedAt,
2669
- model: data.metadata.model ?? "unknown",
2670
- provider: data.metadata.provider ?? "unknown",
2671
- tokenTotal: data.usage.input + data.usage.output,
2898
+ startedAt,
2899
+ endedAt,
2900
+ model,
2901
+ provider,
2902
+ tokenTotal: tokenIn + tokenOut,
2672
2903
  iterationCount: iterationCount > 0 ? iterationCount : void 0,
2673
2904
  toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
2674
2905
  toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
@@ -2687,75 +2918,24 @@ var DefaultSessionStore = class _DefaultSessionStore {
2687
2918
  };
2688
2919
  }
2689
2920
  }
2690
- metaFromEvents(id, events) {
2691
- const start = events.find((e) => e.type === "session_start");
2692
- const end = events.findLast((e) => e.type === "session_end");
2693
- return {
2694
- id,
2695
- startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
2696
- endedAt: end?.ts,
2697
- model: start?.model,
2698
- provider: start?.provider,
2699
- pendingToolUses: end?.pendingToolUses
2700
- };
2701
- }
2702
- replay(events, sessionId = "unknown") {
2703
- const messages = [];
2704
- let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
2705
- const openToolUses = /* @__PURE__ */ new Set();
2706
- for (const e of events) {
2707
- if (e.type === "user_input") {
2708
- openToolUses.clear();
2709
- messages.push({ role: "user", content: e.content, ts: e.ts });
2710
- } else if (e.type === "llm_response") {
2711
- messages.push({ role: "assistant", content: e.content, ts: e.ts });
2712
- for (const b of e.content) {
2713
- if (b.type === "tool_use") openToolUses.add(b.id);
2714
- }
2715
- usage = {
2716
- input: usage.input + (e.usage.input ?? 0),
2717
- output: usage.output + (e.usage.output ?? 0),
2718
- cacheRead: (usage.cacheRead ?? 0) + (e.usage.cacheRead ?? 0),
2719
- cacheWrite: (usage.cacheWrite ?? 0) + (e.usage.cacheWrite ?? 0)
2720
- };
2721
- } else if (e.type === "tool_result") {
2722
- if (!openToolUses.has(e.id)) {
2723
- this.events?.emit("session.damaged", {
2724
- sessionId,
2725
- detail: `Orphan tool_result "${e.id}" has no matching tool_use`
2726
- });
2727
- continue;
2728
- }
2729
- openToolUses.delete(e.id);
2730
- const resultBlock = {
2731
- type: "tool_result",
2732
- tool_use_id: e.id,
2733
- content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
2734
- is_error: e.isError
2735
- };
2736
- const last = messages[messages.length - 1];
2737
- const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
2738
- if (lastIsToolResultUser && Array.isArray(last.content)) {
2739
- last.content.push(resultBlock);
2740
- } else {
2741
- messages.push({ role: "user", content: [resultBlock], ts: e.ts });
2921
+ async *iterSessionEvents(file) {
2922
+ const stream = createReadStream(file, { encoding: "utf8" });
2923
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
2924
+ try {
2925
+ for await (const line of lines) {
2926
+ if (!line.trim()) continue;
2927
+ try {
2928
+ const parsed = JSON.parse(line);
2929
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
2930
+ yield parsed;
2931
+ }
2932
+ } catch {
2742
2933
  }
2743
2934
  }
2935
+ } finally {
2936
+ lines.close();
2937
+ stream.destroy();
2744
2938
  }
2745
- if (openToolUses.size > 0) {
2746
- this.events?.emit("session.damaged", {
2747
- sessionId,
2748
- detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
2749
- });
2750
- }
2751
- const repaired = repairToolUseAdjacency(messages);
2752
- if (repaired.report.changed) {
2753
- this.events?.emit("session.damaged", {
2754
- sessionId,
2755
- detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
2756
- });
2757
- }
2758
- return { messages: repaired.messages, usage };
2759
2939
  }
2760
2940
  };
2761
2941
  function extractToolCallEnds(events) {
@@ -2815,7 +2995,7 @@ var FileSessionWriter = class _FileSessionWriter {
2815
2995
  /**
2816
2996
  * Lazy session_start/session_resumed init, shared by all appenders.
2817
2997
  * A single promise (not a boolean) so a second append racing the first
2818
- * can't push its event into the buffer BEFORE the first append's event
2998
+ * can't push its event into the buffer BEFORE the first append's event —
2819
2999
  * every appender awaits the same init and resumes in FIFO call order.
2820
3000
  */
2821
3001
  initPromise = null;
@@ -2828,24 +3008,24 @@ var FileSessionWriter = class _FileSessionWriter {
2828
3008
  lastAppendWarnAt = 0;
2829
3009
  secretScrubber;
2830
3010
  onCloseCb;
2831
- /** Implements SessionWriter.traceId propagated from ContextInit.traceId. */
3011
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
2832
3012
  traceId;
2833
- // ── Write buffer batches events to reduce per-event disk I/O ─────────
3013
+ // ── Write buffer — batches events to reduce per-event disk I/O ─────────
2834
3014
  //
2835
3015
  // Every append() pushes the scrubbed event into an in-memory buffer instead
2836
3016
  // of calling handle.appendFile() synchronously. The buffer flushes to disk
2837
3017
  // when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.
2838
3018
  // This cuts the number of disk writes by ~95% without changing the on-disk
2839
- // format the JSONL is still one JSON object per line.
3019
+ // format — the JSONL is still one JSON object per line.
2840
3020
  writeBuffer = [];
2841
3021
  flushTimer = null;
2842
3022
  static FLUSH_INTERVAL_MS = 500;
2843
3023
  static FLUSH_SIZE = 50;
2844
- // ── Write serialization ─────────────────────────────────────────────────
3024
+ // ── Write serialization ─────────────────────────────────────────────────
2845
3025
  //
2846
3026
  // All disk writes are funneled through a FIFO promise chain. Without it,
2847
3027
  // a timer-driven flush racing an explicit flush()/close() issues two
2848
- // concurrent appendFile() calls on the shared O_APPEND handle the kernel
3028
+ // concurrent appendFile() calls on the shared O_APPEND handle — the kernel
2849
3029
  // may complete them out of order (chronology breaks) or, for large
2850
3030
  // batches, interleave partial writes (torn JSONL lines). The chain keeps
2851
3031
  // exactly one write in flight; failures don't break the chain.
@@ -2859,7 +3039,7 @@ var FileSessionWriter = class _FileSessionWriter {
2859
3039
  );
2860
3040
  return write;
2861
3041
  }
2862
- // ── Enriched summary tracking ──────────────────────────────────────────
3042
+ // ── Enriched summary tracking ──────────────────────────────────────────
2863
3043
  iterationCount = 0;
2864
3044
  toolCallCount = 0;
2865
3045
  toolErrorCount = 0;
@@ -2951,7 +3131,7 @@ var FileSessionWriter = class _FileSessionWriter {
2951
3131
  * (user_input, llm_response) call this so they survive SIGKILL/crash
2952
3132
  * instead of sitting in the in-memory buffer for up to 500ms.
2953
3133
  *
2954
- * Idempotent cancels any pending timer and writes whatever has
3134
+ * Idempotent — cancels any pending timer and writes whatever has
2955
3135
  * accumulated in the buffer. Safe to call even when the buffer
2956
3136
  * is empty (no-op).
2957
3137
  */
@@ -2974,7 +3154,7 @@ var FileSessionWriter = class _FileSessionWriter {
2974
3154
  /**
2975
3155
  * Flush all buffered events to disk as a single appendFile call.
2976
3156
  * Errors use the same throttled-warning pattern the old per-event
2977
- * append path used one warning every 5s with a suppressed count.
3157
+ * append path used — one warning every 5s with a suppressed count.
2978
3158
  * On failure the buffer is cleared (events are best-effort, same as
2979
3159
  * the old per-event path where a failed write was silently dropped).
2980
3160
  */
@@ -3157,7 +3337,7 @@ var FileSessionWriter = class _FileSessionWriter {
3157
3337
  /**
3158
3338
  * Truncate the session file to the checkpoint with the given promptIndex,
3159
3339
  * removing all events that follow it. Uses a single-pass byte-offset scan
3160
- * so post-checkpoint content is never read or parsed O(1) memory instead
3340
+ * so post-checkpoint content is never read or parsed — O(1) memory instead
3161
3341
  * of O(N) JSON.parse calls over the full file.
3162
3342
  */
3163
3343
  async truncateToCheckpoint(targetPromptIndex) {
@@ -3314,7 +3494,7 @@ var FileSessionWriter = class _FileSessionWriter {
3314
3494
  await fsp2.writeFile(this.filePath, record, "utf8");
3315
3495
  }
3316
3496
  /**
3317
- * Idea #1 write an in-flight marker. The agent loop should call
3497
+ * Idea #1 — write an in-flight marker. The agent loop should call
3318
3498
  * this at the start of each long-running operation; a matching
3319
3499
  * `clearInFlightMarker` follows on clean exit. A stale marker
3320
3500
  * (no end) is what `SessionRecovery.detectStale` looks for.
@@ -3331,9 +3511,9 @@ var FileSessionWriter = class _FileSessionWriter {
3331
3511
  this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
3332
3512
  }
3333
3513
  /**
3334
- * Idea #1 close the in-flight marker. Idempotent in spirit
3514
+ * Idea #1 — close the in-flight marker. Idempotent in spirit
3335
3515
  * (you can call it after a successful iteration even if you
3336
- * didn't open one this round) but the session log records
3516
+ * didn't open one this round) — but the session log records
3337
3517
  * every call so postmortem tooling can see "the agent finished
3338
3518
  * cleanly X times, then died without finishing Y".
3339
3519
  */
@@ -3350,6 +3530,27 @@ function userInputTitle(content) {
3350
3530
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
3351
3531
  return (text || "(non-text input)").slice(0, 60);
3352
3532
  }
3533
+ function compareSessionSummaries(a, b) {
3534
+ if (a.startedAt < b.startedAt) return 1;
3535
+ if (a.startedAt > b.startedAt) return -1;
3536
+ return a.id.localeCompare(b.id);
3537
+ }
3538
+ async function mapWithConcurrency(items, concurrency, fn) {
3539
+ if (items.length === 0) return [];
3540
+ const out = new Array(items.length);
3541
+ let next = 0;
3542
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
3543
+ const workers = Array.from({ length: workerCount }, async () => {
3544
+ for (; ; ) {
3545
+ const idx = next++;
3546
+ if (idx >= items.length) return;
3547
+ const item = items[idx];
3548
+ if (item !== void 0) out[idx] = await fn(item);
3549
+ }
3550
+ });
3551
+ await Promise.all(workers);
3552
+ return out;
3553
+ }
3353
3554
 
3354
3555
  // src/storage/queue-store.ts
3355
3556
  init_atomic_write();
@@ -3937,6 +4138,20 @@ var DefaultMemoryStore = class {
3937
4138
  */
3938
4139
  persistBackup;
3939
4140
  backupDir;
4141
+ /**
4142
+ * Per-scope tracked byte sizes — incremented on `remember()`, decremented on
4143
+ * `forget()`, recalculated after `consolidate()`. Eliminates the redundant
4144
+ * readAll() call that previously checked the file size after every write.
4145
+ */
4146
+ _trackedByteSizes = {};
4147
+ /** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
4148
+ _scoreCache = /* @__PURE__ */ new Map();
4149
+ /**
4150
+ * Per-entry cached lowercase strings — computed once per scoreRelevant() call,
4151
+ * stored here so repeated scoring of the same entries avoids re-computation.
4152
+ * Cleared on every mutation (remember/forget/consolidate/clear).
4153
+ */
4154
+ _cachedLower = null;
3940
4155
  constructor(opts) {
3941
4156
  this.files = {
3942
4157
  "project-agents": opts.paths.inProjectAgentsFile,
@@ -3970,6 +4185,20 @@ var DefaultMemoryStore = class {
3970
4185
  }
3971
4186
  }
3972
4187
  }
4188
+ /**
4189
+ * Recalculate the tracked byte size for a scope by re-reading the file and
4190
+ * summing the serialized byte length of each line. Called after consolidate()
4191
+ * (which modifies the file) to keep the tracker accurate.
4192
+ */
4193
+ async _recalcTrackedByteSize(scope) {
4194
+ const raw = await this.backend.readAll(scope, this.files[scope]);
4195
+ let total = 0;
4196
+ for (const line of raw.split("\n")) {
4197
+ if (line.trim()) total += Buffer.byteLength(line, "utf8");
4198
+ }
4199
+ this._trackedByteSizes[scope] = total;
4200
+ return total;
4201
+ }
3973
4202
  async readAll() {
3974
4203
  const parts = [];
3975
4204
  for (const scope of ["project-agents", "project-memory", "user-memory"]) {
@@ -4070,6 +4299,7 @@ ${body.trim()}`);
4070
4299
  const t0 = Date.now();
4071
4300
  try {
4072
4301
  await this.backend.remember(scope, entry, filePath);
4302
+ this._scoreCache.clear();
4073
4303
  const dur = Date.now() - t0;
4074
4304
  this.events?.emit("storage.write", {
4075
4305
  sessionId: "~memory~",
@@ -4094,16 +4324,21 @@ ${body.trim()}`);
4094
4324
  });
4095
4325
  throw err;
4096
4326
  }
4097
- const raw = await this.backend.readAll(scope, this.files[scope]);
4098
- if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
4327
+ let trackedSize = this._trackedByteSizes[scope];
4328
+ if (trackedSize === void 0) {
4329
+ trackedSize = await this._recalcTrackedByteSize(scope);
4330
+ }
4331
+ if (trackedSize > MAX_BYTES_TOTAL) {
4099
4332
  const removed = await this.backend.consolidate(scope, this.files[scope]);
4100
4333
  if (removed > 0) {
4101
4334
  this.events?.emit("memory.consolidated", {
4102
4335
  scope,
4103
4336
  removed
4104
4337
  });
4338
+ await this._recalcTrackedByteSize(scope);
4105
4339
  }
4106
4340
  }
4341
+ this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
4107
4342
  await this.mirrorBackup(scope);
4108
4343
  this.events?.emit("memory.remembered", {
4109
4344
  scope,
@@ -4122,16 +4357,30 @@ ${body.trim()}`);
4122
4357
  async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
4123
4358
  const all = await this.list(scope);
4124
4359
  if (all.length === 0) return [];
4360
+ const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
4361
+ const now = Date.now();
4362
+ const TTL_MS = 3e4;
4363
+ const cached = this._scoreCache.get(ctxHash);
4364
+ if (cached && cached.expiresAt > now && cached.entries === all) {
4365
+ return cached.scored.slice(0, Math.min(limit, 15));
4366
+ }
4125
4367
  const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
4126
4368
  const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
4127
4369
  const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
4128
- const now = Date.now();
4370
+ this._cachedLower = /* @__PURE__ */ new WeakMap();
4129
4371
  const scored = [];
4130
4372
  for (const entry of all) {
4131
4373
  let score = 0;
4132
4374
  const reasons = [];
4133
- const textLower = entry.text.toLowerCase();
4134
- const tagsLower = (entry.tags ?? []).map((t) => t.toLowerCase());
4375
+ let cachedLower = this._cachedLower.get(entry);
4376
+ if (!cachedLower) {
4377
+ cachedLower = {
4378
+ textLower: entry.text.toLowerCase(),
4379
+ tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
4380
+ };
4381
+ this._cachedLower.set(entry, cachedLower);
4382
+ }
4383
+ const { textLower, tagsLower } = cachedLower;
4135
4384
  let taskHits = 0;
4136
4385
  for (const w of taskWords) {
4137
4386
  if (textLower.includes(w)) {
@@ -4217,6 +4466,7 @@ ${body.trim()}`);
4217
4466
  const relevant = scored.filter(
4218
4467
  (s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
4219
4468
  );
4469
+ this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
4220
4470
  return relevant.slice(0, Math.min(limit, 15));
4221
4471
  }
4222
4472
  async forget(query, scope = "project-memory") {
@@ -4226,6 +4476,7 @@ ${body.trim()}`);
4226
4476
  let removed = 0;
4227
4477
  try {
4228
4478
  removed = await this.backend.forget(scope, query, filePath);
4479
+ this._scoreCache.clear();
4229
4480
  const dur = Date.now() - t0;
4230
4481
  this.events?.emit("storage.write", {
4231
4482
  sessionId: "~memory~",
@@ -4257,6 +4508,7 @@ ${body.trim()}`);
4257
4508
  removed
4258
4509
  });
4259
4510
  await this.mirrorBackup(scope);
4511
+ await this._recalcTrackedByteSize(scope);
4260
4512
  }
4261
4513
  return removed;
4262
4514
  });
@@ -4268,6 +4520,7 @@ ${body.trim()}`);
4268
4520
  let removed = 0;
4269
4521
  try {
4270
4522
  removed = await this.backend.consolidate(scope, filePath);
4523
+ this._scoreCache.clear();
4271
4524
  const dur = Date.now() - t0;
4272
4525
  this.events?.emit("storage.write", {
4273
4526
  sessionId: "~memory~",
@@ -4298,6 +4551,7 @@ ${body.trim()}`);
4298
4551
  removed
4299
4552
  });
4300
4553
  await this.mirrorBackup(scope);
4554
+ await this._recalcTrackedByteSize(scope);
4301
4555
  }
4302
4556
  });
4303
4557
  }
@@ -4308,6 +4562,7 @@ ${body.trim()}`);
4308
4562
  const t0 = Date.now();
4309
4563
  try {
4310
4564
  await this.backend.clear(scope, filePath);
4565
+ this._scoreCache.clear();
4311
4566
  const dur = Date.now() - t0;
4312
4567
  this.events?.emit("storage.write", {
4313
4568
  sessionId: "~memory~",
@@ -4334,6 +4589,7 @@ ${body.trim()}`);
4334
4589
  }
4335
4590
  this.events?.emit("memory.cleared", { scope });
4336
4591
  await this.mirrorBackup(scope);
4592
+ this._trackedByteSizes[scope] = 0;
4337
4593
  });
4338
4594
  return;
4339
4595
  }
@@ -4647,6 +4903,83 @@ var ALGO = "aes-256-gcm";
4647
4903
  var KEY_FILE_MODE = 384;
4648
4904
  var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
4649
4905
  var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
4906
+ var KEK_MAGIC = Buffer.from("WSKW", "ascii");
4907
+ var KEK_SALT_BYTES = 16;
4908
+ var WRAPPED_KEY_FILE_SIZE = KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
4909
+ var SCRYPT_N = 1 << 15;
4910
+ var SCRYPT_R = 8;
4911
+ var SCRYPT_P = 1;
4912
+ var SCRYPT_MAXMEM = 64 * 1024 * 1024;
4913
+ function getVaultPassphrase() {
4914
+ const v = process.env["WRONGSTACK_VAULT_PASSPHRASE"];
4915
+ return v && v.length > 0 ? v : void 0;
4916
+ }
4917
+ function isWrappedKeyFile(buf) {
4918
+ return buf.length === WRAPPED_KEY_FILE_SIZE && buf.subarray(0, KEK_MAGIC.length).equals(KEK_MAGIC);
4919
+ }
4920
+ function deriveKEK(passphrase, salt) {
4921
+ return scryptSync(passphrase, salt, KEY_BYTES, {
4922
+ N: SCRYPT_N,
4923
+ r: SCRYPT_R,
4924
+ p: SCRYPT_P,
4925
+ maxmem: SCRYPT_MAXMEM
4926
+ });
4927
+ }
4928
+ function wrapDataKey(dataKey, keyVersion, passphrase) {
4929
+ const salt = randomBytes(KEK_SALT_BYTES);
4930
+ const iv = randomBytes(IV_BYTES);
4931
+ const kek = deriveKEK(passphrase, salt);
4932
+ const cipher = createCipheriv(ALGO, kek, iv);
4933
+ const ct = Buffer.concat([cipher.update(dataKey), cipher.final()]);
4934
+ const tag = cipher.getAuthTag();
4935
+ const out = Buffer.alloc(WRAPPED_KEY_FILE_SIZE);
4936
+ let off = 0;
4937
+ KEK_MAGIC.copy(out, off);
4938
+ off += KEK_MAGIC.length;
4939
+ out[off] = keyVersion & 255;
4940
+ off += 1;
4941
+ salt.copy(out, off);
4942
+ off += KEK_SALT_BYTES;
4943
+ iv.copy(out, off);
4944
+ off += IV_BYTES;
4945
+ tag.copy(out, off);
4946
+ off += TAG_BYTES;
4947
+ ct.copy(out, off);
4948
+ return out;
4949
+ }
4950
+ function unwrapDataKey(buf, keyFile) {
4951
+ const passphrase = getVaultPassphrase();
4952
+ if (!passphrase) {
4953
+ throw new ConfigError({
4954
+ message: `SecretVault: key file ${keyFile} is passphrase-protected \u2014 set the WRONGSTACK_VAULT_PASSPHRASE environment variable to unlock it.`,
4955
+ code: ERROR_CODES.CONFIG_INVALID,
4956
+ context: { keyFile }
4957
+ });
4958
+ }
4959
+ let off = KEK_MAGIC.length;
4960
+ const version = buf[off];
4961
+ off += 1;
4962
+ const salt = buf.subarray(off, off + KEK_SALT_BYTES);
4963
+ off += KEK_SALT_BYTES;
4964
+ const iv = buf.subarray(off, off + IV_BYTES);
4965
+ off += IV_BYTES;
4966
+ const tag = buf.subarray(off, off + TAG_BYTES);
4967
+ off += TAG_BYTES;
4968
+ const ct = buf.subarray(off, off + KEY_BYTES);
4969
+ const kek = deriveKEK(passphrase, salt);
4970
+ const decipher = createDecipheriv(ALGO, kek, iv);
4971
+ decipher.setAuthTag(tag);
4972
+ try {
4973
+ const key = Buffer.concat([decipher.update(ct), decipher.final()]);
4974
+ return { key: Buffer.from(key), version };
4975
+ } catch {
4976
+ throw new ConfigError({
4977
+ message: `SecretVault: failed to unlock key file ${keyFile} \u2014 wrong WRONGSTACK_VAULT_PASSPHRASE (key unwrap authentication failed).`,
4978
+ code: ERROR_CODES.CONFIG_INVALID,
4979
+ context: { keyFile }
4980
+ });
4981
+ }
4982
+ }
4650
4983
  function checkKeyFilePermissions(keyFile) {
4651
4984
  if (process.platform === "win32") return;
4652
4985
  try {
@@ -4739,25 +5072,56 @@ var DefaultSecretVault = class {
4739
5072
  const oldVersion = this._keyVersion;
4740
5073
  const newKey = randomBytes(KEY_BYTES);
4741
5074
  const newVersion = oldVersion + 1;
4742
- const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
4743
- KEY_FILE_MAGIC.copy(keyFileBuf, 0);
4744
- keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
4745
- newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
4746
5075
  fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
4747
- fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
5076
+ const passphrase = getVaultPassphrase();
5077
+ if (passphrase) {
5078
+ fs4.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
5079
+ } else {
5080
+ const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
5081
+ KEY_FILE_MAGIC.copy(keyFileBuf, 0);
5082
+ keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
5083
+ newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
5084
+ fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
5085
+ }
4748
5086
  checkKeyFilePermissions(this.keyFile);
4749
5087
  this.key = newKey;
4750
5088
  this._keyVersion = newVersion;
4751
5089
  return { oldVersion, newVersion };
4752
5090
  }
5091
+ /**
5092
+ * If WRONGSTACK_VAULT_PASSPHRASE is set but the key on disk is still stored
5093
+ * unwrapped (legacy v1 / versioned v2), re-write it in passphrase-wrapped (v3)
5094
+ * form. The data key is preserved, so all existing ciphertext keeps
5095
+ * decrypting. Best-effort: a write failure leaves the working unwrapped file
5096
+ * in place and is not fatal to load.
5097
+ */
5098
+ migrateToWrappedIfPassphrase() {
5099
+ const passphrase = getVaultPassphrase();
5100
+ if (!passphrase || !this.key) return;
5101
+ try {
5102
+ fs4.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
5103
+ mode: 384
5104
+ });
5105
+ checkKeyFilePermissions(this.keyFile);
5106
+ } catch {
5107
+ }
5108
+ }
4753
5109
  loadOrCreateKey() {
4754
5110
  if (this.key) return this.key;
4755
5111
  try {
4756
5112
  const buf = fs4.readFileSync(this.keyFile);
5113
+ if (isWrappedKeyFile(buf)) {
5114
+ const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
5115
+ this.key = key2;
5116
+ this._keyVersion = version;
5117
+ checkKeyFilePermissions(this.keyFile);
5118
+ return this.key;
5119
+ }
4757
5120
  if (buf.length === KEY_BYTES) {
4758
5121
  this.key = buf;
4759
5122
  this._keyVersion = 1;
4760
5123
  checkKeyFilePermissions(this.keyFile);
5124
+ this.migrateToWrappedIfPassphrase();
4761
5125
  return this.key;
4762
5126
  }
4763
5127
  if (buf.length === VERSIONED_KEY_FILE_SIZE) {
@@ -4781,6 +5145,7 @@ var DefaultSecretVault = class {
4781
5145
  this.key = Buffer.from(key2);
4782
5146
  this._keyVersion = version;
4783
5147
  checkKeyFilePermissions(this.keyFile);
5148
+ this.migrateToWrappedIfPassphrase();
4784
5149
  return this.key;
4785
5150
  }
4786
5151
  throw new ConfigError({
@@ -4793,11 +5158,20 @@ var DefaultSecretVault = class {
4793
5158
  }
4794
5159
  fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
4795
5160
  const key = randomBytes(KEY_BYTES);
5161
+ const passphrase = getVaultPassphrase();
5162
+ const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
4796
5163
  try {
4797
- fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
5164
+ fs4.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
4798
5165
  } catch (err) {
4799
5166
  if (err.code !== "EEXIST") throw err;
4800
5167
  const buf = fs4.readFileSync(this.keyFile);
5168
+ if (isWrappedKeyFile(buf)) {
5169
+ const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
5170
+ this.key = winnerKey;
5171
+ this._keyVersion = version;
5172
+ checkKeyFilePermissions(this.keyFile);
5173
+ return this.key;
5174
+ }
4801
5175
  if (buf.length === KEY_BYTES) {
4802
5176
  this.key = buf;
4803
5177
  this._keyVersion = 1;
@@ -5141,6 +5515,51 @@ var defaultIndexing = {
5141
5515
  watchExternal: true,
5142
5516
  debounceMs: 400
5143
5517
  };
5518
+ var IN_PROJECT_FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
5519
+ "provider",
5520
+ "apiKey",
5521
+ "baseUrl",
5522
+ "providers",
5523
+ "mcpServers",
5524
+ "hooks",
5525
+ "plugins",
5526
+ "sync",
5527
+ "yolo",
5528
+ "extensions"
5529
+ ]);
5530
+ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => console.warn(msg)) {
5531
+ const stripped = [];
5532
+ const out = {};
5533
+ for (const [k, v] of Object.entries(inProject)) {
5534
+ if (IN_PROJECT_FORBIDDEN_KEYS.has(k)) {
5535
+ stripped.push(k);
5536
+ continue;
5537
+ }
5538
+ out[k] = v;
5539
+ }
5540
+ if (stripped.length > 0) {
5541
+ warn(
5542
+ JSON.stringify({
5543
+ level: "warn",
5544
+ event: "config.in_project_unsafe_fields_ignored",
5545
+ path: sourcePath,
5546
+ ignoredKeys: stripped,
5547
+ message: `Ignored ${stripped.length} unsafe field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. These can only be set in your personal ~/.wrongstack/config.json, not in a project-committed file.`,
5548
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5549
+ })
5550
+ );
5551
+ }
5552
+ return out;
5553
+ }
5554
+ function samePath(a, b) {
5555
+ let ra = path4.resolve(a);
5556
+ let rb = path4.resolve(b);
5557
+ if (process.platform === "win32" || process.platform === "darwin") {
5558
+ ra = ra.toLowerCase();
5559
+ rb = rb.toLowerCase();
5560
+ }
5561
+ return ra === rb;
5562
+ }
5144
5563
  function deepMerge2(base, patch) {
5145
5564
  const opts = { arrayMode: "concat-primitives" };
5146
5565
  if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
@@ -5169,14 +5588,15 @@ var DefaultConfigLoader = class {
5169
5588
  }
5170
5589
  async load(opts = {}) {
5171
5590
  let cfg = { ...BEHAVIOR_DEFAULTS };
5591
+ const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
5172
5592
  const [global, local, inProject] = await Promise.all([
5173
5593
  this.readJson(this.paths.globalConfig),
5174
5594
  this.readJson(this.paths.projectLocalConfig),
5175
- this.readJson(this.paths.inProjectConfig)
5595
+ inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
5176
5596
  ]);
5177
5597
  cfg = deepMerge2(cfg, global);
5178
5598
  cfg = deepMerge2(cfg, local);
5179
- cfg = deepMerge2(cfg, inProject);
5599
+ cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
5180
5600
  for (const [key, fn] of Object.entries(ENV_MAP)) {
5181
5601
  const v = process.env[key];
5182
5602
  if (v) fn(cfg, v);
@@ -7057,6 +7477,9 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
7057
7477
  }
7058
7478
 
7059
7479
  // src/security/permission-policy.ts
7480
+ function matchesTrust(patterns, subject) {
7481
+ return patterns.includes(subject) || matchAny(patterns, subject);
7482
+ }
7060
7483
  var DefaultPermissionPolicy = class {
7061
7484
  policy = {};
7062
7485
  loaded = false;
@@ -7193,7 +7616,7 @@ var DefaultPermissionPolicy = class {
7193
7616
  this._evalCache.set(cacheKey, decision);
7194
7617
  return decision;
7195
7618
  }
7196
- if (entry?.deny && subject && matchAny(entry.deny, subject)) {
7619
+ if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
7197
7620
  const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
7198
7621
  this._evalCache.set(cacheKey, decision);
7199
7622
  return decision;
@@ -7203,7 +7626,7 @@ var DefaultPermissionPolicy = class {
7203
7626
  this._evalCache.set(cacheKey, decision);
7204
7627
  return decision;
7205
7628
  }
7206
- if (entry?.allow && subject && matchAny(entry.allow, subject)) {
7629
+ if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
7207
7630
  const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
7208
7631
  this._evalCache.set(cacheKey, decision);
7209
7632
  return decision;
@@ -7927,6 +8350,16 @@ function handleMessageStop(state, ev) {
7927
8350
  async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
7928
8351
  const state = createStreamingState(req.model);
7929
8352
  logger.debug("Stream started", { providerId: provider.id, model: req.model });
8353
+ const TEXT_BATCH_SIZE = 4;
8354
+ let pendingText = "";
8355
+ let pendingCount = 0;
8356
+ const flushText = () => {
8357
+ if (pendingCount > 0) {
8358
+ events.emit("provider.text_delta", { ctx, text: pendingText });
8359
+ pendingText = "";
8360
+ pendingCount = 0;
8361
+ }
8362
+ };
7930
8363
  const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
7931
8364
  try {
7932
8365
  for (; ; ) {
@@ -7946,9 +8379,12 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
7946
8379
  break;
7947
8380
  case "text_delta":
7948
8381
  handleTextDelta(state, ev.text);
7949
- events.emit("provider.text_delta", { ctx, text: ev.text });
8382
+ pendingText += ev.text;
8383
+ pendingCount++;
8384
+ if (pendingCount >= TEXT_BATCH_SIZE) flushText();
7950
8385
  break;
7951
8386
  case "tool_use_start": {
8387
+ flushText();
7952
8388
  const idVal = ev.id;
7953
8389
  const nameVal = ev.name;
7954
8390
  handleToolUseStart(state, { id: idVal, name: nameVal });
@@ -7960,6 +8396,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
7960
8396
  handleToolUseInputDelta(state, ev);
7961
8397
  break;
7962
8398
  case "tool_use_stop": {
8399
+ flushText();
7963
8400
  const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
7964
8401
  handleToolUseStop(state, ev);
7965
8402
  events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
@@ -7969,6 +8406,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
7969
8406
  handleThinkingStart(state, ev);
7970
8407
  break;
7971
8408
  case "thinking_delta":
8409
+ flushText();
7972
8410
  handleThinkingDelta(state, ev.text);
7973
8411
  events.emit("provider.thinking_delta", { ctx, text: ev.text });
7974
8412
  break;
@@ -8000,6 +8438,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
8000
8438
  eventType: String(evAny.type),
8001
8439
  errorMessage: errMsg
8002
8440
  });
8441
+ flushText();
8003
8442
  events.emit("provider.stream_error", {
8004
8443
  ctx,
8005
8444
  eventType: String(evAny.type),
@@ -8010,6 +8449,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
8010
8449
  } catch (err) {
8011
8450
  if (signal.aborted) {
8012
8451
  state.stopReason = "end_turn";
8452
+ flushText();
8013
8453
  logger.debug("Stream aborted \u2014 returning partial state", {
8014
8454
  providerId: provider.id,
8015
8455
  model: req.model,
@@ -8028,8 +8468,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
8028
8468
  });
8029
8469
  await Promise.race([
8030
8470
  drainPromise,
8031
- new Promise((resolve6) => {
8032
- drainTimer = setTimeout(resolve6, STREAM_DRAIN_TIMEOUT_MS);
8471
+ new Promise((resolve7) => {
8472
+ drainTimer = setTimeout(resolve7, STREAM_DRAIN_TIMEOUT_MS);
8033
8473
  })
8034
8474
  ]);
8035
8475
  } finally {
@@ -8038,6 +8478,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
8038
8478
  } catch {
8039
8479
  }
8040
8480
  }
8481
+ flushText();
8041
8482
  logger.debug("Stream completed", {
8042
8483
  providerId: provider.id,
8043
8484
  model: req.model,
@@ -8135,7 +8576,7 @@ async function runProviderWithRetry(opts) {
8135
8576
  description
8136
8577
  });
8137
8578
  }
8138
- await new Promise((resolve6, reject) => {
8579
+ await new Promise((resolve7, reject) => {
8139
8580
  let settled = false;
8140
8581
  const cleanup = () => {
8141
8582
  clearTimeout(t);
@@ -8151,7 +8592,7 @@ async function runProviderWithRetry(opts) {
8151
8592
  if (settled) return;
8152
8593
  settled = true;
8153
8594
  cleanup();
8154
- resolve6();
8595
+ resolve7();
8155
8596
  }, delay);
8156
8597
  if (signal.aborted) {
8157
8598
  onAbort();
@@ -8224,17 +8665,9 @@ function findPreserveStart(messages, preserveK) {
8224
8665
  const prev = messages[preserveStart - 1];
8225
8666
  if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
8226
8667
  if (typeof first.content === "string" || typeof prev.content === "string") break;
8227
- const resultIds = /* @__PURE__ */ new Set();
8228
- for (const block of first.content) {
8229
- pairRepairInnerIterations++;
8230
- if (block.type === "tool_result") resultIds.add(block.tool_use_id);
8231
- }
8232
- if (resultIds.size === 0) break;
8233
- const hasMatchingUse = prev.content.some((block) => {
8234
- pairRepairInnerIterations++;
8235
- return block.type === "tool_use" && resultIds.has(block.id);
8236
- });
8237
- if (!hasMatchingUse) break;
8668
+ const pairCheck = hasMatchingToolPair(first.content, prev.content);
8669
+ pairRepairInnerIterations += pairCheck.iterations;
8670
+ if (!pairCheck.matched) break;
8238
8671
  preserveStart--;
8239
8672
  }
8240
8673
  if (compactionDebugEnabled()) {
@@ -8253,9 +8686,34 @@ function findPreserveStart(messages, preserveK) {
8253
8686
  }
8254
8687
  return preserveStart;
8255
8688
  }
8689
+ function hasMatchingToolPair(resultContent, useContent) {
8690
+ let iterations = 0;
8691
+ let firstResultId;
8692
+ let resultIds;
8693
+ for (const block of resultContent) {
8694
+ iterations++;
8695
+ if (block.type !== "tool_result") continue;
8696
+ if (firstResultId === void 0) {
8697
+ firstResultId = block.tool_use_id;
8698
+ } else {
8699
+ resultIds ??= /* @__PURE__ */ new Set([firstResultId]);
8700
+ resultIds.add(block.tool_use_id);
8701
+ }
8702
+ }
8703
+ if (firstResultId === void 0) return { matched: false, iterations };
8704
+ for (const block of useContent) {
8705
+ iterations++;
8706
+ if (block.type !== "tool_use") continue;
8707
+ if (resultIds ? resultIds.has(block.id) : block.id === firstResultId) {
8708
+ return { matched: true, iterations };
8709
+ }
8710
+ }
8711
+ return { matched: false, iterations };
8712
+ }
8256
8713
  function eliseOldToolResults(messages, opts) {
8257
8714
  const preserveStart = findPreserveStart(messages, opts.preserveK);
8258
8715
  let hasOversized = false;
8716
+ let firstOversizedIndex = -1;
8259
8717
  let fastPathIterations = 0;
8260
8718
  let fastPathInnerIterations = 0;
8261
8719
  for (let i = 0; i < preserveStart && !hasOversized; i++) {
@@ -8267,6 +8725,7 @@ function eliseOldToolResults(messages, opts) {
8267
8725
  const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
8268
8726
  if (oversized) {
8269
8727
  hasOversized = true;
8728
+ firstOversizedIndex = i;
8270
8729
  break;
8271
8730
  }
8272
8731
  }
@@ -8289,26 +8748,29 @@ function eliseOldToolResults(messages, opts) {
8289
8748
  let changed = false;
8290
8749
  let fullPassIterations = 0;
8291
8750
  let fullPassInnerIterations = 0;
8292
- const next = new Array(messages.length);
8293
- for (let i = 0; i < messages.length; i++) {
8751
+ let next;
8752
+ for (let i = firstOversizedIndex; i < preserveStart; i++) {
8294
8753
  fullPassIterations++;
8295
8754
  const msg = messages[i];
8296
- if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
8297
- next[i] = msg;
8298
- continue;
8299
- }
8755
+ if (!msg || !Array.isArray(msg.content)) continue;
8300
8756
  const original = msg.content;
8301
- const newContent = original.map((b) => {
8757
+ let newContent;
8758
+ for (let idx = 0; idx < original.length; idx++) {
8759
+ fullPassInnerIterations++;
8760
+ const b = original[idx];
8761
+ if (!b) continue;
8302
8762
  if (b.type === "tool_use") {
8303
8763
  const tokens2 = estimateToolInputTokens(b.input);
8304
- if (tokens2 < opts.eliseThreshold) return b;
8764
+ if (tokens2 < opts.eliseThreshold) continue;
8305
8765
  const elidedInput = summarizeToolUseInputElision(b, tokens2);
8306
8766
  saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
8307
- return { ...b, input: elidedInput };
8767
+ newContent ??= original.slice();
8768
+ newContent[idx] = { ...b, input: elidedInput };
8769
+ continue;
8308
8770
  }
8309
- if (b.type !== "tool_result") return b;
8771
+ if (b.type !== "tool_result") continue;
8310
8772
  const tokens = estimateToolResultTokens(b.content);
8311
- if (tokens < opts.eliseThreshold) return b;
8773
+ if (tokens < opts.eliseThreshold) continue;
8312
8774
  saved += tokens;
8313
8775
  const elided = {
8314
8776
  type: "tool_result",
@@ -8316,15 +8778,14 @@ function eliseOldToolResults(messages, opts) {
8316
8778
  content: summarizeToolResultElision(b, tokens),
8317
8779
  is_error: b.is_error
8318
8780
  };
8319
- return elided;
8320
- });
8321
- if (newContent.every((b, idx) => b === original[idx])) {
8322
- next[i] = msg;
8323
- } else {
8781
+ newContent ??= original.slice();
8782
+ newContent[idx] = elided;
8783
+ }
8784
+ if (newContent) {
8785
+ next ??= messages.slice();
8324
8786
  next[i] = { ...msg, content: newContent };
8325
8787
  changed = true;
8326
8788
  }
8327
- fullPassInnerIterations += original.length;
8328
8789
  if (compactionDebugEnabled()) {
8329
8790
  const ratio = fullPassInnerIterations / fullPassIterations;
8330
8791
  if (ratio > 10) {
@@ -8351,7 +8812,7 @@ function eliseOldToolResults(messages, opts) {
8351
8812
  tokensSaved: saved,
8352
8813
  changed
8353
8814
  });
8354
- return { messages: changed ? next : messages, saved, changed };
8815
+ return { messages: changed && next ? next : messages, saved, changed };
8355
8816
  }
8356
8817
  function summarizeToolUseInputElision(block, tokens) {
8357
8818
  const fields = {};
@@ -10659,7 +11120,14 @@ var EternalAutonomyEngine = class {
10659
11120
  try {
10660
11121
  const reloaded = await loadGoal(this.goalPath, this.opts.events);
10661
11122
  iterationIndex = reloaded?.iterations ?? 0;
10662
- } catch {
11123
+ } catch (err) {
11124
+ console.error(JSON.stringify({
11125
+ level: "warn",
11126
+ event: "autonomy.goal_reload_failed",
11127
+ message: toErrorMessage(err),
11128
+ context: { goalPath: this.goalPath },
11129
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11130
+ }));
10663
11131
  }
10664
11132
  this.opts.onIteration?.({
10665
11133
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -10894,7 +11362,14 @@ ${lastFew}` : "No prior iterations yet.",
10894
11362
  } finally {
10895
11363
  clearTimeout(timer);
10896
11364
  }
10897
- } catch {
11365
+ } catch (err) {
11366
+ console.error(JSON.stringify({
11367
+ level: "warn",
11368
+ event: "autonomy.brainstorm_failed",
11369
+ message: toErrorMessage(err),
11370
+ context: { goal: goal.goal.slice(0, 100) },
11371
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11372
+ }));
10898
11373
  return null;
10899
11374
  }
10900
11375
  }
@@ -11417,13 +11892,13 @@ var SubagentBudget = class _SubagentBudget {
11417
11892
  if (!bus?.hasListenerFor("budget.threshold_reached")) {
11418
11893
  return Promise.resolve("stop");
11419
11894
  }
11420
- return new Promise((resolve6) => {
11895
+ return new Promise((resolve7) => {
11421
11896
  let resolved = false;
11422
11897
  const respond = (d) => {
11423
11898
  if (resolved) return;
11424
11899
  resolved = true;
11425
11900
  clearTimeout(fallback);
11426
- resolve6(d);
11901
+ resolve7(d);
11427
11902
  };
11428
11903
  const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
11429
11904
  bus.emit("budget.threshold_reached", {
@@ -14774,6 +15249,68 @@ Working rules:
14774
15249
  - When in doubt, flag as medium rather than ignoring potential issues`
14775
15250
  // Budgets are set by the orchestrator per task — see fleet.ts header.
14776
15251
  };
15252
+ var SHADOW_AGENT = {
15253
+ id: "shadow-agent",
15254
+ name: "Shadow",
15255
+ role: "shadow-agent",
15256
+ prompt: `You are the Shadow Agent \u2014 a silent background monitor for the WrongStack fleet.
15257
+
15258
+ Your job is to observe, detect anomalies, and be ready to intervene \u2014 but only when commanded.
15259
+
15260
+ ## Core Responsibilities
15261
+
15262
+ 1. **Fleet Monitoring** (every 30s)
15263
+ - Call \`fleet_status\` + \`fleet_health\` on each heartbeat
15264
+ - Track what each agent is doing (task descriptions)
15265
+ - Detect stuck agents (>5min no events), idle agents, crashed agents
15266
+
15267
+ 2. **FleetBus Subscription**
15268
+ - Subscribe to \`subagent.*\` events to track lifecycle
15269
+ - Subscribe to \`tool.executed\` to monitor activity
15270
+ - Track agent joins (subagent.started) and leaves (subagent.stopped)
15271
+
15272
+ 3. **Mailbox Surveillance**
15273
+ - Monitor for \`control\` type messages starting with "hoop"
15274
+ - Detect orphan assigns (assign without result within 5min)
15275
+ - Cross-session awareness via shared project mailbox
15276
+
15277
+ 4. **Spike Detection**
15278
+ - Track task duration per agent
15279
+ - Flag agents that spawn and die within <5 seconds
15280
+ - Log spike events with reason (completed/error/killed/timeout)
15281
+
15282
+ 5. **Intervention Commands**
15283
+ Parse these from mailbox control messages:
15284
+ - \`hoop <agentId>\` \u2014 terminate specific agent
15285
+ - \`hoop all\` \u2014 terminate all running agents
15286
+ - \`shadow status\` \u2014 report current fleet snapshot
15287
+ - \`shadow mute\` \u2014 pause heartbeat monitoring
15288
+ - \`shadow resume\` \u2014 resume heartbeat monitoring
15289
+ - \`shadow interval <ms>\` \u2014 change heartbeat interval
15290
+ - \`shadow model <model-id>\` \u2014 change analysis model
15291
+
15292
+ ## Operating Rules
15293
+
15294
+ - **Silent by default**: Use DEBUG level logging unless anomaly detected
15295
+ - **Deterministic**: Same state always produces same actions \u2014 no randomness
15296
+ - **Report on anomaly**: When anomaly detected, use \`mail_send\` to broadcast warning
15297
+ - **Never auto-intervene**: Always report unless explicitly commanded
15298
+ - **Minimal footprint**: Small state, efficient snapshots
15299
+
15300
+ ## Startup Sequence
15301
+
15302
+ 1. Send broadcast: \`shadow:started { intervalMs, model, startTime }\`
15303
+ 2. Subscribe to FleetBus for all relevant events
15304
+ 3. Schedule heartbeat cron job at configured interval
15305
+ 4. Wait for commands or anomalies
15306
+
15307
+ ## Shutdown Sequence
15308
+
15309
+ 1. Cancel all cron jobs (\`cron_cancel\`)
15310
+ 2. Send broadcast: \`shadow:stopped { reason, finalState }\`
15311
+ 3. Clean up FleetBus subscriptions`
15312
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
15313
+ };
14777
15314
  var CRITIC_AGENT = {
14778
15315
  id: "critic",
14779
15316
  name: "Critic",
@@ -14814,6 +15351,7 @@ var FLEET_ROSTER = {
14814
15351
  "refactor-planner": REFACTOR_PLANNER_AGENT,
14815
15352
  "security-scanner": SECURITY_SCANNER_AGENT,
14816
15353
  "critic": CRITIC_AGENT,
15354
+ "shadow-agent": SHADOW_AGENT,
14817
15355
  ...Object.fromEntries(
14818
15356
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
14819
15357
  )
@@ -14825,6 +15363,8 @@ var FLEET_ROSTER_BUDGETS = {
14825
15363
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
14826
15364
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
14827
15365
  "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
15366
+ "shadow-agent": { timeoutMs: 24 * 60 * 60 * 1e3, maxIterations: 1e4, maxToolCalls: 5e3 },
15367
+ // Long-running background monitor
14828
15368
  ...Object.fromEntries(
14829
15369
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
14830
15370
  )
@@ -15263,7 +15803,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
15263
15803
  taskIds.map((id) => {
15264
15804
  const cached = this.completedResults.find((r) => r.taskId === id);
15265
15805
  if (cached) return cached;
15266
- return new Promise((resolve6, reject) => {
15806
+ return new Promise((resolve7, reject) => {
15267
15807
  const timeout = setTimeout(() => {
15268
15808
  this.off("task.completed", handler);
15269
15809
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -15272,7 +15812,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
15272
15812
  if (result.taskId === id) {
15273
15813
  clearTimeout(timeout);
15274
15814
  this.off("task.completed", handler);
15275
- resolve6(result);
15815
+ resolve7(result);
15276
15816
  }
15277
15817
  };
15278
15818
  this.on("task.completed", handler);
@@ -15540,12 +16080,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
15540
16080
  }
15541
16081
  return new Promise((resolveDecision) => {
15542
16082
  let settled = false;
15543
- const resolve6 = (d) => {
16083
+ const resolve7 = (d) => {
15544
16084
  if (settled) return;
15545
16085
  settled = true;
15546
16086
  resolveDecision(d);
15547
16087
  };
15548
- const fallback = setTimeout(() => resolve6("stop"), DECISION_TIMEOUT_MS);
16088
+ const fallback = setTimeout(() => resolve7("stop"), DECISION_TIMEOUT_MS);
15549
16089
  budget._events?.emit("budget.threshold_reached", {
15550
16090
  kind: "timeout",
15551
16091
  used,
@@ -15561,11 +16101,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
15561
16101
  // disagreeing, resolves as a stop). Async grants still resolve.
15562
16102
  extend: (extra) => {
15563
16103
  clearTimeout(fallback);
15564
- queueMicrotask(() => resolve6({ extend: extra }));
16104
+ queueMicrotask(() => resolve7({ extend: extra }));
15565
16105
  },
15566
16106
  deny: () => {
15567
16107
  clearTimeout(fallback);
15568
- resolve6("stop");
16108
+ resolve7("stop");
15569
16109
  }
15570
16110
  });
15571
16111
  });
@@ -16032,7 +16572,14 @@ ${personaLine}Task: ${task}
16032
16572
  subagentIds.push(subagentId);
16033
16573
  taskIds.push(taskId);
16034
16574
  await coordinator.assign(spec);
16035
- } catch {
16575
+ } catch (err) {
16576
+ console.error(JSON.stringify({
16577
+ level: "warn",
16578
+ event: "parallel_engine.spawn_failed",
16579
+ message: toErrorMessage(err),
16580
+ context: { slot: i, task, subagentId },
16581
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16582
+ }));
16036
16583
  }
16037
16584
  })()
16038
16585
  );
@@ -16057,7 +16604,14 @@ ${personaLine}Task: ${task}
16057
16604
  } finally {
16058
16605
  clearTimeout(timer);
16059
16606
  }
16060
- } catch {
16607
+ } catch (err) {
16608
+ console.error(JSON.stringify({
16609
+ level: "warn",
16610
+ event: "parallel_engine.brainstorm_results_failed",
16611
+ message: toErrorMessage(err),
16612
+ context: { slotCount, taskIds },
16613
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16614
+ }));
16061
16615
  results = coordinator.results().slice(-taskIds.length);
16062
16616
  }
16063
16617
  await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
@@ -16091,7 +16645,14 @@ ${personaLine}Task: ${task}
16091
16645
  if (file) tasks.push(`[git] inspect and fix: ${file}`);
16092
16646
  }
16093
16647
  }
16094
- } catch {
16648
+ } catch (err) {
16649
+ console.error(JSON.stringify({
16650
+ level: "warn",
16651
+ event: "parallel_engine.git_status_failed",
16652
+ message: toErrorMessage(err),
16653
+ context: { projectRoot: this.opts.projectRoot },
16654
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16655
+ }));
16095
16656
  }
16096
16657
  }
16097
16658
  if (tasks.length < this.slots) {
@@ -16454,7 +17015,7 @@ var InMemoryAgentBridge = class {
16454
17015
  });
16455
17016
  }
16456
17017
  this.inflightGuards.add(correlationId);
16457
- return new Promise((resolve6, reject) => {
17018
+ return new Promise((resolve7, reject) => {
16458
17019
  const timer = setTimeout(() => {
16459
17020
  this.inflightGuards.delete(correlationId);
16460
17021
  this.pendingRequests.delete(correlationId);
@@ -16473,7 +17034,7 @@ var InMemoryAgentBridge = class {
16473
17034
  return;
16474
17035
  }
16475
17036
  this.pendingRequests.set(correlationId, {
16476
- resolve: resolve6,
17037
+ resolve: resolve7,
16477
17038
  reject,
16478
17039
  timer
16479
17040
  });
@@ -18935,11 +19496,11 @@ var Director = class _Director {
18935
19496
  if (cached) return cached;
18936
19497
  const existing = this.taskWaiters.get(id);
18937
19498
  if (existing) return existing.promise;
18938
- let resolve6;
19499
+ let resolve7;
18939
19500
  const promise = new Promise((res) => {
18940
- resolve6 = res;
19501
+ resolve7 = res;
18941
19502
  });
18942
- this.taskWaiters.set(id, { promise, resolve: resolve6 });
19503
+ this.taskWaiters.set(id, { promise, resolve: resolve7 });
18943
19504
  return promise;
18944
19505
  })
18945
19506
  );
@@ -19335,7 +19896,7 @@ function createDelegateTool(opts) {
19335
19896
  subagentId
19336
19897
  });
19337
19898
  const dir = director;
19338
- const result = await new Promise((resolve6) => {
19899
+ const result = await new Promise((resolve7) => {
19339
19900
  let settled = false;
19340
19901
  let timer;
19341
19902
  const finish = (value) => {
@@ -19345,7 +19906,7 @@ function createDelegateTool(opts) {
19345
19906
  offTool();
19346
19907
  offIter();
19347
19908
  offProgress();
19348
- resolve6(value);
19909
+ resolve7(value);
19349
19910
  };
19350
19911
  const arm = () => {
19351
19912
  if (timer) clearTimeout(timer);
@@ -19705,6 +20266,7 @@ var NULL_FLEET_BUS = new FleetBus();
19705
20266
  // src/models/models-registry.ts
19706
20267
  init_atomic_write();
19707
20268
  var DEFAULT_URL = "https://models.dev/api.json";
20269
+ var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
19708
20270
  var DEFAULT_TTL_SECONDS = 24 * 3600;
19709
20271
  var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
19710
20272
  var FAMILY_BY_NPM = {
@@ -19750,7 +20312,7 @@ var DefaultModelsRegistry = class {
19750
20312
  overlayCacheFile;
19751
20313
  constructor(opts) {
19752
20314
  this.cacheFile = opts.cacheFile;
19753
- this.url = opts.url ?? DEFAULT_URL;
20315
+ this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
19754
20316
  this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
19755
20317
  this.fetchImpl = opts.fetchImpl ?? fetch;
19756
20318
  this.seed = opts.seed;
@@ -19795,6 +20357,10 @@ var DefaultModelsRegistry = class {
19795
20357
  const cached = await this.readCacheAt(this.cacheFile);
19796
20358
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
19797
20359
  this.fetchedAt = new Date(cached.fetchedAt);
20360
+ const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
20361
+ console.warn(
20362
+ `ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`
20363
+ );
19798
20364
  return cached.payload;
19799
20365
  }
19800
20366
  if (overlayAvailable) {
@@ -19880,7 +20446,13 @@ var DefaultModelsRegistry = class {
19880
20446
  return json;
19881
20447
  } catch {
19882
20448
  const cached = await this.readCacheAt(this.overlayCacheFile);
19883
- if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) return cached.payload;
20449
+ if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
20450
+ const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
20451
+ console.warn(
20452
+ `ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`
20453
+ );
20454
+ return cached.payload;
20455
+ }
19884
20456
  return void 0;
19885
20457
  }
19886
20458
  }
@@ -19977,6 +20549,16 @@ var DefaultModelsRegistry = class {
19977
20549
  return path4.resolve(this.cacheFile);
19978
20550
  }
19979
20551
  };
20552
+ function formatAge(seconds) {
20553
+ if (seconds < 60) return "<1m";
20554
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
20555
+ if (seconds < 86400) {
20556
+ const h = Math.floor(seconds / 3600);
20557
+ const m = Math.floor(seconds % 3600 / 60);
20558
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
20559
+ }
20560
+ return `${Math.floor(seconds / 86400)}d`;
20561
+ }
19980
20562
  function hasEntries(payload) {
19981
20563
  return payload !== void 0 && Object.keys(payload).length > 0;
19982
20564
  }
@@ -21777,9 +22359,9 @@ var AISpecBuilder = class {
21777
22359
  if (!this.sessionPath) return;
21778
22360
  try {
21779
22361
  const fsp16 = await import('fs/promises');
21780
- const path21 = await import('path');
22362
+ const path22 = await import('path');
21781
22363
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
21782
- await fsp16.mkdir(path21.dirname(this.sessionPath), { recursive: true });
22364
+ await fsp16.mkdir(path22.dirname(this.sessionPath), { recursive: true });
21783
22365
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
21784
22366
  } catch {
21785
22367
  }
@@ -22506,15 +23088,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
22506
23088
  maxId = id;
22507
23089
  }
22508
23090
  }
22509
- const path21 = [];
23091
+ const path22 = [];
22510
23092
  let current = maxId;
22511
23093
  const visited = /* @__PURE__ */ new Set();
22512
23094
  while (current && !visited.has(current)) {
22513
23095
  visited.add(current);
22514
- path21.unshift(current);
23096
+ path22.unshift(current);
22515
23097
  current = prev.get(current) ?? null;
22516
23098
  }
22517
- return path21;
23099
+ return path22;
22518
23100
  }
22519
23101
  function computeParallelGroups(graph, blockedByMap) {
22520
23102
  const groups = [];
@@ -23337,9 +23919,9 @@ var DefaultHealthRegistry = class {
23337
23919
  }
23338
23920
  async runOne(check) {
23339
23921
  let timer = null;
23340
- const timeout = new Promise((resolve6) => {
23922
+ const timeout = new Promise((resolve7) => {
23341
23923
  timer = setTimeout(
23342
- () => resolve6({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
23924
+ () => resolve7({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
23343
23925
  this.timeoutMs
23344
23926
  );
23345
23927
  });
@@ -23522,7 +24104,7 @@ async function startMetricsServer(opts) {
23522
24104
  const tls = opts.tls;
23523
24105
  const useHttps = !!(tls?.cert && tls?.key);
23524
24106
  const host = opts.host ?? "127.0.0.1";
23525
- const path21 = opts.path ?? "/metrics";
24107
+ const path22 = opts.path ?? "/metrics";
23526
24108
  const healthPath = opts.healthPath ?? "/healthz";
23527
24109
  const healthRegistry = opts.healthRegistry;
23528
24110
  const listener = (req, res) => {
@@ -23532,7 +24114,7 @@ async function startMetricsServer(opts) {
23532
24114
  return;
23533
24115
  }
23534
24116
  const url = req.url.split("?")[0];
23535
- if (url === path21) {
24117
+ if (url === path22) {
23536
24118
  let body;
23537
24119
  try {
23538
24120
  body = renderPrometheus(opts.sink.snapshot());
@@ -23578,14 +24160,14 @@ async function startMetricsServer(opts) {
23578
24160
  const { createServer } = await import('http');
23579
24161
  server = createServer(listener);
23580
24162
  }
23581
- await new Promise((resolve6, reject) => {
24163
+ await new Promise((resolve7, reject) => {
23582
24164
  const onError = (err) => {
23583
24165
  server.off("listening", onListening);
23584
24166
  reject(err);
23585
24167
  };
23586
24168
  const onListening = () => {
23587
24169
  server.off("error", onError);
23588
- resolve6();
24170
+ resolve7();
23589
24171
  };
23590
24172
  server.once("error", onError);
23591
24173
  server.once("listening", onListening);
@@ -23596,9 +24178,9 @@ async function startMetricsServer(opts) {
23596
24178
  const protocol = useHttps ? "https" : "http";
23597
24179
  return {
23598
24180
  port: boundPort,
23599
- url: `${protocol}://${host}:${boundPort}${path21}`,
23600
- close: () => new Promise((resolve6, reject) => {
23601
- server.close((err) => err ? reject(err) : resolve6());
24181
+ url: `${protocol}://${host}:${boundPort}${path22}`,
24182
+ close: () => new Promise((resolve7, reject) => {
24183
+ server.close((err) => err ? reject(err) : resolve7());
23602
24184
  })
23603
24185
  };
23604
24186
  }