@wrongstack/core 0.73.1 → 0.82.6

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 (79) hide show
  1. package/dist/{agent-bridge-C0Ze7Ldm.d.ts → agent-bridge-C9P_HPez.d.ts} +2 -2
  2. package/dist/{agent-subagent-runner-BmITbs1Q.d.ts → agent-subagent-runner-2Aq0jOSj.d.ts} +107 -102
  3. package/dist/{compactor-D_ExJajC.d.ts → compactor-CJq7LQev.d.ts} +3 -3
  4. package/dist/{config-Dy0CK_o6.d.ts → config-_DZ7dN-T.d.ts} +77 -75
  5. package/dist/{context-y87Jc5ei.d.ts → context-ToHAp4-U.d.ts} +119 -90
  6. package/dist/coordination/index.d.ts +16 -16
  7. package/dist/coordination/index.js +382 -43
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +31 -31
  10. package/dist/defaults/index.js +524 -110
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{director-state-BmYi3DGA.d.ts → director-state-CgIc30qi.d.ts} +19 -19
  13. package/dist/{events-BBAlxBuw.d.ts → events-DnRqXaZ3.d.ts} +77 -39
  14. package/dist/execution/index.d.ts +53 -53
  15. package/dist/execution/index.js +67 -23
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/extension/index.d.ts +9 -9
  18. package/dist/extension/index.js +8 -1
  19. package/dist/extension/index.js.map +1 -1
  20. package/dist/{goal-store-C7jcumEh.d.ts → goal-store-DvWLNu52.d.ts} +4 -4
  21. package/dist/{index-yQbZ2NQx.d.ts → index-BNOLadHw.d.ts} +28 -28
  22. package/dist/{index-BN6i2Nfg.d.ts → index-N0_c4bHQ.d.ts} +45 -45
  23. package/dist/index.d.ts +233 -160
  24. package/dist/index.js +825 -160
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +9 -9
  27. package/dist/infrastructure/index.js +29 -7
  28. package/dist/infrastructure/index.js.map +1 -1
  29. package/dist/kernel/index.d.ts +14 -14
  30. package/dist/kernel/index.js +7 -0
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/logger-B72yyPc6.d.ts +12 -0
  33. package/dist/{logger-bOzkF5LL.d.ts → logger-C_27pj9i.d.ts} +12 -4
  34. package/dist/{mcp-servers-T0O6UN_w.d.ts → mcp-servers-Dck3T85_.d.ts} +20 -20
  35. package/dist/{mode-BO4SEUIv.d.ts → mode-CHo2XtHs.d.ts} +4 -4
  36. package/dist/models/index.d.ts +10 -10
  37. package/dist/models/index.js +8 -2
  38. package/dist/models/index.js.map +1 -1
  39. package/dist/{models-registry-BcYJDKLm.d.ts → models-registry-Be3osGt5.d.ts} +28 -28
  40. package/dist/{models-registry-Cuq1C8V9.d.ts → models-registry-Boz639EI.d.ts} +12 -12
  41. package/dist/{multi-agent-coordinator-BSBbZt0e.d.ts → multi-agent-coordinator-DllpCVkF.d.ts} +12 -12
  42. package/dist/{null-fleet-bus-BCIRT_nV.d.ts → null-fleet-bus-BY0AN-sr.d.ts} +129 -120
  43. package/dist/observability/index.d.ts +41 -41
  44. package/dist/observability/index.js.map +1 -1
  45. package/dist/{observability-BhnVLBLS.d.ts → observability-CoSNZdhX.d.ts} +4 -4
  46. package/dist/{parallel-eternal-engine-CjAYGaCw.d.ts → parallel-eternal-engine-D402RASp.d.ts} +49 -49
  47. package/dist/{path-resolver-BnqXa9Ze.d.ts → path-resolver-UPFTsDyD.d.ts} +6 -6
  48. package/dist/{permission-V5BLOrY6.d.ts → permission-14CChMmO.d.ts} +10 -8
  49. package/dist/{permission-policy-CBVx-d-8.d.ts → permission-policy-gW5htOo1.d.ts} +7 -7
  50. package/dist/{plan-templates-DBgrTGPu.d.ts → plan-templates-DRvPgkfZ.d.ts} +70 -32
  51. package/dist/{provider-runner-n3KkHT_w.d.ts → provider-runner-COAJM8tC.d.ts} +6 -6
  52. package/dist/{retry-policy-CG3qvH_e.d.ts → retry-policy-DSu6O6rD.d.ts} +4 -4
  53. package/dist/sdd/index.d.ts +47 -47
  54. package/dist/sdd/index.js +47 -22
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/security/index.d.ts +6 -6
  57. package/dist/security/index.js +7 -1
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-RvBR_YRW.d.ts → selector-11-fm95U.d.ts} +2 -2
  60. package/dist/{session-event-bridge-CDHxcmQU.d.ts → session-event-bridge-D0u-x576.d.ts} +7 -7
  61. package/dist/{session-reader-BIpwM60D.d.ts → session-reader-BQU-toaN.d.ts} +23 -23
  62. package/dist/{skill-CxuWrsKK.d.ts → skill-BJeF2DwY.d.ts} +1 -1
  63. package/dist/skills/index.d.ts +9 -9
  64. package/dist/skills/index.js +15 -3
  65. package/dist/skills/index.js.map +1 -1
  66. package/dist/storage/index.d.ts +15 -15
  67. package/dist/storage/index.js +398 -80
  68. package/dist/storage/index.js.map +1 -1
  69. package/dist/{system-prompt-CA11g6Jo.d.ts → system-prompt-C0rLCeyn.d.ts} +16 -11
  70. package/dist/{task-graph-D1YQbpxF.d.ts → task-graph-CikNdRTG.d.ts} +22 -22
  71. package/dist/types/index.d.ts +25 -25
  72. package/dist/types/index.js +61 -12
  73. package/dist/types/index.js.map +1 -1
  74. package/dist/utils/index.d.ts +46 -45
  75. package/dist/utils/index.js +64 -13
  76. package/dist/utils/index.js.map +1 -1
  77. package/dist/{wstack-paths-eMXnY1_X.d.ts → wstack-paths-BQMvEllz.d.ts} +10 -3
  78. package/package.json +1 -1
  79. package/dist/logger-DDd5C--Z.d.ts +0 -12
@@ -1,7 +1,7 @@
1
1
  import * as crypto2 from 'crypto';
2
2
  import { randomBytes, randomUUID, createCipheriv, createDecipheriv, createHash } from 'crypto';
3
3
  import * as fsp from 'fs/promises';
4
- import * as path16 from 'path';
4
+ import * as path3 from 'path';
5
5
  import { isAbsolute, resolve } from 'path';
6
6
  import * as fs5 from 'fs';
7
7
  import * as os from 'os';
@@ -32,9 +32,9 @@ __export(atomic_write_exports, {
32
32
  ensureDir: () => ensureDir
33
33
  });
34
34
  async function atomicWrite(targetPath, content, opts = {}) {
35
- const dir = path16.dirname(targetPath);
35
+ const dir = path3.dirname(targetPath);
36
36
  await fsp.mkdir(dir, { recursive: true });
37
- const tmp = path16.join(dir, `.${path16.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
37
+ const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
38
38
  try {
39
39
  if (typeof content === "string") {
40
40
  await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -108,8 +108,10 @@ function isStdoutTTY() {
108
108
  }
109
109
  function writeTo(s, stream) {
110
110
  if (!stream || typeof stream.write !== "function") return false;
111
- stream.write(s);
112
- return true;
111
+ {
112
+ stream.write(s);
113
+ return true;
114
+ }
113
115
  }
114
116
  function writeErr(s, stream = process.stderr) {
115
117
  return writeTo(s, stream);
@@ -162,14 +164,16 @@ var DefaultLogger = class _DefaultLogger {
162
164
  file;
163
165
  bindings;
164
166
  pretty;
167
+ stderr;
165
168
  constructor(opts = {}) {
166
169
  this.level = opts.level ?? process.env.WRONGSTACK_LOG_LEVEL ?? "info";
167
170
  this.file = opts.file;
168
171
  this.bindings = opts.bindings ?? {};
169
172
  this.pretty = opts.pretty ?? true;
173
+ this.stderr = opts.stderr !== false;
170
174
  if (this.file) {
171
175
  try {
172
- fs5.mkdirSync(path16.dirname(this.file), { recursive: true });
176
+ fs5.mkdirSync(path3.dirname(this.file), { recursive: true });
173
177
  } catch {
174
178
  }
175
179
  }
@@ -194,6 +198,7 @@ var DefaultLogger = class _DefaultLogger {
194
198
  level: this.level,
195
199
  file: this.file,
196
200
  pretty: this.pretty,
201
+ stderr: this.stderr,
197
202
  bindings: { ...this.bindings, ...bindings }
198
203
  });
199
204
  }
@@ -213,6 +218,7 @@ var DefaultLogger = class _DefaultLogger {
213
218
  } catch {
214
219
  }
215
220
  }
221
+ if (!this.stderr) return;
216
222
  if (r <= LEVEL_RANK.warn || this.level === "debug" || this.level === "trace") {
217
223
  const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
218
224
  if (ctx !== void 0) {
@@ -239,6 +245,12 @@ function formatCtx(ctx) {
239
245
  init_atomic_write();
240
246
 
241
247
  // src/utils/message-invariants.ts
248
+ function expectDefined(value) {
249
+ if (value === null || value === void 0) {
250
+ throw new Error("Expected value to be defined");
251
+ }
252
+ return value;
253
+ }
242
254
  function repairToolUseAdjacency(messages) {
243
255
  const removedToolUses = [];
244
256
  const removedToolResults = [];
@@ -246,7 +258,7 @@ function repairToolUseAdjacency(messages) {
246
258
  let changed = false;
247
259
  const out = [];
248
260
  for (let i = 0; i < messages.length; i++) {
249
- const original = messages[i];
261
+ const original = expectDefined(messages[i]);
250
262
  let msg = original;
251
263
  if (hasToolUse(msg)) {
252
264
  const nextIds = toolResultIds(messages[i + 1]);
@@ -331,7 +343,23 @@ function isEmptyMessage(msg) {
331
343
  }
332
344
 
333
345
  // src/storage/session-store.ts
334
- var DefaultSessionStore = class {
346
+ function expectDefined2(value) {
347
+ if (value === null || value === void 0) {
348
+ throw new Error("Expected value to be defined");
349
+ }
350
+ return value;
351
+ }
352
+ function sanitizeModel(model) {
353
+ return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
354
+ }
355
+ function generateSessionId(startedAt, model) {
356
+ const date = startedAt.slice(0, 10);
357
+ const time = startedAt.slice(11, 19).replace(/:/g, "-");
358
+ const suffix = randomBytes(2).toString("hex");
359
+ const modelPart = model ? `_${sanitizeModel(model)}` : "";
360
+ return `${date}/${time}Z${modelPart}_${suffix}`;
361
+ }
362
+ var DefaultSessionStore = class _DefaultSessionStore {
335
363
  dir;
336
364
  events;
337
365
  secretScrubber;
@@ -340,19 +368,29 @@ var DefaultSessionStore = class {
340
368
  this.events = opts.events;
341
369
  this.secretScrubber = opts.secretScrubber;
342
370
  }
371
+ /** Absolute path to the session index file. */
372
+ get indexFile() {
373
+ return path3.join(this.dir, "_index.jsonl");
374
+ }
343
375
  /** Join session ID to its absolute path within the store directory. */
344
376
  sessionPath(id, ext) {
345
- return path16.join(this.dir, `${id}${ext}`);
377
+ return path3.join(this.dir, `${id}${ext}`);
346
378
  }
347
- async ensureShardDir(_id) {
348
- await ensureDir(this.dir);
349
- return this.dir;
379
+ /**
380
+ * Ensure the directory implied by the session ID exists. When the ID
381
+ * contains a date prefix like `2026-06-06/...`, this creates the date
382
+ * subdirectory so sessions group naturally by day.
383
+ */
384
+ async ensureShardDir(id) {
385
+ const dirPath = path3.dirname(path3.join(this.dir, id));
386
+ await ensureDir(dirPath);
387
+ return dirPath;
350
388
  }
351
389
  async create(meta) {
352
390
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
353
- const id = meta.id ?? `${startedAt.replace(/[:.]/g, "-")}-${randomBytes(2).toString("hex")}`;
391
+ const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
354
392
  const shardDir = await this.ensureShardDir(id);
355
- const file = path16.join(shardDir, `${id}.jsonl`);
393
+ const file = path3.join(shardDir, `${path3.basename(id)}.jsonl`);
356
394
  let handle;
357
395
  try {
358
396
  handle = await fsp.open(file, "a", 384);
@@ -366,7 +404,8 @@ var DefaultSessionStore = class {
366
404
  return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
367
405
  dir: shardDir,
368
406
  filePath: file,
369
- secretScrubber: this.secretScrubber
407
+ secretScrubber: this.secretScrubber,
408
+ onClose: (s) => this.appendToIndex(s)
370
409
  });
371
410
  } catch (err) {
372
411
  await handle.close().catch(() => {
@@ -397,7 +436,7 @@ var DefaultSessionStore = class {
397
436
  provider: data.metadata.provider
398
437
  },
399
438
  this.events,
400
- { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
439
+ { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber, onClose: (s) => this.appendToIndex(s) }
401
440
  );
402
441
  return { writer, data };
403
442
  } catch (err) {
@@ -427,6 +466,15 @@ var DefaultSessionStore = class {
427
466
  async list(limit = 20) {
428
467
  try {
429
468
  await ensureDir(this.dir);
469
+ const indexed = await this.readIndex();
470
+ if (indexed.length > 0) {
471
+ indexed.sort((a, b) => {
472
+ if (a.startedAt < b.startedAt) return 1;
473
+ if (a.startedAt > b.startedAt) return -1;
474
+ return a.id.localeCompare(b.id);
475
+ });
476
+ return indexed.slice(0, limit);
477
+ }
430
478
  const ids = await this.collectSessionIds(this.dir);
431
479
  const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
432
480
  const out = sessions.filter((s) => s !== null);
@@ -440,16 +488,121 @@ var DefaultSessionStore = class {
440
488
  return [];
441
489
  }
442
490
  }
443
- /** Recursively collect all session IDs from shard subdirectories. */
444
- async collectSessionIds(dir) {
491
+ // ── Session index (_index.jsonl) ─────────────────────────────────────────
492
+ //
493
+ // One JSON line per closed session, appended atomically on close().
494
+ // When a session is deleted, a tombstone {action:"delete",id:"..."} is
495
+ // appended. On read, tombstones filter out matching session entries.
496
+ // This keeps listing O(lines-in-index) instead of O(files-on-disk).
497
+ //
498
+ // The index auto-compacts every N appends to prevent unbounded growth
499
+ // from tombstones and duplicate entries (resume cycles).
500
+ indexAppendCount = 0;
501
+ static COMPACT_EVERY = 30;
502
+ /** Append a session summary to the index. */
503
+ async appendToIndex(summary) {
504
+ try {
505
+ await ensureDir(this.dir);
506
+ const line = JSON.stringify(summary) + "\n";
507
+ await fsp.appendFile(this.indexFile, line, "utf8");
508
+ this.indexAppendCount++;
509
+ if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
510
+ await this.compactIndex();
511
+ this.indexAppendCount = 0;
512
+ }
513
+ } catch {
514
+ }
515
+ }
516
+ /** Append a tombstone entry for a deleted session. */
517
+ async writeTombstone(id) {
518
+ try {
519
+ await ensureDir(this.dir);
520
+ const line = JSON.stringify({ action: "delete", id }) + "\n";
521
+ await fsp.appendFile(this.indexFile, line, "utf8");
522
+ this.indexAppendCount++;
523
+ } catch {
524
+ }
525
+ }
526
+ /**
527
+ * Compact the index: read all entries, drop tombstones, deduplicate
528
+ * (keep latest per session), and rewrite. Atomic via temp+rename.
529
+ */
530
+ async compactIndex() {
531
+ const entries = await this.readIndex();
532
+ if (entries.length === 0) return;
533
+ const tmp = `${this.indexFile}.compact.tmp`;
534
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
535
+ await fsp.writeFile(tmp, lines, "utf8");
536
+ await fsp.rename(tmp, this.indexFile);
537
+ }
538
+ /**
539
+ * Read the index file and return deduplicated session summaries.
540
+ * Entries with a matching tombstone are filtered out.
541
+ * Returns empty array when the index doesn't exist or is corrupt.
542
+ */
543
+ async readIndex() {
544
+ let raw;
545
+ try {
546
+ raw = await fsp.readFile(this.indexFile, "utf8");
547
+ } catch {
548
+ return [];
549
+ }
550
+ const deleted = /* @__PURE__ */ new Set();
551
+ const seen = /* @__PURE__ */ new Map();
552
+ for (const line of raw.split("\n")) {
553
+ if (!line.trim()) continue;
554
+ try {
555
+ const entry = JSON.parse(line);
556
+ if (entry.action === "delete" && entry.id) {
557
+ deleted.add(entry.id);
558
+ seen.delete(entry.id);
559
+ continue;
560
+ }
561
+ if (entry.id && !deleted.has(entry.id)) {
562
+ seen.set(entry.id, entry);
563
+ }
564
+ } catch {
565
+ }
566
+ }
567
+ return Array.from(seen.values());
568
+ }
569
+ /**
570
+ * Rebuild the index from disk by scanning all sessions and writing a
571
+ * fresh _index.jsonl. Useful after manual cleanup or index corruption.
572
+ */
573
+ async rebuildIndex() {
574
+ const ids = await this.collectSessionIds(this.dir);
575
+ const summaries = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
576
+ const valid = summaries.filter((s) => s !== null);
577
+ const tmp = `${this.indexFile}.tmp`;
578
+ const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
579
+ await fsp.writeFile(tmp, lines, "utf8");
580
+ await fsp.rename(tmp, this.indexFile);
581
+ return valid.length;
582
+ }
583
+ /** Recursively collect session IDs from date-shard subdirectories.
584
+ * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
585
+ * Skips `.jsonl`/`.summary.json` root files, dot-files, and
586
+ * sub-directories that belong to fleet/subagent sessions. */
587
+ async collectSessionIds(dir, prefix = "", depth = 0) {
445
588
  const ids = [];
446
- const entries = await fsp.readdir(dir, { withFileTypes: true });
589
+ let entries;
590
+ try {
591
+ entries = await fsp.readdir(dir, { withFileTypes: true });
592
+ } catch {
593
+ return ids;
594
+ }
447
595
  for (const entry of entries) {
448
- const full = path16.join(dir, entry.name);
596
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
597
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
598
+ continue;
449
599
  if (entry.isDirectory()) {
450
- ids.push(...await this.collectSessionIds(full));
600
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
601
+ ids.push(...await this.collectSessionIds(path3.join(dir, entry.name), childPrefix, depth + 1));
451
602
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
452
- ids.push(entry.name.replace(/\.jsonl$/, ""));
603
+ if (entry.name === "_index.jsonl") continue;
604
+ const base = entry.name.replace(/\.jsonl$/, "");
605
+ ids.push(prefix ? `${prefix}/${base}` : base);
453
606
  }
454
607
  }
455
608
  return ids;
@@ -472,9 +625,70 @@ var DefaultSessionStore = class {
472
625
  return summary;
473
626
  }
474
627
  }
475
- async delete(id) {
476
- await fsp.unlink(this.sessionPath(id, ".jsonl"));
628
+ /**
629
+ * Delete a session and all associated files: JSONL, summary, plan/todos
630
+ * sidecars, and the session directory (fleet.json, shared/, subagents/).
631
+ */
632
+ async deleteSession(id) {
633
+ await fsp.unlink(this.sessionPath(id, ".jsonl")).catch(() => void 0);
477
634
  await fsp.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
635
+ const shardDir = path3.dirname(path3.join(this.dir, id));
636
+ const base = path3.basename(id);
637
+ for (const ext of [".plan.json", ".todos.json"]) {
638
+ await fsp.unlink(path3.join(shardDir, `${base}${ext}`)).catch(() => void 0);
639
+ }
640
+ const sessDir = path3.join(shardDir, base);
641
+ await fsp.rm(sessDir, { recursive: true, force: true }).catch(() => void 0);
642
+ await this.writeTombstone(id);
643
+ }
644
+ async delete(id) {
645
+ await this.deleteSession(id);
646
+ }
647
+ async prune(maxAgeDays = 30) {
648
+ const cutoff = Date.now() - maxAgeDays * 864e5;
649
+ let deleted = 0;
650
+ let activeSessionId = null;
651
+ try {
652
+ const raw = await fsp.readFile(path3.join(this.dir, "active.json"), "utf8");
653
+ const active = JSON.parse(raw);
654
+ activeSessionId = active.sessionId ?? null;
655
+ } catch {
656
+ }
657
+ const entries = await fsp.readdir(this.dir, { withFileTypes: true }).catch(() => []);
658
+ for (const entry of entries) {
659
+ if (!entry.isDirectory()) continue;
660
+ const dateDir = path3.join(this.dir, entry.name);
661
+ const files = await fsp.readdir(dateDir, { withFileTypes: true }).catch(() => []);
662
+ for (const file of files) {
663
+ if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
664
+ const jsonlPath = path3.join(dateDir, file.name);
665
+ try {
666
+ const stat5 = await fsp.stat(jsonlPath);
667
+ if (stat5.mtimeMs >= cutoff) continue;
668
+ } catch {
669
+ continue;
670
+ }
671
+ const id = `${entry.name}/${file.name.replace(/\.jsonl$/, "")}`;
672
+ if (activeSessionId && id === activeSessionId) continue;
673
+ await this.deleteSession(id);
674
+ deleted++;
675
+ }
676
+ }
677
+ if (deleted > 0) {
678
+ await this.compactIndex().catch(() => void 0);
679
+ }
680
+ for (const entry of entries) {
681
+ if (!entry.isDirectory()) continue;
682
+ const dateDir = path3.join(this.dir, entry.name);
683
+ try {
684
+ const remaining = await fsp.readdir(dateDir);
685
+ if (remaining.length === 0) {
686
+ await fsp.rmdir(dateDir).catch(() => void 0);
687
+ }
688
+ } catch {
689
+ }
690
+ }
691
+ return deleted;
478
692
  }
479
693
  async clearHistory(id) {
480
694
  await this.ensureShardDir(id);
@@ -496,13 +710,42 @@ var DefaultSessionStore = class {
496
710
  const data = await this.load(id);
497
711
  const firstUser = data.events.find((e) => e.type === "user_input");
498
712
  const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
713
+ let iterationCount = 0;
714
+ let toolCallCount = 0;
715
+ let toolErrorCount = 0;
716
+ let fileChangeCount = 0;
717
+ const toolBreakdown = {};
718
+ let outcome = void 0;
719
+ const lastEvent = data.events[data.events.length - 1];
720
+ for (const e of data.events) {
721
+ if (e.type === "in_flight_start") iterationCount++;
722
+ else if (e.type === "tool_call_start") {
723
+ toolCallCount++;
724
+ toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
725
+ } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
726
+ else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
727
+ }
728
+ if (lastEvent?.type === "session_end") {
729
+ outcome = "completed";
730
+ } else if (lastEvent?.type === "in_flight_start") {
731
+ outcome = "aborted";
732
+ } else if (data.events.some((e) => e.type === "error")) {
733
+ outcome = "error";
734
+ }
499
735
  return {
500
736
  id,
501
737
  title,
502
738
  startedAt: data.metadata.startedAt,
739
+ endedAt: data.metadata.endedAt,
503
740
  model: data.metadata.model ?? "unknown",
504
741
  provider: data.metadata.provider ?? "unknown",
505
- tokenTotal: data.usage.input + data.usage.output
742
+ tokenTotal: data.usage.input + data.usage.output,
743
+ iterationCount: iterationCount > 0 ? iterationCount : void 0,
744
+ toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
745
+ toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
746
+ fileChangeCount: fileChangeCount > 0 ? fileChangeCount : void 0,
747
+ toolBreakdown: Object.keys(toolBreakdown).length > 0 ? toolBreakdown : {},
748
+ outcome
506
749
  };
507
750
  } catch {
508
751
  return {
@@ -601,9 +844,10 @@ var FileSessionWriter = class {
601
844
  this.meta = meta;
602
845
  this.events = events;
603
846
  this.resumed = opts.resumed ?? false;
604
- this.manifestFile = opts.dir ? path16.join(opts.dir, `${id}.summary.json`) : "";
847
+ this.manifestFile = opts.dir ? path3.join(opts.dir, `${path3.basename(id)}.summary.json`) : "";
605
848
  this.filePath = opts.filePath ?? "";
606
849
  this.secretScrubber = opts.secretScrubber;
850
+ this.onCloseCb = opts.onClose;
607
851
  this.summary = {
608
852
  id,
609
853
  title: "(empty session)",
@@ -633,6 +877,15 @@ var FileSessionWriter = class {
633
877
  appendFailCount = 0;
634
878
  lastAppendWarnAt = 0;
635
879
  secretScrubber;
880
+ onCloseCb;
881
+ // ── Enriched summary tracking ──────────────────────────────────────────
882
+ iterationCount = 0;
883
+ toolCallCount = 0;
884
+ toolErrorCount = 0;
885
+ toolBreakdown = {};
886
+ fileChangeCount = 0;
887
+ compactionCount = 0;
888
+ outcome = void 0;
636
889
  /**
637
890
  * Scrub secrets out of conversation-turn events before they are observed
638
891
  * for the summary, written to the JSONL log, or surfaced on resume. Only
@@ -710,8 +963,22 @@ var FileSessionWriter = class {
710
963
  observeForSummary(event) {
711
964
  if (event.type === "tool_use") {
712
965
  this.openToolUses.add(event.id);
966
+ } else if (event.type === "tool_call_start") {
967
+ this.toolCallCount++;
968
+ this.toolBreakdown[event.name] = (this.toolBreakdown[event.name] ?? 0) + 1;
713
969
  } else if (event.type === "tool_result") {
714
970
  this.openToolUses.delete(event.id);
971
+ if (event.isError) {
972
+ this.toolErrorCount++;
973
+ this.outcome = "error";
974
+ }
975
+ } else if (event.type === "file_snapshot") {
976
+ this.fileChangeCount += event.files.length;
977
+ } else if (event.type === "compaction") {
978
+ this.compactionCount++;
979
+ }
980
+ if (event.type === "error" || event.type === "provider_error") {
981
+ this.outcome = "error";
715
982
  }
716
983
  if (event.type === "user_input" && this.summary.title === "(empty session)") {
717
984
  this.summary = { ...this.summary, title: userInputTitle(event.content) };
@@ -722,18 +989,35 @@ var FileSessionWriter = class {
722
989
  } else if (event.type === "session_end") {
723
990
  const total = event.usage.input + event.usage.output;
724
991
  if (total > 0) this.summary = { ...this.summary, tokenTotal: total };
992
+ } else if (event.type === "in_flight_start") {
993
+ this.iterationCount++;
725
994
  }
726
995
  }
727
996
  async close() {
728
997
  if (this.closing) return;
729
998
  this.closing = true;
730
999
  this.closed = true;
1000
+ this.summary = {
1001
+ ...this.summary,
1002
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1003
+ iterationCount: this.iterationCount,
1004
+ toolCallCount: this.toolCallCount,
1005
+ toolErrorCount: this.toolErrorCount,
1006
+ fileChangeCount: this.fileChangeCount,
1007
+ compactionCount: this.compactionCount > 0 ? this.compactionCount : void 0,
1008
+ toolBreakdown: { ...this.toolBreakdown },
1009
+ outcome: this.outcome ?? "completed"
1010
+ };
731
1011
  if (this.manifestFile) {
732
1012
  try {
733
1013
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
734
1014
  } catch {
735
1015
  }
736
1016
  }
1017
+ try {
1018
+ await this.onCloseCb?.(this.summary);
1019
+ } catch {
1020
+ }
737
1021
  try {
738
1022
  await this.handle.close();
739
1023
  } catch {
@@ -775,7 +1059,7 @@ var FileSessionWriter = class {
775
1059
  let targetCheckpointLine = -1;
776
1060
  let afterTarget = false;
777
1061
  for (let i = 0; i < lines.length; i++) {
778
- const line = lines[i];
1062
+ const line = expectDefined2(lines[i]);
779
1063
  if (!line.trim()) continue;
780
1064
  let event;
781
1065
  try {
@@ -883,7 +1167,7 @@ init_atomic_write();
883
1167
  var QueueStore = class {
884
1168
  file;
885
1169
  constructor(opts) {
886
- this.file = path16.join(opts.dir, "queue.json");
1170
+ this.file = path3.join(opts.dir, "queue.json");
887
1171
  }
888
1172
  async write(items) {
889
1173
  if (items.length === 0) {
@@ -953,7 +1237,7 @@ var DefaultAttachmentStore = class {
953
1237
  let data = input.data;
954
1238
  if (this.spoolDir && bytes >= this.spoolThreshold) {
955
1239
  await fsp.mkdir(this.spoolDir, { recursive: true });
956
- spooledPath = path16.join(this.spoolDir, `${id}.bin`);
1240
+ spooledPath = path3.join(this.spoolDir, `${id}.bin`);
957
1241
  await atomicWrite(spooledPath, input.data, {
958
1242
  encoding: input.kind === "image" ? "base64" : "utf8"
959
1243
  });
@@ -1141,7 +1425,7 @@ ${body.trim()}`);
1141
1425
  async remember(text, scope = "project-memory") {
1142
1426
  return this.runSerialized(scope, async () => {
1143
1427
  const file = this.files[scope];
1144
- await ensureDir(path16.dirname(file));
1428
+ await ensureDir(path3.dirname(file));
1145
1429
  let existing = "";
1146
1430
  try {
1147
1431
  existing = await fsp.readFile(file, "utf8");
@@ -1789,7 +2073,7 @@ var RecoveryLock = class {
1789
2073
  sessionStore;
1790
2074
  probe;
1791
2075
  constructor(opts) {
1792
- this.file = path16.join(opts.dir, LOCK_FILE);
2076
+ this.file = path3.join(opts.dir, LOCK_FILE);
1793
2077
  this.pid = opts.pid ?? process.pid;
1794
2078
  this.hostname = opts.hostname ?? os.hostname();
1795
2079
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -1847,7 +2131,7 @@ var RecoveryLock = class {
1847
2131
  * null return before calling this.
1848
2132
  */
1849
2133
  async write(sessionId) {
1850
- await ensureDir(path16.dirname(this.file));
2134
+ await ensureDir(path3.dirname(this.file));
1851
2135
  const lock = {
1852
2136
  v: 1,
1853
2137
  sessionId,
@@ -1951,6 +2235,12 @@ function compileUserRegex(pattern, flags) {
1951
2235
  }
1952
2236
 
1953
2237
  // src/storage/session-reader.ts
2238
+ function expectDefined3(value) {
2239
+ if (value === null || value === void 0) {
2240
+ throw new Error("Expected value to be defined");
2241
+ }
2242
+ return value;
2243
+ }
1954
2244
  var DefaultSessionReader = class {
1955
2245
  store;
1956
2246
  constructor(opts) {
@@ -2012,7 +2302,7 @@ var DefaultSessionReader = class {
2012
2302
  continue;
2013
2303
  }
2014
2304
  for (let i = 0; i < data.events.length; i++) {
2015
- const ev = data.events[i];
2305
+ const ev = expectDefined3(data.events[i]);
2016
2306
  if (allowedTypes && !allowedTypes.has(ev.type)) continue;
2017
2307
  const text = eventText(ev);
2018
2308
  if (text === null) continue;
@@ -2317,8 +2607,11 @@ var SessionAnalyzer = class {
2317
2607
  }
2318
2608
  calcDuration(events) {
2319
2609
  if (events.length < 2) return 0;
2320
- const first = new Date(events[0].ts).getTime();
2321
- const last = new Date(events[events.length - 1].ts).getTime();
2610
+ const firstEvent = events[0];
2611
+ const lastEvent = events[events.length - 1];
2612
+ if (!firstEvent || !lastEvent) return 0;
2613
+ const first = new Date(firstEvent.ts).getTime();
2614
+ const last = new Date(lastEvent.ts).getTime();
2322
2615
  return last - first;
2323
2616
  }
2324
2617
  };
@@ -2431,7 +2724,7 @@ async function loadTodosCheckpoint(filePath) {
2431
2724
  const parsed = JSON.parse(raw);
2432
2725
  if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
2433
2726
  return parsed.todos.filter(
2434
- (t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string"
2727
+ (t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string" && (t.activeForm === void 0 || typeof t.activeForm === "string")
2435
2728
  );
2436
2729
  } catch {
2437
2730
  return null;
@@ -3074,7 +3367,7 @@ var DefaultSecretVault = class {
3074
3367
  } catch (err) {
3075
3368
  if (err.code !== "ENOENT") throw err;
3076
3369
  }
3077
- fs5.mkdirSync(path16.dirname(this.keyFile), { recursive: true });
3370
+ fs5.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
3078
3371
  const key = randomBytes(KEY_BYTES);
3079
3372
  try {
3080
3373
  fs5.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
@@ -3143,7 +3436,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
3143
3436
  }
3144
3437
  const merged = deepMerge2(current, patch ?? {});
3145
3438
  const encrypted = encryptConfigSecrets(merged, vault);
3146
- await fsp.mkdir(path16.dirname(configPath), { recursive: true });
3439
+ await fsp.mkdir(path3.dirname(configPath), { recursive: true });
3147
3440
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3148
3441
  await restrictFilePermissions(configPath);
3149
3442
  }
@@ -3271,6 +3564,12 @@ function getDangerousCapabilities(toolOrCaps) {
3271
3564
  init_atomic_write();
3272
3565
 
3273
3566
  // src/utils/glob-match.ts
3567
+ function expectDefined4(value) {
3568
+ if (value === null || value === void 0) {
3569
+ throw new Error("Expected value to be defined");
3570
+ }
3571
+ return value;
3572
+ }
3274
3573
  function escapeRegex(s) {
3275
3574
  return s.replace(/[.+^${}()|\\]/g, "\\$&");
3276
3575
  }
@@ -3282,7 +3581,7 @@ function getCachedGlob(pattern) {
3282
3581
  if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
3283
3582
  const keys = [...COMPILED_GLOB_CACHE.keys()];
3284
3583
  for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
3285
- COMPILED_GLOB_CACHE.delete(keys[i]);
3584
+ COMPILED_GLOB_CACHE.delete(expectDefined4(keys[i]));
3286
3585
  }
3287
3586
  }
3288
3587
  const re = compileGlob(pattern);
@@ -3367,9 +3666,9 @@ function getInputString(input, key) {
3367
3666
  function pathLooksInsideProject(rawPath, projectRoot) {
3368
3667
  if (!projectRoot) return false;
3369
3668
  if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
3370
- const resolved = path16.resolve(projectRoot, rawPath);
3371
- const relative2 = path16.relative(projectRoot, resolved);
3372
- return !!relative2 && !relative2.startsWith("..") && !path16.isAbsolute(relative2);
3669
+ const resolved = path3.resolve(projectRoot, rawPath);
3670
+ const relative2 = path3.relative(projectRoot, resolved);
3671
+ return !!relative2 && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
3373
3672
  }
3374
3673
  function tokenizeShell(command) {
3375
3674
  return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
@@ -3379,7 +3678,7 @@ function pathTokenIsOutsideProject(token, projectRoot) {
3379
3678
  if (token === "/" || token === "~" || token === "." || token === "..") return token !== ".";
3380
3679
  if (token.includes("*")) return true;
3381
3680
  if (token.startsWith("..") || token.includes("../") || token.includes("..\\")) return true;
3382
- if (path16.isAbsolute(token) || token.startsWith("~/")) return !pathLooksInsideProject(token, projectRoot);
3681
+ if (path3.isAbsolute(token) || token.startsWith("~/")) return !pathLooksInsideProject(token, projectRoot);
3383
3682
  return false;
3384
3683
  }
3385
3684
  function hasDangerousDeleteTarget(tokens, start, projectRoot) {
@@ -4035,7 +4334,7 @@ var DefaultSkillLoader = class {
4035
4334
  const entries = await fsp.readdir(dir, { withFileTypes: true });
4036
4335
  for (const e of entries) {
4037
4336
  if (!e.isDirectory()) continue;
4038
- const skillFile = path16.join(dir, e.name, "SKILL.md");
4337
+ const skillFile = path3.join(dir, e.name, "SKILL.md");
4039
4338
  try {
4040
4339
  const raw = await fsp.readFile(skillFile, "utf8");
4041
4340
  const meta = parseFrontmatter(raw);
@@ -4140,13 +4439,19 @@ function parseDescription(raw) {
4140
4439
  const scope = [];
4141
4440
  const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
4142
4441
  if (coversMatch) {
4143
- const items = coversMatch[1].replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
4442
+ const items = coversMatch[1] ?? "".replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
4144
4443
  scope.push(...items);
4145
4444
  }
4146
4445
  return { trigger, scope };
4147
4446
  }
4148
4447
 
4149
4448
  // src/utils/json-repair.ts
4449
+ function expectDefined5(value) {
4450
+ if (value === null || value === void 0) {
4451
+ throw new Error("Expected value to be defined");
4452
+ }
4453
+ return value;
4454
+ }
4150
4455
  function completePartialObject(s) {
4151
4456
  if (!s.trim().startsWith("{")) return s;
4152
4457
  if (tryParse(s).ok) return s;
@@ -4158,7 +4463,7 @@ function completePartialObject(s) {
4158
4463
  let contentEnd = 0;
4159
4464
  let stringBraceDepth = 0;
4160
4465
  for (let i = 0; i < s.length; i++) {
4161
- const ch = s[i];
4466
+ const ch = expectDefined5(s[i]);
4162
4467
  if (inString) {
4163
4468
  contentEnd = i + 1;
4164
4469
  if (escaped) {
@@ -4543,6 +4848,12 @@ var DefaultProviderRunner = class {
4543
4848
  };
4544
4849
 
4545
4850
  // src/utils/token-estimate.ts
4851
+ function expectDefined6(value) {
4852
+ if (value === null || value === void 0) {
4853
+ throw new Error("Expected value to be defined");
4854
+ }
4855
+ return value;
4856
+ }
4546
4857
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
4547
4858
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
4548
4859
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
@@ -4552,7 +4863,7 @@ function getCachedEstimate(key, compute) {
4552
4863
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
4553
4864
  const keys = [...ESTIMATE_CACHE.keys()];
4554
4865
  for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
4555
- ESTIMATE_CACHE.delete(keys[i]);
4866
+ ESTIMATE_CACHE.delete(expectDefined6(keys[i]));
4556
4867
  }
4557
4868
  }
4558
4869
  const estimate = compute();
@@ -4942,7 +5253,7 @@ var IntelligentCompactor = class {
4942
5253
  maxTokens: 1024
4943
5254
  };
4944
5255
  const ac = ctx.signal ? void 0 : new AbortController();
4945
- const signal = ctx.signal ?? ac.signal;
5256
+ const signal = ctx.signal ?? ac?.signal;
4946
5257
  const res = await this.provider.complete(req, { signal });
4947
5258
  const textBlocks = res.content.filter(isTextBlock);
4948
5259
  return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
@@ -5045,6 +5356,12 @@ var IntelligentCompactor = class {
5045
5356
  };
5046
5357
 
5047
5358
  // src/models/llm-selector.ts
5359
+ function expectDefined7(value) {
5360
+ if (value === null || value === void 0) {
5361
+ throw new Error("Expected value to be defined");
5362
+ }
5363
+ return value;
5364
+ }
5048
5365
  var DEFAULT_SYSTEM_PROMPT = `You are a context pruning assistant. Given a conversation history and a token budget, decide which message ranges are worth keeping verbatim and which should be collapsed into summaries.
5049
5366
 
5050
5367
  Output a JSON object with this structure:
@@ -5085,7 +5402,7 @@ function formatMessages(messages, maxChars = 8e3) {
5085
5402
  const lines = [];
5086
5403
  let used = 0;
5087
5404
  for (let i = 0; i < messages.length; i++) {
5088
- const m = messages[i];
5405
+ const m = expectDefined7(messages[i]);
5089
5406
  const role = m.role.padEnd(10, " ");
5090
5407
  let text;
5091
5408
  if (typeof m.content === "string") {
@@ -5150,7 +5467,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
5150
5467
  let tokenCount = 0;
5151
5468
  let startIdx = 0;
5152
5469
  for (let i = messages.length - 1; i >= 0; i--) {
5153
- const m = messages[i];
5470
+ const m = expectDefined7(messages[i]);
5154
5471
  const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
5155
5472
  (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
5156
5473
  0
@@ -5361,6 +5678,7 @@ Summarize the following message range:`;
5361
5678
  let boundary = preserveIdx;
5362
5679
  for (let i = preserveIdx; i < messages.length && i < preserveIdx + 6; i++) {
5363
5680
  const m = messages[i];
5681
+ if (!m) continue;
5364
5682
  if (m.role === "user" && this.hasTextContent(m)) {
5365
5683
  boundary = i;
5366
5684
  break;
@@ -5756,6 +6074,12 @@ function createToolOutputSerializer(opts = {}) {
5756
6074
  }
5757
6075
 
5758
6076
  // src/execution/tool-executor.ts
6077
+ function expectDefined8(value) {
6078
+ if (value === null || value === void 0) {
6079
+ throw new Error("Expected value to be defined");
6080
+ }
6081
+ return value;
6082
+ }
5759
6083
  var ToolExecutor = class {
5760
6084
  constructor(registry, opts) {
5761
6085
  this.registry = registry;
@@ -6044,6 +6368,9 @@ ${post.additionalContext}` };
6044
6368
  async runStreamedTool(tool, input, ctx, signal, toolUseId) {
6045
6369
  let finalOutput;
6046
6370
  let sawFinal = false;
6371
+ if (!tool.executeStream) {
6372
+ throw new Error(`Tool "${tool.name}" does not support streaming execution`);
6373
+ }
6047
6374
  const stream = tool.executeStream(input, ctx, { signal });
6048
6375
  for await (const ev of stream) {
6049
6376
  if (ev.type === "final") {
@@ -6148,7 +6475,7 @@ function hasMalformedArguments(input) {
6148
6475
  function extractMalformedRaw(input) {
6149
6476
  if (!hasMalformedArguments(input)) return void 0;
6150
6477
  const obj = input;
6151
- const value = obj[Object.keys(obj)[0]];
6478
+ const value = obj[expectDefined8(Object.keys(obj)[0])];
6152
6479
  if (value === void 0 || value === null) return void 0;
6153
6480
  if (typeof value === "string") return value;
6154
6481
  try {
@@ -6327,8 +6654,8 @@ var AutonomousRunner = class {
6327
6654
  init_atomic_write();
6328
6655
  var MAX_JOURNAL_ENTRIES = 500;
6329
6656
  function goalFilePath(projectRoot) {
6330
- const hash = createHash("sha256").update(path16.resolve(projectRoot)).digest("hex").slice(0, 12);
6331
- return path16.join(os.homedir(), ".wrongstack", "projects", hash, "goal.json");
6657
+ const hash = createHash("sha256").update(path3.resolve(projectRoot)).digest("hex").slice(0, 12);
6658
+ return path3.join(os.homedir(), ".wrongstack", "projects", hash, "goal.json");
6332
6659
  }
6333
6660
  async function loadGoal(filePath) {
6334
6661
  let raw;
@@ -7109,16 +7436,16 @@ var SubagentBudget = class _SubagentBudget {
7109
7436
  }
7110
7437
  if (exceeded.length === 0) return [];
7111
7438
  if (!this._onThreshold) {
7112
- const first2 = exceeded[0];
7439
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7113
7440
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7114
7441
  }
7115
7442
  if (this._mode === "sync") {
7116
- const first2 = exceeded[0];
7443
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7117
7444
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7118
7445
  }
7119
7446
  const bus = this._events;
7120
7447
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
7121
- const first2 = exceeded[0];
7448
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7122
7449
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7123
7450
  }
7124
7451
  for (const entry of exceeded) {
@@ -7126,8 +7453,9 @@ var SubagentBudget = class _SubagentBudget {
7126
7453
  const decision2 = this._negotiateExtension(entry.kind, exceeded);
7127
7454
  this._pendingNegotiations.set(entry.kind, decision2);
7128
7455
  }
7129
- const first = exceeded[0];
7456
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7130
7457
  const decision = this._pendingNegotiations.get(first.kind);
7458
+ if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
7131
7459
  throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
7132
7460
  }
7133
7461
  /**
@@ -7149,8 +7477,11 @@ var SubagentBudget = class _SubagentBudget {
7149
7477
  * a fresh signal.
7150
7478
  */
7151
7479
  async _negotiateExtension(kind, exceeded) {
7480
+ if (!this._onThreshold) {
7481
+ return "stop";
7482
+ }
7152
7483
  try {
7153
- const first = exceeded[0];
7484
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7154
7485
  const result = this._onThreshold({
7155
7486
  kind: first.kind,
7156
7487
  used: first.used,
@@ -9633,6 +9964,11 @@ function getAgentDefinition(role) {
9633
9964
 
9634
9965
  // src/coordination/dispatcher.ts
9635
9966
  var DEFAULT_DISPATCH_ROLE = "executor";
9967
+ var FALLBACK_DEFINITION = {
9968
+ config: { role: "unknown", name: "Unknown Agent" },
9969
+ budget: {},
9970
+ capability: { phase: "meta", summary: "", keywords: [] }
9971
+ };
9636
9972
  function normalize(text) {
9637
9973
  return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
9638
9974
  }
@@ -9660,7 +9996,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
9660
9996
  }
9661
9997
  function heuristicConfidence(candidates) {
9662
9998
  if (candidates.length === 0) return 0;
9663
- const top = candidates[0].score;
9999
+ const top = candidates[0]?.score ?? 0;
9664
10000
  const second = candidates[1]?.score ?? 0;
9665
10001
  const strength = Math.min(1, top / 3);
9666
10002
  const margin = (top - second + 1) / (top + 1);
@@ -9676,7 +10012,7 @@ async function dispatchAgent(task, opts = {}) {
9676
10012
  if (top && confidence >= threshold) {
9677
10013
  return {
9678
10014
  role: top.role,
9679
- definition: catalog[top.role],
10015
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
9680
10016
  confidence,
9681
10017
  method: "heuristic",
9682
10018
  reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
@@ -9684,7 +10020,7 @@ async function dispatchAgent(task, opts = {}) {
9684
10020
  };
9685
10021
  }
9686
10022
  if (opts.classifier) {
9687
- const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role]) : ALL_AGENT_DEFINITIONS).map((d) => ({
10023
+ const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role] ?? FALLBACK_DEFINITION) : ALL_AGENT_DEFINITIONS).map((d) => ({
9688
10024
  role: d.config.role,
9689
10025
  name: d.config.name,
9690
10026
  summary: d.capability.summary
@@ -9694,7 +10030,7 @@ async function dispatchAgent(task, opts = {}) {
9694
10030
  if (choice && catalog[choice.role]) {
9695
10031
  return {
9696
10032
  role: choice.role,
9697
- definition: catalog[choice.role],
10033
+ definition: catalog[choice.role] ?? FALLBACK_DEFINITION,
9698
10034
  confidence: 1,
9699
10035
  method: "llm",
9700
10036
  reason: choice.reason ?? "Selected by LLM classifier",
@@ -9707,17 +10043,17 @@ async function dispatchAgent(task, opts = {}) {
9707
10043
  if (top) {
9708
10044
  return {
9709
10045
  role: top.role,
9710
- definition: catalog[top.role],
10046
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
9711
10047
  confidence,
9712
10048
  method: "heuristic",
9713
10049
  reason: `Weak match (${top.matched.slice(0, 3).join(", ") || "low signal"})`,
9714
10050
  alternatives: candidates.slice(1, maxCandidates)
9715
10051
  };
9716
10052
  }
9717
- const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0];
10053
+ const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0] ?? DEFAULT_DISPATCH_ROLE;
9718
10054
  return {
9719
10055
  role: fallbackRole,
9720
- definition: catalog[fallbackRole],
10056
+ definition: catalog[fallbackRole] ?? FALLBACK_DEFINITION,
9721
10057
  confidence: 0,
9722
10058
  method: "fallback",
9723
10059
  reason: "No keyword signal; defaulting to the generalist Executor",
@@ -10530,6 +10866,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10530
10866
  takeNextDispatchableTask() {
10531
10867
  for (let i = 0; i < this.pendingTasks.length; i++) {
10532
10868
  const task = this.pendingTasks[i];
10869
+ if (!task) continue;
10533
10870
  const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
10534
10871
  if (!subagentId) continue;
10535
10872
  this.pendingTasks.splice(i, 1);
@@ -10721,14 +11058,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10721
11058
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
10722
11059
  if (idleExceeded && !wallExceeded) {
10723
11060
  this.subagents.get(ctx.subagentId)?.abortController.abort();
10724
- reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
11061
+ reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
10725
11062
  return;
10726
11063
  }
10727
11064
  if (!wallExceeded) {
10728
11065
  scheduleNext();
10729
11066
  return;
10730
11067
  }
10731
- const limit = wallLimit;
11068
+ const limit = wallLimit ?? 0;
10732
11069
  if (!budget.onThreshold) {
10733
11070
  this.subagents.get(ctx.subagentId)?.abortController.abort();
10734
11071
  reject(new BudgetExceededError("timeout", limit, elapsed));
@@ -10887,6 +11224,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10887
11224
  };
10888
11225
 
10889
11226
  // src/execution/parallel-eternal-engine.ts
11227
+ function expectDefined9(value) {
11228
+ if (value === null || value === void 0) {
11229
+ throw new Error("Expected value to be defined");
11230
+ }
11231
+ return value;
11232
+ }
10890
11233
  function sleep2(ms) {
10891
11234
  return new Promise((resolve5) => setTimeout(resolve5, ms));
10892
11235
  }
@@ -11045,7 +11388,7 @@ var ParallelEternalEngine = class {
11045
11388
  // Fan-out
11046
11389
  // -------------------------------------------------------------------------
11047
11390
  async fanOut(goal, tasks) {
11048
- const coordinator = this.coordinator;
11391
+ const coordinator = expectDefined9(this.coordinator);
11049
11392
  const slotCount = Math.min(this.slots, tasks.length);
11050
11393
  const routes = this.dispatchEnabled ? await Promise.all(
11051
11394
  tasks.slice(0, slotCount).map(
@@ -11077,7 +11420,7 @@ ${recentJournal}` : "No prior iterations.",
11077
11420
  const routeInfo = [];
11078
11421
  const spawnPromises = [];
11079
11422
  for (let i = 0; i < slotCount; i++) {
11080
- const task = tasks[i];
11423
+ const task = expectDefined9(tasks[i]);
11081
11424
  const route = routes[i] ?? null;
11082
11425
  const subagentId = `parallel-${this.iterations}-${i}`;
11083
11426
  const taskId = randomUUID();
@@ -11450,7 +11793,7 @@ var InMemoryBridgeTransport = class {
11450
11793
  }
11451
11794
  subscribe(agentId, handler) {
11452
11795
  if (!this.subs.has(agentId)) this.subs.set(agentId, /* @__PURE__ */ new Set());
11453
- this.subs.get(agentId).add(handler);
11796
+ this.subs.get(agentId)?.add(handler);
11454
11797
  return () => this.subs.get(agentId)?.delete(handler);
11455
11798
  }
11456
11799
  close(agentId) {
@@ -11568,6 +11911,12 @@ function createMessage(type, from, payload, to) {
11568
11911
  priority: "normal"
11569
11912
  };
11570
11913
  }
11914
+ function expectDefined10(value) {
11915
+ if (value === null || value === void 0) {
11916
+ throw new Error("Expected value to be defined");
11917
+ }
11918
+ return value;
11919
+ }
11571
11920
  var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
11572
11921
  var IS_WINDOWS = process.platform === "win32";
11573
11922
  var SEP = IS_WINDOWS ? "\\" : "/";
@@ -11581,7 +11930,7 @@ function globToRegex(pat) {
11581
11930
  let i = 0;
11582
11931
  let re = "^";
11583
11932
  while (i < pat.length) {
11584
- const c = pat[i];
11933
+ const c = expectDefined10(pat[i]);
11585
11934
  if (c === "*") {
11586
11935
  if (pat[i + 1] === "*") {
11587
11936
  re += ".*";
@@ -11620,7 +11969,7 @@ function globToRegex(pat) {
11620
11969
  }
11621
11970
  function baseDir(pat) {
11622
11971
  let i = pat.length - 1;
11623
- while (i >= 0 && !GLOB_CHARS.has(pat[i]) && pat[i] !== SEP && pat[i] !== "/") i--;
11972
+ while (i >= 0 && !GLOB_CHARS.has(expectDefined10(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
11624
11973
  const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
11625
11974
  return cut < 0 ? "." : pat.slice(0, cut);
11626
11975
  }
@@ -11845,7 +12194,7 @@ var CollabSession = class extends EventEmitter {
11845
12194
  this.emit("session.error", error);
11846
12195
  throw error;
11847
12196
  }
11848
- for (const result of results.flat()) {
12197
+ for (const result of results?.flat() ?? []) {
11849
12198
  await this.parseAndEmit(result);
11850
12199
  }
11851
12200
  const report = this.assembleReport();
@@ -11906,7 +12255,7 @@ var CollabSession = class extends EventEmitter {
11906
12255
  }
11907
12256
  budgetForRole(role) {
11908
12257
  if (this.options.budgetOverrides?.[role]) {
11909
- return this.options.budgetOverrides[role];
12258
+ return this.options.budgetOverrides[role] ?? { maxIterations: 0, maxToolCalls: 0, timeoutMs: 0 };
11910
12259
  }
11911
12260
  const defaults = {
11912
12261
  "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
@@ -12359,7 +12708,7 @@ function makeSpawnTool(director, roster) {
12359
12708
  });
12360
12709
  const dispatchRole = dispatchResult.role;
12361
12710
  if (roster?.[dispatchRole]) {
12362
- cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
12711
+ cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole] ?? {});
12363
12712
  } else {
12364
12713
  const def = dispatchResult.definition;
12365
12714
  cfg = {
@@ -13842,7 +14191,7 @@ var Director = class _Director {
13842
14191
  })),
13843
14192
  usage: this.usage.snapshot()
13844
14193
  };
13845
- await fsp.mkdir(path16.dirname(this.manifestPath), { recursive: true });
14194
+ await fsp.mkdir(path3.dirname(this.manifestPath), { recursive: true });
13846
14195
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
13847
14196
  return this.manifestPath;
13848
14197
  }
@@ -14048,7 +14397,7 @@ var Director = class _Director {
14048
14397
  */
14049
14398
  async readSession(subagentId, tail) {
14050
14399
  if (!this.sessionsRoot) return null;
14051
- const filePath = path16.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
14400
+ const filePath = path3.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
14052
14401
  let raw;
14053
14402
  try {
14054
14403
  raw = await fsp.readFile(filePath, "utf8");
@@ -14284,6 +14633,7 @@ function createDelegateTool(opts) {
14284
14633
  if (typeof i.task !== "string" || !i.task.trim()) {
14285
14634
  return { ok: false, error: "`task` is required." };
14286
14635
  }
14636
+ const target = i.role ?? i.name ?? "subagent";
14287
14637
  try {
14288
14638
  let director = await opts.host.ensureDirector();
14289
14639
  if (!director) {
@@ -14344,6 +14694,7 @@ function createDelegateTool(opts) {
14344
14694
  if (!cfg.timeoutMs) {
14345
14695
  cfg.timeoutMs = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
14346
14696
  }
14697
+ opts.events?.emit("delegate.started", { target, task: i.task });
14347
14698
  const subagentId = await director.spawn(cfg);
14348
14699
  const taskId = await director.assign({
14349
14700
  id: `${randomUUID()}`,
@@ -14378,6 +14729,17 @@ function createDelegateTool(opts) {
14378
14729
  });
14379
14730
  if ("__timeout" in result) {
14380
14731
  const partial2 = await readSubagentPartial(opts, subagentId);
14732
+ opts.events?.emit("delegate.completed", {
14733
+ target,
14734
+ task: i.task,
14735
+ ok: false,
14736
+ status: "host_timeout",
14737
+ summary: `[${target}] timed out \u2014 no result within ${Math.round(timeoutMs / 1e3)}s`,
14738
+ durationMs: timeoutMs,
14739
+ iterations: partial2?.events ?? 0,
14740
+ toolCalls: partial2?.toolUsesObserved ?? 0,
14741
+ subagentId
14742
+ });
14381
14743
  return {
14382
14744
  ok: false,
14383
14745
  stopReason: "host_timeout",
@@ -14394,6 +14756,24 @@ function createDelegateTool(opts) {
14394
14756
  const retryable = result.error?.retryable;
14395
14757
  const backoffMs = result.error?.backoffMs;
14396
14758
  const summary = buildDelegateSummary(i.role, result);
14759
+ let costUsd;
14760
+ try {
14761
+ costUsd = dir.snapshot().perSubagent[result.subagentId]?.cost;
14762
+ } catch {
14763
+ costUsd = void 0;
14764
+ }
14765
+ opts.events?.emit("delegate.completed", {
14766
+ target,
14767
+ task: i.task,
14768
+ ok: result.status === "success",
14769
+ status: result.status,
14770
+ summary,
14771
+ durationMs: result.durationMs,
14772
+ iterations: result.iterations,
14773
+ toolCalls: result.toolCalls,
14774
+ costUsd,
14775
+ subagentId: result.subagentId
14776
+ });
14397
14777
  return {
14398
14778
  ok: result.status === "success",
14399
14779
  status: result.status,
@@ -14415,10 +14795,21 @@ function createDelegateTool(opts) {
14415
14795
  summary
14416
14796
  };
14417
14797
  } catch (err) {
14798
+ const message = err instanceof Error ? err.message : String(err);
14799
+ opts.events?.emit("delegate.completed", {
14800
+ target,
14801
+ task: i.task,
14802
+ ok: false,
14803
+ status: "error",
14804
+ summary: `[${target}] failed \u2014 ${message}`,
14805
+ durationMs: 0,
14806
+ iterations: 0,
14807
+ toolCalls: 0
14808
+ });
14418
14809
  return {
14419
14810
  ok: false,
14420
14811
  stopReason: "error",
14421
- error: err instanceof Error ? err.message : String(err)
14812
+ error: message
14422
14813
  };
14423
14814
  }
14424
14815
  }
@@ -14505,13 +14896,13 @@ async function readSubagentPartial(opts, subagentId) {
14505
14896
  if (!opts.sessionsRoot) return void 0;
14506
14897
  const candidates = [];
14507
14898
  if (opts.directorRunId) {
14508
- candidates.push(path16.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
14899
+ candidates.push(path3.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
14509
14900
  } else {
14510
14901
  try {
14511
14902
  const entries = await fsp.readdir(opts.sessionsRoot, { withFileTypes: true });
14512
14903
  for (const entry of entries) {
14513
14904
  if (entry.isDirectory()) {
14514
- candidates.push(path16.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
14905
+ candidates.push(path3.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
14515
14906
  }
14516
14907
  }
14517
14908
  } catch {
@@ -14558,9 +14949,9 @@ function makeDirectorSessionFactory(opts) {
14558
14949
  let dir;
14559
14950
  if (opts.store) {
14560
14951
  store = opts.store;
14561
- dir = opts.sessionsRoot ? path16.join(opts.sessionsRoot, runId) : "(caller-managed)";
14952
+ dir = opts.sessionsRoot ? path3.join(opts.sessionsRoot, runId) : "(caller-managed)";
14562
14953
  } else if (opts.sessionsRoot) {
14563
- dir = path16.join(opts.sessionsRoot, runId);
14954
+ dir = path3.join(opts.sessionsRoot, runId);
14564
14955
  store = new DefaultSessionStore({ dir });
14565
14956
  } else {
14566
14957
  throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
@@ -14614,7 +15005,10 @@ function attachAutoExtend(events, policy = {}) {
14614
15005
  if (kind === "timeout" || kind === "idle_timeout") {
14615
15006
  if (progress > lastTimeoutProgress) {
14616
15007
  lastTimeoutProgress = progress;
14617
- const next2 = Math.min(Math.ceil(limit * (1 + factor)), ceiling.timeoutMs);
15008
+ const next2 = Math.min(
15009
+ Math.ceil(limit * (1 + factor)),
15010
+ ceiling.timeoutMs ?? DEFAULT_CEILING.timeoutMs
15011
+ );
14618
15012
  extend({ timeoutMs: next2 });
14619
15013
  } else {
14620
15014
  deny();
@@ -14628,7 +15022,7 @@ function attachAutoExtend(events, policy = {}) {
14628
15022
  }
14629
15023
  extendCounts.set(kind, count + 1);
14630
15024
  const field = FIELD_BY_KIND[kind];
14631
- const cap = ceiling[field];
15025
+ const cap = ceiling[field] ?? DEFAULT_CEILING[field];
14632
15026
  const next = Math.min(Math.ceil(limit * (1 + factor)), cap);
14633
15027
  extend({ [field]: next });
14634
15028
  })
@@ -14764,7 +15158,7 @@ var DefaultModelsRegistry = class {
14764
15158
  this.overlay = opts.overlay;
14765
15159
  this.overlayUrl = opts.overlayUrl;
14766
15160
  this.overlayFile = opts.overlayFile;
14767
- this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path16.join(path16.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
15161
+ this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path3.join(path3.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
14768
15162
  }
14769
15163
  async load(opts = {}) {
14770
15164
  if (this.payload && !opts.force) return this.payload;
@@ -14977,7 +15371,7 @@ var DefaultModelsRegistry = class {
14977
15371
  }
14978
15372
  /** Used by `wstack models refresh` to expose where the cache lives. */
14979
15373
  cacheLocation() {
14980
- return path16.resolve(this.cacheFile);
15374
+ return path3.resolve(this.cacheFile);
14981
15375
  }
14982
15376
  };
14983
15377
  function hasEntries(payload) {
@@ -15273,7 +15667,7 @@ var DefaultModeStore = class {
15273
15667
  }
15274
15668
  async loadActiveMode() {
15275
15669
  try {
15276
- const configPath = path16.join(this.configDir, "mode.json");
15670
+ const configPath = path3.join(this.configDir, "mode.json");
15277
15671
  const content = await fsp.readFile(configPath, "utf8");
15278
15672
  const data = JSON.parse(content);
15279
15673
  this.activeModeId = data.activeMode ?? null;
@@ -15284,7 +15678,7 @@ var DefaultModeStore = class {
15284
15678
  async saveActiveMode() {
15285
15679
  try {
15286
15680
  await fsp.mkdir(this.configDir, { recursive: true });
15287
- const configPath = path16.join(this.configDir, "mode.json");
15681
+ const configPath = path3.join(this.configDir, "mode.json");
15288
15682
  await atomicWrite(
15289
15683
  configPath,
15290
15684
  JSON.stringify({ activeMode: this.activeModeId }, null, 2)
@@ -15299,11 +15693,11 @@ async function loadProjectModes(modesDir) {
15299
15693
  const entries = await fsp.readdir(modesDir);
15300
15694
  for (const entry of entries) {
15301
15695
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
15302
- const filePath = path16.join(modesDir, entry);
15696
+ const filePath = path3.join(modesDir, entry);
15303
15697
  const stat5 = await fsp.stat(filePath);
15304
15698
  if (!stat5.isFile()) continue;
15305
15699
  const content = await fsp.readFile(filePath, "utf8");
15306
- const id = path16.basename(entry, path16.extname(entry));
15700
+ const id = path3.basename(entry, path3.extname(entry));
15307
15701
  modes.push({
15308
15702
  id,
15309
15703
  name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
@@ -15319,7 +15713,7 @@ async function loadProjectModes(modesDir) {
15319
15713
  async function loadUserModes(modesDir) {
15320
15714
  const modes = [];
15321
15715
  try {
15322
- const manifestPath = path16.join(modesDir, "modes.json");
15716
+ const manifestPath = path3.join(modesDir, "modes.json");
15323
15717
  const content = await fsp.readFile(manifestPath, "utf8");
15324
15718
  const manifest = JSON.parse(content);
15325
15719
  for (const mode of manifest.modes) {
@@ -15927,7 +16321,7 @@ var TaskTracker = class {
15927
16321
  if (filter.type?.length && !filter.type.includes(n.type)) return false;
15928
16322
  if (filter.assignee?.length && n.assignee && !filter.assignee.includes(n.assignee))
15929
16323
  return false;
15930
- if (filter.tags?.length && n.tags && !n.tags.some((t) => filter.tags.includes(t)))
16324
+ if (filter.tags?.length && n.tags && !n.tags.some((t) => filter.tags?.includes(t)))
15931
16325
  return false;
15932
16326
  if (filter.specRequirementId && n.specRequirementId !== filter.specRequirementId)
15933
16327
  return false;
@@ -16236,7 +16630,7 @@ var SpecStore = class {
16236
16630
  indexPath;
16237
16631
  constructor(opts) {
16238
16632
  this.baseDir = opts.baseDir;
16239
- this.indexPath = path16.join(this.baseDir, "_index.json");
16633
+ this.indexPath = path3.join(this.baseDir, "_index.json");
16240
16634
  }
16241
16635
  async save(spec) {
16242
16636
  await ensureDir(this.baseDir);
@@ -16305,7 +16699,7 @@ var SpecStore = class {
16305
16699
  return updated;
16306
16700
  }
16307
16701
  filePath(id) {
16308
- return path16.join(this.baseDir, `${id}.json`);
16702
+ return path3.join(this.baseDir, `${id}.json`);
16309
16703
  }
16310
16704
  async readIndex() {
16311
16705
  try {
@@ -16362,7 +16756,7 @@ var TaskGraphStore = class {
16362
16756
  indexPath;
16363
16757
  constructor(opts) {
16364
16758
  this.baseDir = opts.baseDir;
16365
- this.indexPath = path16.join(this.baseDir, "_index.json");
16759
+ this.indexPath = path3.join(this.baseDir, "_index.json");
16366
16760
  }
16367
16761
  async save(graph) {
16368
16762
  await ensureDir(this.baseDir);
@@ -16400,7 +16794,7 @@ var TaskGraphStore = class {
16400
16794
  }
16401
16795
  }
16402
16796
  filePath(id) {
16403
- return path16.join(this.baseDir, `${id}.json`);
16797
+ return path3.join(this.baseDir, `${id}.json`);
16404
16798
  }
16405
16799
  async readIndex() {
16406
16800
  try {
@@ -16441,6 +16835,12 @@ var TaskGraphStore = class {
16441
16835
  };
16442
16836
 
16443
16837
  // src/sdd/spec-builder.ts
16838
+ function expectDefined11(value) {
16839
+ if (value === null || value === void 0) {
16840
+ throw new Error("Expected value to be defined");
16841
+ }
16842
+ return value;
16843
+ }
16444
16844
  function buildQuestioningPrompt(session, min, max) {
16445
16845
  const answered = session.answers.length;
16446
16846
  const remaining = Math.max(0, min - answered);
@@ -16486,7 +16886,7 @@ function buildQuestioningPrompt(session, min, max) {
16486
16886
  if (answered > 0) {
16487
16887
  lines.push("", "**Conversation so far:**");
16488
16888
  for (let i = 0; i < answered; i++) {
16489
- const a = session.answers[i];
16889
+ const a = expectDefined11(session.answers[i]);
16490
16890
  lines.push(``, `Q${i + 1}: ${a.question}`, `A${i + 1}: ${a.answer}`);
16491
16891
  }
16492
16892
  }
@@ -17228,6 +17628,12 @@ function truncate2(str, maxLen) {
17228
17628
  }
17229
17629
 
17230
17630
  // src/sdd/critical-path.ts
17631
+ function expectDefined12(value) {
17632
+ if (value === null || value === void 0) {
17633
+ throw new Error("Expected value to be defined");
17634
+ }
17635
+ return value;
17636
+ }
17231
17637
  function analyzeCriticalPath(graph) {
17232
17638
  const nodes = Array.from(graph.nodes.values());
17233
17639
  const topoOrder = topologicalSort(graph);
@@ -17236,9 +17642,9 @@ function analyzeCriticalPath(graph) {
17236
17642
  for (const edge of graph.edges) {
17237
17643
  if (edge.type === "depends_on") {
17238
17644
  if (!blockedByMap.has(edge.from)) blockedByMap.set(edge.from, /* @__PURE__ */ new Set());
17239
- blockedByMap.get(edge.from).add(edge.to);
17645
+ blockedByMap.get(edge.from)?.add(edge.to);
17240
17646
  if (!blocksMap.has(edge.to)) blocksMap.set(edge.to, /* @__PURE__ */ new Set());
17241
- blocksMap.get(edge.to).add(edge.from);
17647
+ blocksMap.get(edge.to)?.add(edge.from);
17242
17648
  }
17243
17649
  }
17244
17650
  const readyTasks = [];
@@ -17303,7 +17709,7 @@ function getTransitiveBlocked(_graph, taskId, blocksMap) {
17303
17709
  const visited = /* @__PURE__ */ new Set();
17304
17710
  const queue = [taskId];
17305
17711
  while (queue.length > 0) {
17306
- const current = queue.shift();
17712
+ const current = expectDefined12(queue.shift());
17307
17713
  const blocked = blocksMap.get(current);
17308
17714
  if (!blocked) continue;
17309
17715
  for (const id of blocked) {
@@ -17328,7 +17734,7 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
17328
17734
  for (const [taskId, blockers] of blockedByMap) {
17329
17735
  for (const blockerId of blockers) {
17330
17736
  if (!blocksMap.has(blockerId)) blocksMap.set(blockerId, /* @__PURE__ */ new Set());
17331
- blocksMap.get(blockerId).add(taskId);
17737
+ blocksMap.get(blockerId)?.add(taskId);
17332
17738
  }
17333
17739
  }
17334
17740
  const n = allIds.length;
@@ -17349,7 +17755,7 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
17349
17755
  if (!changed) break;
17350
17756
  }
17351
17757
  let maxDist = 0;
17352
- let maxId = allIds[0];
17758
+ let maxId = expectDefined12(allIds[0]);
17353
17759
  for (const id of allIds) {
17354
17760
  const d = dist.get(id) ?? 0;
17355
17761
  if (d > maxDist) {
@@ -17818,6 +18224,12 @@ var SddTaskDecomposer = class {
17818
18224
  return nodes.some((n) => n.status === "blocked");
17819
18225
  }
17820
18226
  };
18227
+ function expectDefined13(value) {
18228
+ if (value === null || value === void 0) {
18229
+ throw new Error("Expected value to be defined");
18230
+ }
18231
+ return value;
18232
+ }
17821
18233
  var SddParallelRun = class {
17822
18234
  constructor(opts) {
17823
18235
  this.opts = opts;
@@ -17918,8 +18330,10 @@ var SddParallelRun = class {
17918
18330
  "\u2022 Do not ask before routine in-project tool use; if a permission gate appears, wait for that flow.",
17919
18331
  "\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
17920
18332
  ].join("\n");
18333
+ if (!this.coordinator) throw new Error("SDD parallel runner requires a coordinator");
18334
+ const coordinator = this.coordinator;
17921
18335
  const spawns = subagentIds.map(
17922
- (subagentId) => this.coordinator.spawn({
18336
+ (subagentId) => coordinator.spawn({
17923
18337
  id: subagentId,
17924
18338
  name: subagentId,
17925
18339
  role: "executor",
@@ -17927,12 +18341,12 @@ var SddParallelRun = class {
17927
18341
  })
17928
18342
  );
17929
18343
  const spawnResults = await Promise.all(spawns);
17930
- if (!spawnResults.every((r) => r.subagentId)) {
18344
+ if (!spawnResults.every((r) => Boolean(r.subagentId))) {
17931
18345
  throw new Error("One or more subagent spawns failed");
17932
18346
  }
17933
18347
  const assignPromises = tasks.map((task, i) => {
17934
18348
  const spec = {
17935
- id: taskIds[i],
18349
+ id: taskIds[i] ?? task.id,
17936
18350
  description: [
17937
18351
  directivePreamble,
17938
18352
  "",
@@ -17941,15 +18355,15 @@ var SddParallelRun = class {
17941
18355
  "",
17942
18356
  task.description
17943
18357
  ].join("\n"),
17944
- subagentId: subagentIds[i],
18358
+ subagentId: subagentIds[i] ?? spawnResults[i]?.subagentId ?? task.id,
17945
18359
  timeoutMs: this.timeoutMs
17946
18360
  };
17947
- return this.coordinator.assign(spec);
18361
+ return this.coordinator?.assign(spec);
17948
18362
  });
17949
18363
  await Promise.all(assignPromises);
17950
18364
  let results;
17951
18365
  try {
17952
- results = await this.coordinator.awaitTasks(taskIds);
18366
+ results = await coordinator.awaitTasks(taskIds);
17953
18367
  } catch (err) {
17954
18368
  results = taskIds.map((id) => ({
17955
18369
  subagentId: "",
@@ -17964,8 +18378,8 @@ var SddParallelRun = class {
17964
18378
  const successCount = results.filter((r) => r.status === "success").length;
17965
18379
  const failCount = results.length - successCount;
17966
18380
  for (let i = 0; i < results.length; i++) {
17967
- const result = results[i];
17968
- const taskId = taskIds[i];
18381
+ const result = expectDefined13(results[i]);
18382
+ const taskId = expectDefined13(taskIds[i]);
17969
18383
  if (result.status === "success") {
17970
18384
  this.opts.tracker.updateNodeStatus(taskId, "completed");
17971
18385
  } else {