@wrongstack/core 0.77.0 → 0.84.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) 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 +32 -32
  10. package/dist/defaults/index.js +433 -81
  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 +72 -29
  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 +167 -167
  24. package/dist/index.js +617 -155
  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/{secret-scrubber-7rSC_emZ.d.ts → secret-scrubber-yGBFQYju.d.ts} +10 -2
  57. package/dist/security/index.d.ts +7 -7
  58. package/dist/security/index.js +15 -8
  59. package/dist/security/index.js.map +1 -1
  60. package/dist/{selector-RvBR_YRW.d.ts → selector-11-fm95U.d.ts} +2 -2
  61. package/dist/{session-event-bridge-CDHxcmQU.d.ts → session-event-bridge-D0u-x576.d.ts} +7 -7
  62. package/dist/{session-reader-BIpwM60D.d.ts → session-reader-BQU-toaN.d.ts} +23 -23
  63. package/dist/{skill-CxuWrsKK.d.ts → skill-BJeF2DwY.d.ts} +1 -1
  64. package/dist/skills/index.d.ts +9 -9
  65. package/dist/skills/index.js +15 -3
  66. package/dist/skills/index.js.map +1 -1
  67. package/dist/storage/index.d.ts +15 -15
  68. package/dist/storage/index.js +378 -76
  69. package/dist/storage/index.js.map +1 -1
  70. package/dist/{system-prompt-CA11g6Jo.d.ts → system-prompt-C0rLCeyn.d.ts} +16 -11
  71. package/dist/{task-graph-D1YQbpxF.d.ts → task-graph-CikNdRTG.d.ts} +22 -22
  72. package/dist/types/index.d.ts +26 -26
  73. package/dist/types/index.js +53 -17
  74. package/dist/types/index.js.map +1 -1
  75. package/dist/utils/index.d.ts +57 -45
  76. package/dist/utils/index.js +66 -12
  77. package/dist/utils/index.js.map +1 -1
  78. package/dist/{wstack-paths-D7evAFWM.d.ts → wstack-paths-BQMvEllz.d.ts} +2 -2
  79. package/package.json +1 -1
  80. 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
  };
@@ -3113,20 +3386,20 @@ var DefaultSecretVault = class {
3113
3386
  return key;
3114
3387
  }
3115
3388
  };
3116
- function decryptConfigSecrets2(cfg, vault) {
3389
+ function decryptConfigSecrets2(cfg, vault, opts) {
3390
+ const warn = opts?.warn ?? ((msg) => console.warn(msg));
3117
3391
  return walk2(cfg, vault, (v, key) => {
3118
3392
  try {
3119
3393
  return vault.decrypt(v);
3120
3394
  } catch (err) {
3121
- console.warn(
3122
- `[secret-vault] Failed to decrypt "${key}":`,
3123
- err instanceof Error ? err.message : err
3395
+ warn(
3396
+ `[secret-vault] Failed to decrypt "${key}": ${err instanceof Error ? err.message : err}`
3124
3397
  );
3125
3398
  return "";
3126
3399
  }
3127
3400
  });
3128
3401
  }
3129
- function encryptConfigSecrets(cfg, vault) {
3402
+ function encryptConfigSecrets(cfg, vault, _opts) {
3130
3403
  return walk2(cfg, vault, (v) => vault.encrypt(v));
3131
3404
  }
3132
3405
  function walk2(node, vault, transform) {
@@ -3187,7 +3460,8 @@ async function migratePlaintextSecrets(configPath, vault) {
3187
3460
  await restrictFilePermissions(configPath);
3188
3461
  return { migrated: counter.n, file: configPath };
3189
3462
  }
3190
- async function restrictFilePermissions(filePath) {
3463
+ async function restrictFilePermissions(filePath, opts) {
3464
+ const warn = ((msg) => console.warn(msg));
3191
3465
  if (process.platform === "win32") {
3192
3466
  try {
3193
3467
  const { execFile: execFile2 } = await import('child_process');
@@ -3195,7 +3469,7 @@ async function restrictFilePermissions(filePath) {
3195
3469
  const execFileAsync = promisify2(execFile2);
3196
3470
  await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${process.env.USERNAME}:(F)`]);
3197
3471
  } catch {
3198
- console.warn(`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`);
3472
+ warn(`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`);
3199
3473
  }
3200
3474
  } else {
3201
3475
  try {
@@ -3291,6 +3565,12 @@ function getDangerousCapabilities(toolOrCaps) {
3291
3565
  init_atomic_write();
3292
3566
 
3293
3567
  // src/utils/glob-match.ts
3568
+ function expectDefined4(value) {
3569
+ if (value === null || value === void 0) {
3570
+ throw new Error("Expected value to be defined");
3571
+ }
3572
+ return value;
3573
+ }
3294
3574
  function escapeRegex(s) {
3295
3575
  return s.replace(/[.+^${}()|\\]/g, "\\$&");
3296
3576
  }
@@ -3302,7 +3582,7 @@ function getCachedGlob(pattern) {
3302
3582
  if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
3303
3583
  const keys = [...COMPILED_GLOB_CACHE.keys()];
3304
3584
  for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
3305
- COMPILED_GLOB_CACHE.delete(keys[i]);
3585
+ COMPILED_GLOB_CACHE.delete(expectDefined4(keys[i]));
3306
3586
  }
3307
3587
  }
3308
3588
  const re = compileGlob(pattern);
@@ -4160,13 +4440,19 @@ function parseDescription(raw) {
4160
4440
  const scope = [];
4161
4441
  const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
4162
4442
  if (coversMatch) {
4163
- const items = coversMatch[1].replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
4443
+ const items = coversMatch[1] ?? "".replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
4164
4444
  scope.push(...items);
4165
4445
  }
4166
4446
  return { trigger, scope };
4167
4447
  }
4168
4448
 
4169
4449
  // src/utils/json-repair.ts
4450
+ function expectDefined5(value) {
4451
+ if (value === null || value === void 0) {
4452
+ throw new Error("Expected value to be defined");
4453
+ }
4454
+ return value;
4455
+ }
4170
4456
  function completePartialObject(s) {
4171
4457
  if (!s.trim().startsWith("{")) return s;
4172
4458
  if (tryParse(s).ok) return s;
@@ -4178,7 +4464,7 @@ function completePartialObject(s) {
4178
4464
  let contentEnd = 0;
4179
4465
  let stringBraceDepth = 0;
4180
4466
  for (let i = 0; i < s.length; i++) {
4181
- const ch = s[i];
4467
+ const ch = expectDefined5(s[i]);
4182
4468
  if (inString) {
4183
4469
  contentEnd = i + 1;
4184
4470
  if (escaped) {
@@ -4314,7 +4600,7 @@ function handleContentBlockStart(state, ev) {
4314
4600
  state.textBuffers.push("");
4315
4601
  state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
4316
4602
  } else if (kind === "tool_use") {
4317
- const id = ev.id ?? crypto.randomUUID();
4603
+ const id = ev.id ?? randomUUID();
4318
4604
  state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
4319
4605
  state.blockOrder.push({ kind: "tool", id });
4320
4606
  state.currentTextIndex = -1;
@@ -4563,6 +4849,12 @@ var DefaultProviderRunner = class {
4563
4849
  };
4564
4850
 
4565
4851
  // src/utils/token-estimate.ts
4852
+ function expectDefined6(value) {
4853
+ if (value === null || value === void 0) {
4854
+ throw new Error("Expected value to be defined");
4855
+ }
4856
+ return value;
4857
+ }
4566
4858
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
4567
4859
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
4568
4860
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
@@ -4572,7 +4864,7 @@ function getCachedEstimate(key, compute) {
4572
4864
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
4573
4865
  const keys = [...ESTIMATE_CACHE.keys()];
4574
4866
  for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
4575
- ESTIMATE_CACHE.delete(keys[i]);
4867
+ ESTIMATE_CACHE.delete(expectDefined6(keys[i]));
4576
4868
  }
4577
4869
  }
4578
4870
  const estimate = compute();
@@ -4962,7 +5254,7 @@ var IntelligentCompactor = class {
4962
5254
  maxTokens: 1024
4963
5255
  };
4964
5256
  const ac = ctx.signal ? void 0 : new AbortController();
4965
- const signal = ctx.signal ?? ac.signal;
5257
+ const signal = ctx.signal ?? ac?.signal;
4966
5258
  const res = await this.provider.complete(req, { signal });
4967
5259
  const textBlocks = res.content.filter(isTextBlock);
4968
5260
  return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
@@ -5065,6 +5357,12 @@ var IntelligentCompactor = class {
5065
5357
  };
5066
5358
 
5067
5359
  // src/models/llm-selector.ts
5360
+ function expectDefined7(value) {
5361
+ if (value === null || value === void 0) {
5362
+ throw new Error("Expected value to be defined");
5363
+ }
5364
+ return value;
5365
+ }
5068
5366
  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
5367
 
5070
5368
  Output a JSON object with this structure:
@@ -5105,7 +5403,7 @@ function formatMessages(messages, maxChars = 8e3) {
5105
5403
  const lines = [];
5106
5404
  let used = 0;
5107
5405
  for (let i = 0; i < messages.length; i++) {
5108
- const m = messages[i];
5406
+ const m = expectDefined7(messages[i]);
5109
5407
  const role = m.role.padEnd(10, " ");
5110
5408
  let text;
5111
5409
  if (typeof m.content === "string") {
@@ -5170,7 +5468,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
5170
5468
  let tokenCount = 0;
5171
5469
  let startIdx = 0;
5172
5470
  for (let i = messages.length - 1; i >= 0; i--) {
5173
- const m = messages[i];
5471
+ const m = expectDefined7(messages[i]);
5174
5472
  const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
5175
5473
  (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
5176
5474
  0
@@ -5381,6 +5679,7 @@ Summarize the following message range:`;
5381
5679
  let boundary = preserveIdx;
5382
5680
  for (let i = preserveIdx; i < messages.length && i < preserveIdx + 6; i++) {
5383
5681
  const m = messages[i];
5682
+ if (!m) continue;
5384
5683
  if (m.role === "user" && this.hasTextContent(m)) {
5385
5684
  boundary = i;
5386
5685
  break;
@@ -5776,6 +6075,12 @@ function createToolOutputSerializer(opts = {}) {
5776
6075
  }
5777
6076
 
5778
6077
  // src/execution/tool-executor.ts
6078
+ function expectDefined8(value) {
6079
+ if (value === null || value === void 0) {
6080
+ throw new Error("Expected value to be defined");
6081
+ }
6082
+ return value;
6083
+ }
5779
6084
  var ToolExecutor = class {
5780
6085
  constructor(registry, opts) {
5781
6086
  this.registry = registry;
@@ -6064,6 +6369,9 @@ ${post.additionalContext}` };
6064
6369
  async runStreamedTool(tool, input, ctx, signal, toolUseId) {
6065
6370
  let finalOutput;
6066
6371
  let sawFinal = false;
6372
+ if (!tool.executeStream) {
6373
+ throw new Error(`Tool "${tool.name}" does not support streaming execution`);
6374
+ }
6067
6375
  const stream = tool.executeStream(input, ctx, { signal });
6068
6376
  for await (const ev of stream) {
6069
6377
  if (ev.type === "final") {
@@ -6168,7 +6476,7 @@ function hasMalformedArguments(input) {
6168
6476
  function extractMalformedRaw(input) {
6169
6477
  if (!hasMalformedArguments(input)) return void 0;
6170
6478
  const obj = input;
6171
- const value = obj[Object.keys(obj)[0]];
6479
+ const value = obj[expectDefined8(Object.keys(obj)[0])];
6172
6480
  if (value === void 0 || value === null) return void 0;
6173
6481
  if (typeof value === "string") return value;
6174
6482
  try {
@@ -6393,6 +6701,11 @@ function appendJournal(goal, entry) {
6393
6701
  };
6394
6702
  }
6395
6703
 
6704
+ // src/utils/sleep.ts
6705
+ function sleep(ms) {
6706
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
6707
+ }
6708
+
6396
6709
  // src/execution/eternal-autonomy.ts
6397
6710
  var execFileP = promisify(execFile);
6398
6711
  var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
@@ -6963,9 +7276,6 @@ ${recentJournal}` : "No prior iterations.",
6963
7276
  await saveGoal(this.goalPath, { ...current, engineState: state });
6964
7277
  }
6965
7278
  };
6966
- function sleep(ms) {
6967
- return new Promise((resolve5) => setTimeout(resolve5, ms));
6968
- }
6969
7279
 
6970
7280
  // src/coordination/subagent-budget.ts
6971
7281
  var BudgetExceededError = class extends Error {
@@ -7129,16 +7439,16 @@ var SubagentBudget = class _SubagentBudget {
7129
7439
  }
7130
7440
  if (exceeded.length === 0) return [];
7131
7441
  if (!this._onThreshold) {
7132
- const first2 = exceeded[0];
7442
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7133
7443
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7134
7444
  }
7135
7445
  if (this._mode === "sync") {
7136
- const first2 = exceeded[0];
7446
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7137
7447
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7138
7448
  }
7139
7449
  const bus = this._events;
7140
7450
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
7141
- const first2 = exceeded[0];
7451
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7142
7452
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
7143
7453
  }
7144
7454
  for (const entry of exceeded) {
@@ -7146,8 +7456,9 @@ var SubagentBudget = class _SubagentBudget {
7146
7456
  const decision2 = this._negotiateExtension(entry.kind, exceeded);
7147
7457
  this._pendingNegotiations.set(entry.kind, decision2);
7148
7458
  }
7149
- const first = exceeded[0];
7459
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7150
7460
  const decision = this._pendingNegotiations.get(first.kind);
7461
+ if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
7151
7462
  throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
7152
7463
  }
7153
7464
  /**
@@ -7169,8 +7480,11 @@ var SubagentBudget = class _SubagentBudget {
7169
7480
  * a fresh signal.
7170
7481
  */
7171
7482
  async _negotiateExtension(kind, exceeded) {
7483
+ if (!this._onThreshold) {
7484
+ return "stop";
7485
+ }
7172
7486
  try {
7173
- const first = exceeded[0];
7487
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
7174
7488
  const result = this._onThreshold({
7175
7489
  kind: first.kind,
7176
7490
  used: first.used,
@@ -9653,6 +9967,11 @@ function getAgentDefinition(role) {
9653
9967
 
9654
9968
  // src/coordination/dispatcher.ts
9655
9969
  var DEFAULT_DISPATCH_ROLE = "executor";
9970
+ var FALLBACK_DEFINITION = {
9971
+ config: { role: "unknown", name: "Unknown Agent" },
9972
+ budget: {},
9973
+ capability: { phase: "meta", summary: "", keywords: [] }
9974
+ };
9656
9975
  function normalize(text) {
9657
9976
  return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
9658
9977
  }
@@ -9680,7 +9999,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
9680
9999
  }
9681
10000
  function heuristicConfidence(candidates) {
9682
10001
  if (candidates.length === 0) return 0;
9683
- const top = candidates[0].score;
10002
+ const top = candidates[0]?.score ?? 0;
9684
10003
  const second = candidates[1]?.score ?? 0;
9685
10004
  const strength = Math.min(1, top / 3);
9686
10005
  const margin = (top - second + 1) / (top + 1);
@@ -9696,7 +10015,7 @@ async function dispatchAgent(task, opts = {}) {
9696
10015
  if (top && confidence >= threshold) {
9697
10016
  return {
9698
10017
  role: top.role,
9699
- definition: catalog[top.role],
10018
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
9700
10019
  confidence,
9701
10020
  method: "heuristic",
9702
10021
  reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
@@ -9704,7 +10023,7 @@ async function dispatchAgent(task, opts = {}) {
9704
10023
  };
9705
10024
  }
9706
10025
  if (opts.classifier) {
9707
- const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role]) : ALL_AGENT_DEFINITIONS).map((d) => ({
10026
+ const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role] ?? FALLBACK_DEFINITION) : ALL_AGENT_DEFINITIONS).map((d) => ({
9708
10027
  role: d.config.role,
9709
10028
  name: d.config.name,
9710
10029
  summary: d.capability.summary
@@ -9714,7 +10033,7 @@ async function dispatchAgent(task, opts = {}) {
9714
10033
  if (choice && catalog[choice.role]) {
9715
10034
  return {
9716
10035
  role: choice.role,
9717
- definition: catalog[choice.role],
10036
+ definition: catalog[choice.role] ?? FALLBACK_DEFINITION,
9718
10037
  confidence: 1,
9719
10038
  method: "llm",
9720
10039
  reason: choice.reason ?? "Selected by LLM classifier",
@@ -9727,17 +10046,17 @@ async function dispatchAgent(task, opts = {}) {
9727
10046
  if (top) {
9728
10047
  return {
9729
10048
  role: top.role,
9730
- definition: catalog[top.role],
10049
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
9731
10050
  confidence,
9732
10051
  method: "heuristic",
9733
10052
  reason: `Weak match (${top.matched.slice(0, 3).join(", ") || "low signal"})`,
9734
10053
  alternatives: candidates.slice(1, maxCandidates)
9735
10054
  };
9736
10055
  }
9737
- const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0];
10056
+ const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0] ?? DEFAULT_DISPATCH_ROLE;
9738
10057
  return {
9739
10058
  role: fallbackRole,
9740
- definition: catalog[fallbackRole],
10059
+ definition: catalog[fallbackRole] ?? FALLBACK_DEFINITION,
9741
10060
  confidence: 0,
9742
10061
  method: "fallback",
9743
10062
  reason: "No keyword signal; defaulting to the generalist Executor",
@@ -10550,6 +10869,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10550
10869
  takeNextDispatchableTask() {
10551
10870
  for (let i = 0; i < this.pendingTasks.length; i++) {
10552
10871
  const task = this.pendingTasks[i];
10872
+ if (!task) continue;
10553
10873
  const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
10554
10874
  if (!subagentId) continue;
10555
10875
  this.pendingTasks.splice(i, 1);
@@ -10741,14 +11061,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10741
11061
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
10742
11062
  if (idleExceeded && !wallExceeded) {
10743
11063
  this.subagents.get(ctx.subagentId)?.abortController.abort();
10744
- reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
11064
+ reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
10745
11065
  return;
10746
11066
  }
10747
11067
  if (!wallExceeded) {
10748
11068
  scheduleNext();
10749
11069
  return;
10750
11070
  }
10751
- const limit = wallLimit;
11071
+ const limit = wallLimit ?? 0;
10752
11072
  if (!budget.onThreshold) {
10753
11073
  this.subagents.get(ctx.subagentId)?.abortController.abort();
10754
11074
  reject(new BudgetExceededError("timeout", limit, elapsed));
@@ -10907,8 +11227,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10907
11227
  };
10908
11228
 
10909
11229
  // src/execution/parallel-eternal-engine.ts
10910
- function sleep2(ms) {
10911
- return new Promise((resolve5) => setTimeout(resolve5, ms));
11230
+ function expectDefined9(value) {
11231
+ if (value === null || value === void 0) {
11232
+ throw new Error("Expected value to be defined");
11233
+ }
11234
+ return value;
10912
11235
  }
10913
11236
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
10914
11237
  var ParallelEternalEngine = class {
@@ -10984,7 +11307,7 @@ var ParallelEternalEngine = class {
10984
11307
  );
10985
11308
  }
10986
11309
  if (this.stopRequested) break;
10987
- await sleep2(2e3);
11310
+ await sleep(2e3);
10988
11311
  }
10989
11312
  } finally {
10990
11313
  this.state = "stopped";
@@ -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 {