@wrongstack/core 0.77.0 → 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-EWdqs8v6.d.ts → agent-bridge-C9P_HPez.d.ts} +2 -2
  2. package/dist/{agent-subagent-runner-D8qW8OSC.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 +318 -37
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +31 -31
  10. package/dist/defaults/index.js +419 -67
  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-CYaoLN5_.d.ts → events-DnRqXaZ3.d.ts} +43 -42
  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-DIxjTOga.d.ts → index-BNOLadHw.d.ts} +28 -28
  22. package/dist/{index-Dsda0uCn.d.ts → index-N0_c4bHQ.d.ts} +45 -45
  23. package/dist/index.d.ts +165 -165
  24. package/dist/index.js +593 -137
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +9 -9
  27. package/dist/infrastructure/index.js +13 -5
  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-BppKxDqZ.d.ts → logger-C_27pj9i.d.ts} +6 -7
  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-DpbG3wiy.d.ts → multi-agent-coordinator-DllpCVkF.d.ts} +12 -12
  42. package/dist/{null-fleet-bus-u5ys3lW_.d.ts → null-fleet-bus-BY0AN-sr.d.ts} +121 -121
  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-Dn0P8Pbj.d.ts → parallel-eternal-engine-D402RASp.d.ts} +49 -49
  47. package/dist/{path-resolver-B32v2JIq.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-BcUwLlMQ.d.ts → plan-templates-DRvPgkfZ.d.ts} +65 -32
  51. package/dist/{provider-runner-CSi_7l0h.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 +378 -76
  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 +45 -10
  73. package/dist/types/index.js.map +1 -1
  74. package/dist/utils/index.d.ts +46 -45
  75. package/dist/utils/index.js +53 -12
  76. package/dist/utils/index.js.map +1 -1
  77. package/dist/{wstack-paths-D7evAFWM.d.ts → wstack-paths-BQMvEllz.d.ts} +2 -2
  78. package/package.json +1 -1
  79. package/dist/logger-DDd5C--Z.d.ts +0 -12
@@ -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);
@@ -243,6 +245,12 @@ function formatCtx(ctx) {
243
245
  init_atomic_write();
244
246
 
245
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
+ }
246
254
  function repairToolUseAdjacency(messages) {
247
255
  const removedToolUses = [];
248
256
  const removedToolResults = [];
@@ -250,7 +258,7 @@ function repairToolUseAdjacency(messages) {
250
258
  let changed = false;
251
259
  const out = [];
252
260
  for (let i = 0; i < messages.length; i++) {
253
- const original = messages[i];
261
+ const original = expectDefined(messages[i]);
254
262
  let msg = original;
255
263
  if (hasToolUse(msg)) {
256
264
  const nextIds = toolResultIds(messages[i + 1]);
@@ -335,6 +343,12 @@ function isEmptyMessage(msg) {
335
343
  }
336
344
 
337
345
  // src/storage/session-store.ts
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
+ }
338
352
  function sanitizeModel(model) {
339
353
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
340
354
  }
@@ -345,7 +359,7 @@ function generateSessionId(startedAt, model) {
345
359
  const modelPart = model ? `_${sanitizeModel(model)}` : "";
346
360
  return `${date}/${time}Z${modelPart}_${suffix}`;
347
361
  }
348
- var DefaultSessionStore = class {
362
+ var DefaultSessionStore = class _DefaultSessionStore {
349
363
  dir;
350
364
  events;
351
365
  secretScrubber;
@@ -354,6 +368,10 @@ var DefaultSessionStore = class {
354
368
  this.events = opts.events;
355
369
  this.secretScrubber = opts.secretScrubber;
356
370
  }
371
+ /** Absolute path to the session index file. */
372
+ get indexFile() {
373
+ return path3.join(this.dir, "_index.jsonl");
374
+ }
357
375
  /** Join session ID to its absolute path within the store directory. */
358
376
  sessionPath(id, ext) {
359
377
  return path3.join(this.dir, `${id}${ext}`);
@@ -386,7 +404,8 @@ var DefaultSessionStore = class {
386
404
  return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
387
405
  dir: shardDir,
388
406
  filePath: file,
389
- secretScrubber: this.secretScrubber
407
+ secretScrubber: this.secretScrubber,
408
+ onClose: (s) => this.appendToIndex(s)
390
409
  });
391
410
  } catch (err) {
392
411
  await handle.close().catch(() => {
@@ -417,7 +436,7 @@ var DefaultSessionStore = class {
417
436
  provider: data.metadata.provider
418
437
  },
419
438
  this.events,
420
- { 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) }
421
440
  );
422
441
  return { writer, data };
423
442
  } catch (err) {
@@ -447,6 +466,15 @@ var DefaultSessionStore = class {
447
466
  async list(limit = 20) {
448
467
  try {
449
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
+ }
450
478
  const ids = await this.collectSessionIds(this.dir);
451
479
  const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
452
480
  const out = sessions.filter((s) => s !== null);
@@ -460,16 +488,121 @@ var DefaultSessionStore = class {
460
488
  return [];
461
489
  }
462
490
  }
463
- /** Recursively collect all session IDs from shard subdirectories. */
464
- 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) {
465
588
  const ids = [];
466
- 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
+ }
467
595
  for (const entry of entries) {
468
- const full = path3.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;
469
599
  if (entry.isDirectory()) {
470
- 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));
471
602
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
472
- 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);
473
606
  }
474
607
  }
475
608
  return ids;
@@ -492,9 +625,70 @@ var DefaultSessionStore = class {
492
625
  return summary;
493
626
  }
494
627
  }
495
- async delete(id) {
496
- 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);
497
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;
498
692
  }
499
693
  async clearHistory(id) {
500
694
  await this.ensureShardDir(id);
@@ -516,13 +710,42 @@ var DefaultSessionStore = class {
516
710
  const data = await this.load(id);
517
711
  const firstUser = data.events.find((e) => e.type === "user_input");
518
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
+ }
519
735
  return {
520
736
  id,
521
737
  title,
522
738
  startedAt: data.metadata.startedAt,
739
+ endedAt: data.metadata.endedAt,
523
740
  model: data.metadata.model ?? "unknown",
524
741
  provider: data.metadata.provider ?? "unknown",
525
- 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
526
749
  };
527
750
  } catch {
528
751
  return {
@@ -621,9 +844,10 @@ var FileSessionWriter = class {
621
844
  this.meta = meta;
622
845
  this.events = events;
623
846
  this.resumed = opts.resumed ?? false;
624
- this.manifestFile = opts.dir ? path3.join(opts.dir, `${id}.summary.json`) : "";
847
+ this.manifestFile = opts.dir ? path3.join(opts.dir, `${path3.basename(id)}.summary.json`) : "";
625
848
  this.filePath = opts.filePath ?? "";
626
849
  this.secretScrubber = opts.secretScrubber;
850
+ this.onCloseCb = opts.onClose;
627
851
  this.summary = {
628
852
  id,
629
853
  title: "(empty session)",
@@ -653,6 +877,15 @@ var FileSessionWriter = class {
653
877
  appendFailCount = 0;
654
878
  lastAppendWarnAt = 0;
655
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;
656
889
  /**
657
890
  * Scrub secrets out of conversation-turn events before they are observed
658
891
  * for the summary, written to the JSONL log, or surfaced on resume. Only
@@ -730,8 +963,22 @@ var FileSessionWriter = class {
730
963
  observeForSummary(event) {
731
964
  if (event.type === "tool_use") {
732
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;
733
969
  } else if (event.type === "tool_result") {
734
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";
735
982
  }
736
983
  if (event.type === "user_input" && this.summary.title === "(empty session)") {
737
984
  this.summary = { ...this.summary, title: userInputTitle(event.content) };
@@ -742,18 +989,35 @@ var FileSessionWriter = class {
742
989
  } else if (event.type === "session_end") {
743
990
  const total = event.usage.input + event.usage.output;
744
991
  if (total > 0) this.summary = { ...this.summary, tokenTotal: total };
992
+ } else if (event.type === "in_flight_start") {
993
+ this.iterationCount++;
745
994
  }
746
995
  }
747
996
  async close() {
748
997
  if (this.closing) return;
749
998
  this.closing = true;
750
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
+ };
751
1011
  if (this.manifestFile) {
752
1012
  try {
753
1013
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
754
1014
  } catch {
755
1015
  }
756
1016
  }
1017
+ try {
1018
+ await this.onCloseCb?.(this.summary);
1019
+ } catch {
1020
+ }
757
1021
  try {
758
1022
  await this.handle.close();
759
1023
  } catch {
@@ -795,7 +1059,7 @@ var FileSessionWriter = class {
795
1059
  let targetCheckpointLine = -1;
796
1060
  let afterTarget = false;
797
1061
  for (let i = 0; i < lines.length; i++) {
798
- const line = lines[i];
1062
+ const line = expectDefined2(lines[i]);
799
1063
  if (!line.trim()) continue;
800
1064
  let event;
801
1065
  try {
@@ -1971,6 +2235,12 @@ function compileUserRegex(pattern, flags) {
1971
2235
  }
1972
2236
 
1973
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
+ }
1974
2244
  var DefaultSessionReader = class {
1975
2245
  store;
1976
2246
  constructor(opts) {
@@ -2032,7 +2302,7 @@ var DefaultSessionReader = class {
2032
2302
  continue;
2033
2303
  }
2034
2304
  for (let i = 0; i < data.events.length; i++) {
2035
- const ev = data.events[i];
2305
+ const ev = expectDefined3(data.events[i]);
2036
2306
  if (allowedTypes && !allowedTypes.has(ev.type)) continue;
2037
2307
  const text = eventText(ev);
2038
2308
  if (text === null) continue;
@@ -2337,8 +2607,11 @@ var SessionAnalyzer = class {
2337
2607
  }
2338
2608
  calcDuration(events) {
2339
2609
  if (events.length < 2) return 0;
2340
- const first = new Date(events[0].ts).getTime();
2341
- 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();
2342
2615
  return last - first;
2343
2616
  }
2344
2617
  };
@@ -3291,6 +3564,12 @@ function getDangerousCapabilities(toolOrCaps) {
3291
3564
  init_atomic_write();
3292
3565
 
3293
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
+ }
3294
3573
  function escapeRegex(s) {
3295
3574
  return s.replace(/[.+^${}()|\\]/g, "\\$&");
3296
3575
  }
@@ -3302,7 +3581,7 @@ function getCachedGlob(pattern) {
3302
3581
  if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
3303
3582
  const keys = [...COMPILED_GLOB_CACHE.keys()];
3304
3583
  for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
3305
- COMPILED_GLOB_CACHE.delete(keys[i]);
3584
+ COMPILED_GLOB_CACHE.delete(expectDefined4(keys[i]));
3306
3585
  }
3307
3586
  }
3308
3587
  const re = compileGlob(pattern);
@@ -4160,13 +4439,19 @@ function parseDescription(raw) {
4160
4439
  const scope = [];
4161
4440
  const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
4162
4441
  if (coversMatch) {
4163
- 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);
4164
4443
  scope.push(...items);
4165
4444
  }
4166
4445
  return { trigger, scope };
4167
4446
  }
4168
4447
 
4169
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
+ }
4170
4455
  function completePartialObject(s) {
4171
4456
  if (!s.trim().startsWith("{")) return s;
4172
4457
  if (tryParse(s).ok) return s;
@@ -4178,7 +4463,7 @@ function completePartialObject(s) {
4178
4463
  let contentEnd = 0;
4179
4464
  let stringBraceDepth = 0;
4180
4465
  for (let i = 0; i < s.length; i++) {
4181
- const ch = s[i];
4466
+ const ch = expectDefined5(s[i]);
4182
4467
  if (inString) {
4183
4468
  contentEnd = i + 1;
4184
4469
  if (escaped) {
@@ -4563,6 +4848,12 @@ var DefaultProviderRunner = class {
4563
4848
  };
4564
4849
 
4565
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
+ }
4566
4857
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
4567
4858
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
4568
4859
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
@@ -4572,7 +4863,7 @@ function getCachedEstimate(key, compute) {
4572
4863
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
4573
4864
  const keys = [...ESTIMATE_CACHE.keys()];
4574
4865
  for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
4575
- ESTIMATE_CACHE.delete(keys[i]);
4866
+ ESTIMATE_CACHE.delete(expectDefined6(keys[i]));
4576
4867
  }
4577
4868
  }
4578
4869
  const estimate = compute();
@@ -4962,7 +5253,7 @@ var IntelligentCompactor = class {
4962
5253
  maxTokens: 1024
4963
5254
  };
4964
5255
  const ac = ctx.signal ? void 0 : new AbortController();
4965
- const signal = ctx.signal ?? ac.signal;
5256
+ const signal = ctx.signal ?? ac?.signal;
4966
5257
  const res = await this.provider.complete(req, { signal });
4967
5258
  const textBlocks = res.content.filter(isTextBlock);
4968
5259
  return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
@@ -5065,6 +5356,12 @@ var IntelligentCompactor = class {
5065
5356
  };
5066
5357
 
5067
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
+ }
5068
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.
5069
5366
 
5070
5367
  Output a JSON object with this structure:
@@ -5105,7 +5402,7 @@ function formatMessages(messages, maxChars = 8e3) {
5105
5402
  const lines = [];
5106
5403
  let used = 0;
5107
5404
  for (let i = 0; i < messages.length; i++) {
5108
- const m = messages[i];
5405
+ const m = expectDefined7(messages[i]);
5109
5406
  const role = m.role.padEnd(10, " ");
5110
5407
  let text;
5111
5408
  if (typeof m.content === "string") {
@@ -5170,7 +5467,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
5170
5467
  let tokenCount = 0;
5171
5468
  let startIdx = 0;
5172
5469
  for (let i = messages.length - 1; i >= 0; i--) {
5173
- const m = messages[i];
5470
+ const m = expectDefined7(messages[i]);
5174
5471
  const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
5175
5472
  (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
5176
5473
  0
@@ -5381,6 +5678,7 @@ Summarize the following message range:`;
5381
5678
  let boundary = preserveIdx;
5382
5679
  for (let i = preserveIdx; i < messages.length && i < preserveIdx + 6; i++) {
5383
5680
  const m = messages[i];
5681
+ if (!m) continue;
5384
5682
  if (m.role === "user" && this.hasTextContent(m)) {
5385
5683
  boundary = i;
5386
5684
  break;
@@ -5776,6 +6074,12 @@ function createToolOutputSerializer(opts = {}) {
5776
6074
  }
5777
6075
 
5778
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
+ }
5779
6083
  var ToolExecutor = class {
5780
6084
  constructor(registry, opts) {
5781
6085
  this.registry = registry;
@@ -6064,6 +6368,9 @@ ${post.additionalContext}` };
6064
6368
  async runStreamedTool(tool, input, ctx, signal, toolUseId) {
6065
6369
  let finalOutput;
6066
6370
  let sawFinal = false;
6371
+ if (!tool.executeStream) {
6372
+ throw new Error(`Tool "${tool.name}" does not support streaming execution`);
6373
+ }
6067
6374
  const stream = tool.executeStream(input, ctx, { signal });
6068
6375
  for await (const ev of stream) {
6069
6376
  if (ev.type === "final") {
@@ -6168,7 +6475,7 @@ function hasMalformedArguments(input) {
6168
6475
  function extractMalformedRaw(input) {
6169
6476
  if (!hasMalformedArguments(input)) return void 0;
6170
6477
  const obj = input;
6171
- const value = obj[Object.keys(obj)[0]];
6478
+ const value = obj[expectDefined8(Object.keys(obj)[0])];
6172
6479
  if (value === void 0 || value === null) return void 0;
6173
6480
  if (typeof value === "string") return value;
6174
6481
  try {
@@ -7129,16 +7436,16 @@ var SubagentBudget = class _SubagentBudget {
7129
7436
  }
7130
7437
  if (exceeded.length === 0) return [];
7131
7438
  if (!this._onThreshold) {
7132
- const first2 = exceeded[0];
7439
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7133
7440
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7134
7441
  }
7135
7442
  if (this._mode === "sync") {
7136
- const first2 = exceeded[0];
7443
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7137
7444
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7138
7445
  }
7139
7446
  const bus = this._events;
7140
7447
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
7141
- const first2 = exceeded[0];
7448
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7142
7449
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7143
7450
  }
7144
7451
  for (const entry of exceeded) {
@@ -7146,8 +7453,9 @@ var SubagentBudget = class _SubagentBudget {
7146
7453
  const decision2 = this._negotiateExtension(entry.kind, exceeded);
7147
7454
  this._pendingNegotiations.set(entry.kind, decision2);
7148
7455
  }
7149
- const first = exceeded[0];
7456
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7150
7457
  const decision = this._pendingNegotiations.get(first.kind);
7458
+ if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
7151
7459
  throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
7152
7460
  }
7153
7461
  /**
@@ -7169,8 +7477,11 @@ var SubagentBudget = class _SubagentBudget {
7169
7477
  * a fresh signal.
7170
7478
  */
7171
7479
  async _negotiateExtension(kind, exceeded) {
7480
+ if (!this._onThreshold) {
7481
+ return "stop";
7482
+ }
7172
7483
  try {
7173
- const first = exceeded[0];
7484
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7174
7485
  const result = this._onThreshold({
7175
7486
  kind: first.kind,
7176
7487
  used: first.used,
@@ -9653,6 +9964,11 @@ function getAgentDefinition(role) {
9653
9964
 
9654
9965
  // src/coordination/dispatcher.ts
9655
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
+ };
9656
9972
  function normalize(text) {
9657
9973
  return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
9658
9974
  }
@@ -9680,7 +9996,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
9680
9996
  }
9681
9997
  function heuristicConfidence(candidates) {
9682
9998
  if (candidates.length === 0) return 0;
9683
- const top = candidates[0].score;
9999
+ const top = candidates[0]?.score ?? 0;
9684
10000
  const second = candidates[1]?.score ?? 0;
9685
10001
  const strength = Math.min(1, top / 3);
9686
10002
  const margin = (top - second + 1) / (top + 1);
@@ -9696,7 +10012,7 @@ async function dispatchAgent(task, opts = {}) {
9696
10012
  if (top && confidence >= threshold) {
9697
10013
  return {
9698
10014
  role: top.role,
9699
- definition: catalog[top.role],
10015
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
9700
10016
  confidence,
9701
10017
  method: "heuristic",
9702
10018
  reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
@@ -9704,7 +10020,7 @@ async function dispatchAgent(task, opts = {}) {
9704
10020
  };
9705
10021
  }
9706
10022
  if (opts.classifier) {
9707
- 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) => ({
9708
10024
  role: d.config.role,
9709
10025
  name: d.config.name,
9710
10026
  summary: d.capability.summary
@@ -9714,7 +10030,7 @@ async function dispatchAgent(task, opts = {}) {
9714
10030
  if (choice && catalog[choice.role]) {
9715
10031
  return {
9716
10032
  role: choice.role,
9717
- definition: catalog[choice.role],
10033
+ definition: catalog[choice.role] ?? FALLBACK_DEFINITION,
9718
10034
  confidence: 1,
9719
10035
  method: "llm",
9720
10036
  reason: choice.reason ?? "Selected by LLM classifier",
@@ -9727,17 +10043,17 @@ async function dispatchAgent(task, opts = {}) {
9727
10043
  if (top) {
9728
10044
  return {
9729
10045
  role: top.role,
9730
- definition: catalog[top.role],
10046
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
9731
10047
  confidence,
9732
10048
  method: "heuristic",
9733
10049
  reason: `Weak match (${top.matched.slice(0, 3).join(", ") || "low signal"})`,
9734
10050
  alternatives: candidates.slice(1, maxCandidates)
9735
10051
  };
9736
10052
  }
9737
- 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;
9738
10054
  return {
9739
10055
  role: fallbackRole,
9740
- definition: catalog[fallbackRole],
10056
+ definition: catalog[fallbackRole] ?? FALLBACK_DEFINITION,
9741
10057
  confidence: 0,
9742
10058
  method: "fallback",
9743
10059
  reason: "No keyword signal; defaulting to the generalist Executor",
@@ -10550,6 +10866,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10550
10866
  takeNextDispatchableTask() {
10551
10867
  for (let i = 0; i < this.pendingTasks.length; i++) {
10552
10868
  const task = this.pendingTasks[i];
10869
+ if (!task) continue;
10553
10870
  const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
10554
10871
  if (!subagentId) continue;
10555
10872
  this.pendingTasks.splice(i, 1);
@@ -10741,14 +11058,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10741
11058
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
10742
11059
  if (idleExceeded && !wallExceeded) {
10743
11060
  this.subagents.get(ctx.subagentId)?.abortController.abort();
10744
- reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
11061
+ reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
10745
11062
  return;
10746
11063
  }
10747
11064
  if (!wallExceeded) {
10748
11065
  scheduleNext();
10749
11066
  return;
10750
11067
  }
10751
- const limit = wallLimit;
11068
+ const limit = wallLimit ?? 0;
10752
11069
  if (!budget.onThreshold) {
10753
11070
  this.subagents.get(ctx.subagentId)?.abortController.abort();
10754
11071
  reject(new BudgetExceededError("timeout", limit, elapsed));
@@ -10907,6 +11224,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10907
11224
  };
10908
11225
 
10909
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
+ }
10910
11233
  function sleep2(ms) {
10911
11234
  return new Promise((resolve5) => setTimeout(resolve5, ms));
10912
11235
  }
@@ -11065,7 +11388,7 @@ var ParallelEternalEngine = class {
11065
11388
  // Fan-out
11066
11389
  // -------------------------------------------------------------------------
11067
11390
  async fanOut(goal, tasks) {
11068
- const coordinator = this.coordinator;
11391
+ const coordinator = expectDefined9(this.coordinator);
11069
11392
  const slotCount = Math.min(this.slots, tasks.length);
11070
11393
  const routes = this.dispatchEnabled ? await Promise.all(
11071
11394
  tasks.slice(0, slotCount).map(
@@ -11097,7 +11420,7 @@ ${recentJournal}` : "No prior iterations.",
11097
11420
  const routeInfo = [];
11098
11421
  const spawnPromises = [];
11099
11422
  for (let i = 0; i < slotCount; i++) {
11100
- const task = tasks[i];
11423
+ const task = expectDefined9(tasks[i]);
11101
11424
  const route = routes[i] ?? null;
11102
11425
  const subagentId = `parallel-${this.iterations}-${i}`;
11103
11426
  const taskId = randomUUID();
@@ -11470,7 +11793,7 @@ var InMemoryBridgeTransport = class {
11470
11793
  }
11471
11794
  subscribe(agentId, handler) {
11472
11795
  if (!this.subs.has(agentId)) this.subs.set(agentId, /* @__PURE__ */ new Set());
11473
- this.subs.get(agentId).add(handler);
11796
+ this.subs.get(agentId)?.add(handler);
11474
11797
  return () => this.subs.get(agentId)?.delete(handler);
11475
11798
  }
11476
11799
  close(agentId) {
@@ -11588,6 +11911,12 @@ function createMessage(type, from, payload, to) {
11588
11911
  priority: "normal"
11589
11912
  };
11590
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
+ }
11591
11920
  var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
11592
11921
  var IS_WINDOWS = process.platform === "win32";
11593
11922
  var SEP = IS_WINDOWS ? "\\" : "/";
@@ -11601,7 +11930,7 @@ function globToRegex(pat) {
11601
11930
  let i = 0;
11602
11931
  let re = "^";
11603
11932
  while (i < pat.length) {
11604
- const c = pat[i];
11933
+ const c = expectDefined10(pat[i]);
11605
11934
  if (c === "*") {
11606
11935
  if (pat[i + 1] === "*") {
11607
11936
  re += ".*";
@@ -11640,7 +11969,7 @@ function globToRegex(pat) {
11640
11969
  }
11641
11970
  function baseDir(pat) {
11642
11971
  let i = pat.length - 1;
11643
- 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--;
11644
11973
  const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
11645
11974
  return cut < 0 ? "." : pat.slice(0, cut);
11646
11975
  }
@@ -11865,7 +12194,7 @@ var CollabSession = class extends EventEmitter {
11865
12194
  this.emit("session.error", error);
11866
12195
  throw error;
11867
12196
  }
11868
- for (const result of results.flat()) {
12197
+ for (const result of results?.flat() ?? []) {
11869
12198
  await this.parseAndEmit(result);
11870
12199
  }
11871
12200
  const report = this.assembleReport();
@@ -11926,7 +12255,7 @@ var CollabSession = class extends EventEmitter {
11926
12255
  }
11927
12256
  budgetForRole(role) {
11928
12257
  if (this.options.budgetOverrides?.[role]) {
11929
- return this.options.budgetOverrides[role];
12258
+ return this.options.budgetOverrides[role] ?? { maxIterations: 0, maxToolCalls: 0, timeoutMs: 0 };
11930
12259
  }
11931
12260
  const defaults = {
11932
12261
  "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
@@ -12379,7 +12708,7 @@ function makeSpawnTool(director, roster) {
12379
12708
  });
12380
12709
  const dispatchRole = dispatchResult.role;
12381
12710
  if (roster?.[dispatchRole]) {
12382
- cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
12711
+ cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole] ?? {});
12383
12712
  } else {
12384
12713
  const def = dispatchResult.definition;
12385
12714
  cfg = {
@@ -14676,7 +15005,10 @@ function attachAutoExtend(events, policy = {}) {
14676
15005
  if (kind === "timeout" || kind === "idle_timeout") {
14677
15006
  if (progress > lastTimeoutProgress) {
14678
15007
  lastTimeoutProgress = progress;
14679
- 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
+ );
14680
15012
  extend({ timeoutMs: next2 });
14681
15013
  } else {
14682
15014
  deny();
@@ -14690,7 +15022,7 @@ function attachAutoExtend(events, policy = {}) {
14690
15022
  }
14691
15023
  extendCounts.set(kind, count + 1);
14692
15024
  const field = FIELD_BY_KIND[kind];
14693
- const cap = ceiling[field];
15025
+ const cap = ceiling[field] ?? DEFAULT_CEILING[field];
14694
15026
  const next = Math.min(Math.ceil(limit * (1 + factor)), cap);
14695
15027
  extend({ [field]: next });
14696
15028
  })
@@ -15989,7 +16321,7 @@ var TaskTracker = class {
15989
16321
  if (filter.type?.length && !filter.type.includes(n.type)) return false;
15990
16322
  if (filter.assignee?.length && n.assignee && !filter.assignee.includes(n.assignee))
15991
16323
  return false;
15992
- 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)))
15993
16325
  return false;
15994
16326
  if (filter.specRequirementId && n.specRequirementId !== filter.specRequirementId)
15995
16327
  return false;
@@ -16503,6 +16835,12 @@ var TaskGraphStore = class {
16503
16835
  };
16504
16836
 
16505
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
+ }
16506
16844
  function buildQuestioningPrompt(session, min, max) {
16507
16845
  const answered = session.answers.length;
16508
16846
  const remaining = Math.max(0, min - answered);
@@ -16548,7 +16886,7 @@ function buildQuestioningPrompt(session, min, max) {
16548
16886
  if (answered > 0) {
16549
16887
  lines.push("", "**Conversation so far:**");
16550
16888
  for (let i = 0; i < answered; i++) {
16551
- const a = session.answers[i];
16889
+ const a = expectDefined11(session.answers[i]);
16552
16890
  lines.push(``, `Q${i + 1}: ${a.question}`, `A${i + 1}: ${a.answer}`);
16553
16891
  }
16554
16892
  }
@@ -17290,6 +17628,12 @@ function truncate2(str, maxLen) {
17290
17628
  }
17291
17629
 
17292
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
+ }
17293
17637
  function analyzeCriticalPath(graph) {
17294
17638
  const nodes = Array.from(graph.nodes.values());
17295
17639
  const topoOrder = topologicalSort(graph);
@@ -17298,9 +17642,9 @@ function analyzeCriticalPath(graph) {
17298
17642
  for (const edge of graph.edges) {
17299
17643
  if (edge.type === "depends_on") {
17300
17644
  if (!blockedByMap.has(edge.from)) blockedByMap.set(edge.from, /* @__PURE__ */ new Set());
17301
- blockedByMap.get(edge.from).add(edge.to);
17645
+ blockedByMap.get(edge.from)?.add(edge.to);
17302
17646
  if (!blocksMap.has(edge.to)) blocksMap.set(edge.to, /* @__PURE__ */ new Set());
17303
- blocksMap.get(edge.to).add(edge.from);
17647
+ blocksMap.get(edge.to)?.add(edge.from);
17304
17648
  }
17305
17649
  }
17306
17650
  const readyTasks = [];
@@ -17365,7 +17709,7 @@ function getTransitiveBlocked(_graph, taskId, blocksMap) {
17365
17709
  const visited = /* @__PURE__ */ new Set();
17366
17710
  const queue = [taskId];
17367
17711
  while (queue.length > 0) {
17368
- const current = queue.shift();
17712
+ const current = expectDefined12(queue.shift());
17369
17713
  const blocked = blocksMap.get(current);
17370
17714
  if (!blocked) continue;
17371
17715
  for (const id of blocked) {
@@ -17390,7 +17734,7 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
17390
17734
  for (const [taskId, blockers] of blockedByMap) {
17391
17735
  for (const blockerId of blockers) {
17392
17736
  if (!blocksMap.has(blockerId)) blocksMap.set(blockerId, /* @__PURE__ */ new Set());
17393
- blocksMap.get(blockerId).add(taskId);
17737
+ blocksMap.get(blockerId)?.add(taskId);
17394
17738
  }
17395
17739
  }
17396
17740
  const n = allIds.length;
@@ -17411,7 +17755,7 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
17411
17755
  if (!changed) break;
17412
17756
  }
17413
17757
  let maxDist = 0;
17414
- let maxId = allIds[0];
17758
+ let maxId = expectDefined12(allIds[0]);
17415
17759
  for (const id of allIds) {
17416
17760
  const d = dist.get(id) ?? 0;
17417
17761
  if (d > maxDist) {
@@ -17880,6 +18224,12 @@ var SddTaskDecomposer = class {
17880
18224
  return nodes.some((n) => n.status === "blocked");
17881
18225
  }
17882
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
+ }
17883
18233
  var SddParallelRun = class {
17884
18234
  constructor(opts) {
17885
18235
  this.opts = opts;
@@ -17980,8 +18330,10 @@ var SddParallelRun = class {
17980
18330
  "\u2022 Do not ask before routine in-project tool use; if a permission gate appears, wait for that flow.",
17981
18331
  "\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
17982
18332
  ].join("\n");
18333
+ if (!this.coordinator) throw new Error("SDD parallel runner requires a coordinator");
18334
+ const coordinator = this.coordinator;
17983
18335
  const spawns = subagentIds.map(
17984
- (subagentId) => this.coordinator.spawn({
18336
+ (subagentId) => coordinator.spawn({
17985
18337
  id: subagentId,
17986
18338
  name: subagentId,
17987
18339
  role: "executor",
@@ -17989,12 +18341,12 @@ var SddParallelRun = class {
17989
18341
  })
17990
18342
  );
17991
18343
  const spawnResults = await Promise.all(spawns);
17992
- if (!spawnResults.every((r) => r.subagentId)) {
18344
+ if (!spawnResults.every((r) => Boolean(r.subagentId))) {
17993
18345
  throw new Error("One or more subagent spawns failed");
17994
18346
  }
17995
18347
  const assignPromises = tasks.map((task, i) => {
17996
18348
  const spec = {
17997
- id: taskIds[i],
18349
+ id: taskIds[i] ?? task.id,
17998
18350
  description: [
17999
18351
  directivePreamble,
18000
18352
  "",
@@ -18003,15 +18355,15 @@ var SddParallelRun = class {
18003
18355
  "",
18004
18356
  task.description
18005
18357
  ].join("\n"),
18006
- subagentId: subagentIds[i],
18358
+ subagentId: subagentIds[i] ?? spawnResults[i]?.subagentId ?? task.id,
18007
18359
  timeoutMs: this.timeoutMs
18008
18360
  };
18009
- return this.coordinator.assign(spec);
18361
+ return this.coordinator?.assign(spec);
18010
18362
  });
18011
18363
  await Promise.all(assignPromises);
18012
18364
  let results;
18013
18365
  try {
18014
- results = await this.coordinator.awaitTasks(taskIds);
18366
+ results = await coordinator.awaitTasks(taskIds);
18015
18367
  } catch (err) {
18016
18368
  results = taskIds.map((id) => ({
18017
18369
  subagentId: "",
@@ -18026,8 +18378,8 @@ var SddParallelRun = class {
18026
18378
  const successCount = results.filter((r) => r.status === "success").length;
18027
18379
  const failCount = results.length - successCount;
18028
18380
  for (let i = 0; i < results.length; i++) {
18029
- const result = results[i];
18030
- const taskId = taskIds[i];
18381
+ const result = expectDefined13(results[i]);
18382
+ const taskId = expectDefined13(taskIds[i]);
18031
18383
  if (result.status === "success") {
18032
18384
  this.opts.tracker.updateNodeStatus(taskId, "completed");
18033
18385
  } else {