@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
@@ -410,7 +410,7 @@ var InMemoryBridgeTransport = class {
410
410
  }
411
411
  subscribe(agentId, handler) {
412
412
  if (!this.subs.has(agentId)) this.subs.set(agentId, /* @__PURE__ */ new Set());
413
- this.subs.get(agentId).add(handler);
413
+ this.subs.get(agentId)?.add(handler);
414
414
  return () => this.subs.get(agentId)?.delete(handler);
415
415
  }
416
416
  close(agentId) {
@@ -528,6 +528,12 @@ function createMessage(type, from, payload, to) {
528
528
  priority: "normal"
529
529
  };
530
530
  }
531
+ function expectDefined(value) {
532
+ if (value === null || value === void 0) {
533
+ throw new Error("Expected value to be defined");
534
+ }
535
+ return value;
536
+ }
531
537
  var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
532
538
  var IS_WINDOWS = process.platform === "win32";
533
539
  var SEP = IS_WINDOWS ? "\\" : "/";
@@ -541,7 +547,7 @@ function globToRegex(pat) {
541
547
  let i = 0;
542
548
  let re = "^";
543
549
  while (i < pat.length) {
544
- const c = pat[i];
550
+ const c = expectDefined(pat[i]);
545
551
  if (c === "*") {
546
552
  if (pat[i + 1] === "*") {
547
553
  re += ".*";
@@ -580,7 +586,7 @@ function globToRegex(pat) {
580
586
  }
581
587
  function baseDir(pat) {
582
588
  let i = pat.length - 1;
583
- while (i >= 0 && !GLOB_CHARS.has(pat[i]) && pat[i] !== SEP && pat[i] !== "/") i--;
589
+ while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
584
590
  const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
585
591
  return cut < 0 ? "." : pat.slice(0, cut);
586
592
  }
@@ -811,7 +817,7 @@ var CollabSession = class extends EventEmitter {
811
817
  this.emit("session.error", error);
812
818
  throw error;
813
819
  }
814
- for (const result of results.flat()) {
820
+ for (const result of results?.flat() ?? []) {
815
821
  await this.parseAndEmit(result);
816
822
  }
817
823
  const report = this.assembleReport();
@@ -872,7 +878,7 @@ var CollabSession = class extends EventEmitter {
872
878
  }
873
879
  budgetForRole(role) {
874
880
  if (this.options.budgetOverrides?.[role]) {
875
- return this.options.budgetOverrides[role];
881
+ return this.options.budgetOverrides[role] ?? { maxIterations: 0, maxToolCalls: 0, timeoutMs: 0 };
876
882
  }
877
883
  const defaults = {
878
884
  "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
@@ -3461,6 +3467,11 @@ function getAgentDefinition(role) {
3461
3467
 
3462
3468
  // src/coordination/dispatcher.ts
3463
3469
  var DEFAULT_DISPATCH_ROLE = "executor";
3470
+ var FALLBACK_DEFINITION = {
3471
+ config: { role: "unknown", name: "Unknown Agent" },
3472
+ budget: {},
3473
+ capability: { phase: "meta", summary: "", keywords: [] }
3474
+ };
3464
3475
  function normalize(text) {
3465
3476
  return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
3466
3477
  }
@@ -3488,7 +3499,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
3488
3499
  }
3489
3500
  function heuristicConfidence(candidates) {
3490
3501
  if (candidates.length === 0) return 0;
3491
- const top = candidates[0].score;
3502
+ const top = candidates[0]?.score ?? 0;
3492
3503
  const second = candidates[1]?.score ?? 0;
3493
3504
  const strength = Math.min(1, top / 3);
3494
3505
  const margin = (top - second + 1) / (top + 1);
@@ -3504,7 +3515,7 @@ async function dispatchAgent(task, opts = {}) {
3504
3515
  if (top && confidence >= threshold) {
3505
3516
  return {
3506
3517
  role: top.role,
3507
- definition: catalog[top.role],
3518
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
3508
3519
  confidence,
3509
3520
  method: "heuristic",
3510
3521
  reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
@@ -3512,7 +3523,7 @@ async function dispatchAgent(task, opts = {}) {
3512
3523
  };
3513
3524
  }
3514
3525
  if (opts.classifier) {
3515
- const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role]) : ALL_AGENT_DEFINITIONS).map((d) => ({
3526
+ const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role] ?? FALLBACK_DEFINITION) : ALL_AGENT_DEFINITIONS).map((d) => ({
3516
3527
  role: d.config.role,
3517
3528
  name: d.config.name,
3518
3529
  summary: d.capability.summary
@@ -3522,7 +3533,7 @@ async function dispatchAgent(task, opts = {}) {
3522
3533
  if (choice && catalog[choice.role]) {
3523
3534
  return {
3524
3535
  role: choice.role,
3525
- definition: catalog[choice.role],
3536
+ definition: catalog[choice.role] ?? FALLBACK_DEFINITION,
3526
3537
  confidence: 1,
3527
3538
  method: "llm",
3528
3539
  reason: choice.reason ?? "Selected by LLM classifier",
@@ -3535,17 +3546,17 @@ async function dispatchAgent(task, opts = {}) {
3535
3546
  if (top) {
3536
3547
  return {
3537
3548
  role: top.role,
3538
- definition: catalog[top.role],
3549
+ definition: catalog[top.role] ?? FALLBACK_DEFINITION,
3539
3550
  confidence,
3540
3551
  method: "heuristic",
3541
3552
  reason: `Weak match (${top.matched.slice(0, 3).join(", ") || "low signal"})`,
3542
3553
  alternatives: candidates.slice(1, maxCandidates)
3543
3554
  };
3544
3555
  }
3545
- const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0];
3556
+ const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0] ?? DEFAULT_DISPATCH_ROLE;
3546
3557
  return {
3547
3558
  role: fallbackRole,
3548
- definition: catalog[fallbackRole],
3559
+ definition: catalog[fallbackRole] ?? FALLBACK_DEFINITION,
3549
3560
  confidence: 0,
3550
3561
  method: "fallback",
3551
3562
  reason: "No keyword signal; defaulting to the generalist Executor",
@@ -3624,7 +3635,7 @@ function makeSpawnTool(director, roster) {
3624
3635
  });
3625
3636
  const dispatchRole = dispatchResult.role;
3626
3637
  if (roster?.[dispatchRole]) {
3627
- cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
3638
+ cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole] ?? {});
3628
3639
  } else {
3629
3640
  const def = dispatchResult.definition;
3630
3641
  cfg = {
@@ -4451,16 +4462,16 @@ var SubagentBudget = class _SubagentBudget {
4451
4462
  }
4452
4463
  if (exceeded.length === 0) return [];
4453
4464
  if (!this._onThreshold) {
4454
- const first2 = exceeded[0];
4465
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
4455
4466
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
4456
4467
  }
4457
4468
  if (this._mode === "sync") {
4458
- const first2 = exceeded[0];
4469
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
4459
4470
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
4460
4471
  }
4461
4472
  const bus = this._events;
4462
4473
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
4463
- const first2 = exceeded[0];
4474
+ const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
4464
4475
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
4465
4476
  }
4466
4477
  for (const entry of exceeded) {
@@ -4468,8 +4479,9 @@ var SubagentBudget = class _SubagentBudget {
4468
4479
  const decision2 = this._negotiateExtension(entry.kind, exceeded);
4469
4480
  this._pendingNegotiations.set(entry.kind, decision2);
4470
4481
  }
4471
- const first = exceeded[0];
4482
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
4472
4483
  const decision = this._pendingNegotiations.get(first.kind);
4484
+ if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
4473
4485
  throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
4474
4486
  }
4475
4487
  /**
@@ -4491,8 +4503,11 @@ var SubagentBudget = class _SubagentBudget {
4491
4503
  * a fresh signal.
4492
4504
  */
4493
4505
  async _negotiateExtension(kind, exceeded) {
4506
+ if (!this._onThreshold) {
4507
+ return "stop";
4508
+ }
4494
4509
  try {
4495
- const first = exceeded[0];
4510
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
4496
4511
  const result = this._onThreshold({
4497
4512
  kind: first.kind,
4498
4513
  used: first.used,
@@ -5515,6 +5530,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5515
5530
  takeNextDispatchableTask() {
5516
5531
  for (let i = 0; i < this.pendingTasks.length; i++) {
5517
5532
  const task = this.pendingTasks[i];
5533
+ if (!task) continue;
5518
5534
  const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
5519
5535
  if (!subagentId) continue;
5520
5536
  this.pendingTasks.splice(i, 1);
@@ -5706,14 +5722,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5706
5722
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
5707
5723
  if (idleExceeded && !wallExceeded) {
5708
5724
  this.subagents.get(ctx.subagentId)?.abortController.abort();
5709
- reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
5725
+ reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
5710
5726
  return;
5711
5727
  }
5712
5728
  if (!wallExceeded) {
5713
5729
  scheduleNext();
5714
5730
  return;
5715
5731
  }
5716
- const limit = wallLimit;
5732
+ const limit = wallLimit ?? 0;
5717
5733
  if (!budget.onThreshold) {
5718
5734
  this.subagents.get(ctx.subagentId)?.abortController.abort();
5719
5735
  reject(new BudgetExceededError("timeout", limit, elapsed));
@@ -7613,6 +7629,12 @@ function defaultFormatTaskInput(task) {
7613
7629
  }
7614
7630
 
7615
7631
  // src/utils/message-invariants.ts
7632
+ function expectDefined2(value) {
7633
+ if (value === null || value === void 0) {
7634
+ throw new Error("Expected value to be defined");
7635
+ }
7636
+ return value;
7637
+ }
7616
7638
  function repairToolUseAdjacency(messages) {
7617
7639
  const removedToolUses = [];
7618
7640
  const removedToolResults = [];
@@ -7620,7 +7642,7 @@ function repairToolUseAdjacency(messages) {
7620
7642
  let changed = false;
7621
7643
  const out = [];
7622
7644
  for (let i = 0; i < messages.length; i++) {
7623
- const original = messages[i];
7645
+ const original = expectDefined2(messages[i]);
7624
7646
  let msg = original;
7625
7647
  if (hasToolUse(msg)) {
7626
7648
  const nextIds = toolResultIds(messages[i + 1]);
@@ -7705,6 +7727,12 @@ function isEmptyMessage(msg) {
7705
7727
  }
7706
7728
 
7707
7729
  // src/storage/session-store.ts
7730
+ function expectDefined3(value) {
7731
+ if (value === null || value === void 0) {
7732
+ throw new Error("Expected value to be defined");
7733
+ }
7734
+ return value;
7735
+ }
7708
7736
  function sanitizeModel(model) {
7709
7737
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
7710
7738
  }
@@ -7715,7 +7743,7 @@ function generateSessionId(startedAt, model) {
7715
7743
  const modelPart = model ? `_${sanitizeModel(model)}` : "";
7716
7744
  return `${date}/${time}Z${modelPart}_${suffix}`;
7717
7745
  }
7718
- var DefaultSessionStore = class {
7746
+ var DefaultSessionStore = class _DefaultSessionStore {
7719
7747
  dir;
7720
7748
  events;
7721
7749
  secretScrubber;
@@ -7724,6 +7752,10 @@ var DefaultSessionStore = class {
7724
7752
  this.events = opts.events;
7725
7753
  this.secretScrubber = opts.secretScrubber;
7726
7754
  }
7755
+ /** Absolute path to the session index file. */
7756
+ get indexFile() {
7757
+ return path4.join(this.dir, "_index.jsonl");
7758
+ }
7727
7759
  /** Join session ID to its absolute path within the store directory. */
7728
7760
  sessionPath(id, ext) {
7729
7761
  return path4.join(this.dir, `${id}${ext}`);
@@ -7756,7 +7788,8 @@ var DefaultSessionStore = class {
7756
7788
  return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
7757
7789
  dir: shardDir,
7758
7790
  filePath: file,
7759
- secretScrubber: this.secretScrubber
7791
+ secretScrubber: this.secretScrubber,
7792
+ onClose: (s) => this.appendToIndex(s)
7760
7793
  });
7761
7794
  } catch (err) {
7762
7795
  await handle.close().catch(() => {
@@ -7787,7 +7820,7 @@ var DefaultSessionStore = class {
7787
7820
  provider: data.metadata.provider
7788
7821
  },
7789
7822
  this.events,
7790
- { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
7823
+ { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber, onClose: (s) => this.appendToIndex(s) }
7791
7824
  );
7792
7825
  return { writer, data };
7793
7826
  } catch (err) {
@@ -7817,6 +7850,15 @@ var DefaultSessionStore = class {
7817
7850
  async list(limit = 20) {
7818
7851
  try {
7819
7852
  await ensureDir(this.dir);
7853
+ const indexed = await this.readIndex();
7854
+ if (indexed.length > 0) {
7855
+ indexed.sort((a, b) => {
7856
+ if (a.startedAt < b.startedAt) return 1;
7857
+ if (a.startedAt > b.startedAt) return -1;
7858
+ return a.id.localeCompare(b.id);
7859
+ });
7860
+ return indexed.slice(0, limit);
7861
+ }
7820
7862
  const ids = await this.collectSessionIds(this.dir);
7821
7863
  const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
7822
7864
  const out = sessions.filter((s) => s !== null);
@@ -7830,16 +7872,121 @@ var DefaultSessionStore = class {
7830
7872
  return [];
7831
7873
  }
7832
7874
  }
7833
- /** Recursively collect all session IDs from shard subdirectories. */
7834
- async collectSessionIds(dir) {
7875
+ // ── Session index (_index.jsonl) ─────────────────────────────────────────
7876
+ //
7877
+ // One JSON line per closed session, appended atomically on close().
7878
+ // When a session is deleted, a tombstone {action:"delete",id:"..."} is
7879
+ // appended. On read, tombstones filter out matching session entries.
7880
+ // This keeps listing O(lines-in-index) instead of O(files-on-disk).
7881
+ //
7882
+ // The index auto-compacts every N appends to prevent unbounded growth
7883
+ // from tombstones and duplicate entries (resume cycles).
7884
+ indexAppendCount = 0;
7885
+ static COMPACT_EVERY = 30;
7886
+ /** Append a session summary to the index. */
7887
+ async appendToIndex(summary) {
7888
+ try {
7889
+ await ensureDir(this.dir);
7890
+ const line = JSON.stringify(summary) + "\n";
7891
+ await fsp6.appendFile(this.indexFile, line, "utf8");
7892
+ this.indexAppendCount++;
7893
+ if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
7894
+ await this.compactIndex();
7895
+ this.indexAppendCount = 0;
7896
+ }
7897
+ } catch {
7898
+ }
7899
+ }
7900
+ /** Append a tombstone entry for a deleted session. */
7901
+ async writeTombstone(id) {
7902
+ try {
7903
+ await ensureDir(this.dir);
7904
+ const line = JSON.stringify({ action: "delete", id }) + "\n";
7905
+ await fsp6.appendFile(this.indexFile, line, "utf8");
7906
+ this.indexAppendCount++;
7907
+ } catch {
7908
+ }
7909
+ }
7910
+ /**
7911
+ * Compact the index: read all entries, drop tombstones, deduplicate
7912
+ * (keep latest per session), and rewrite. Atomic via temp+rename.
7913
+ */
7914
+ async compactIndex() {
7915
+ const entries = await this.readIndex();
7916
+ if (entries.length === 0) return;
7917
+ const tmp = `${this.indexFile}.compact.tmp`;
7918
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
7919
+ await fsp6.writeFile(tmp, lines, "utf8");
7920
+ await fsp6.rename(tmp, this.indexFile);
7921
+ }
7922
+ /**
7923
+ * Read the index file and return deduplicated session summaries.
7924
+ * Entries with a matching tombstone are filtered out.
7925
+ * Returns empty array when the index doesn't exist or is corrupt.
7926
+ */
7927
+ async readIndex() {
7928
+ let raw;
7929
+ try {
7930
+ raw = await fsp6.readFile(this.indexFile, "utf8");
7931
+ } catch {
7932
+ return [];
7933
+ }
7934
+ const deleted = /* @__PURE__ */ new Set();
7935
+ const seen = /* @__PURE__ */ new Map();
7936
+ for (const line of raw.split("\n")) {
7937
+ if (!line.trim()) continue;
7938
+ try {
7939
+ const entry = JSON.parse(line);
7940
+ if (entry.action === "delete" && entry.id) {
7941
+ deleted.add(entry.id);
7942
+ seen.delete(entry.id);
7943
+ continue;
7944
+ }
7945
+ if (entry.id && !deleted.has(entry.id)) {
7946
+ seen.set(entry.id, entry);
7947
+ }
7948
+ } catch {
7949
+ }
7950
+ }
7951
+ return Array.from(seen.values());
7952
+ }
7953
+ /**
7954
+ * Rebuild the index from disk by scanning all sessions and writing a
7955
+ * fresh _index.jsonl. Useful after manual cleanup or index corruption.
7956
+ */
7957
+ async rebuildIndex() {
7958
+ const ids = await this.collectSessionIds(this.dir);
7959
+ const summaries = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
7960
+ const valid = summaries.filter((s) => s !== null);
7961
+ const tmp = `${this.indexFile}.tmp`;
7962
+ const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
7963
+ await fsp6.writeFile(tmp, lines, "utf8");
7964
+ await fsp6.rename(tmp, this.indexFile);
7965
+ return valid.length;
7966
+ }
7967
+ /** Recursively collect session IDs from date-shard subdirectories.
7968
+ * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
7969
+ * Skips `.jsonl`/`.summary.json` root files, dot-files, and
7970
+ * sub-directories that belong to fleet/subagent sessions. */
7971
+ async collectSessionIds(dir, prefix = "", depth = 0) {
7835
7972
  const ids = [];
7836
- const entries = await fsp6.readdir(dir, { withFileTypes: true });
7973
+ let entries;
7974
+ try {
7975
+ entries = await fsp6.readdir(dir, { withFileTypes: true });
7976
+ } catch {
7977
+ return ids;
7978
+ }
7837
7979
  for (const entry of entries) {
7838
- const full = path4.join(dir, entry.name);
7980
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
7981
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
7982
+ continue;
7839
7983
  if (entry.isDirectory()) {
7840
- ids.push(...await this.collectSessionIds(full));
7984
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
7985
+ ids.push(...await this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1));
7841
7986
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
7842
- ids.push(entry.name.replace(/\.jsonl$/, ""));
7987
+ if (entry.name === "_index.jsonl") continue;
7988
+ const base = entry.name.replace(/\.jsonl$/, "");
7989
+ ids.push(prefix ? `${prefix}/${base}` : base);
7843
7990
  }
7844
7991
  }
7845
7992
  return ids;
@@ -7862,9 +8009,70 @@ var DefaultSessionStore = class {
7862
8009
  return summary;
7863
8010
  }
7864
8011
  }
7865
- async delete(id) {
7866
- await fsp6.unlink(this.sessionPath(id, ".jsonl"));
8012
+ /**
8013
+ * Delete a session and all associated files: JSONL, summary, plan/todos
8014
+ * sidecars, and the session directory (fleet.json, shared/, subagents/).
8015
+ */
8016
+ async deleteSession(id) {
8017
+ await fsp6.unlink(this.sessionPath(id, ".jsonl")).catch(() => void 0);
7867
8018
  await fsp6.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
8019
+ const shardDir = path4.dirname(path4.join(this.dir, id));
8020
+ const base = path4.basename(id);
8021
+ for (const ext of [".plan.json", ".todos.json"]) {
8022
+ await fsp6.unlink(path4.join(shardDir, `${base}${ext}`)).catch(() => void 0);
8023
+ }
8024
+ const sessDir = path4.join(shardDir, base);
8025
+ await fsp6.rm(sessDir, { recursive: true, force: true }).catch(() => void 0);
8026
+ await this.writeTombstone(id);
8027
+ }
8028
+ async delete(id) {
8029
+ await this.deleteSession(id);
8030
+ }
8031
+ async prune(maxAgeDays = 30) {
8032
+ const cutoff = Date.now() - maxAgeDays * 864e5;
8033
+ let deleted = 0;
8034
+ let activeSessionId = null;
8035
+ try {
8036
+ const raw = await fsp6.readFile(path4.join(this.dir, "active.json"), "utf8");
8037
+ const active = JSON.parse(raw);
8038
+ activeSessionId = active.sessionId ?? null;
8039
+ } catch {
8040
+ }
8041
+ const entries = await fsp6.readdir(this.dir, { withFileTypes: true }).catch(() => []);
8042
+ for (const entry of entries) {
8043
+ if (!entry.isDirectory()) continue;
8044
+ const dateDir = path4.join(this.dir, entry.name);
8045
+ const files = await fsp6.readdir(dateDir, { withFileTypes: true }).catch(() => []);
8046
+ for (const file of files) {
8047
+ if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
8048
+ const jsonlPath = path4.join(dateDir, file.name);
8049
+ try {
8050
+ const stat4 = await fsp6.stat(jsonlPath);
8051
+ if (stat4.mtimeMs >= cutoff) continue;
8052
+ } catch {
8053
+ continue;
8054
+ }
8055
+ const id = `${entry.name}/${file.name.replace(/\.jsonl$/, "")}`;
8056
+ if (activeSessionId && id === activeSessionId) continue;
8057
+ await this.deleteSession(id);
8058
+ deleted++;
8059
+ }
8060
+ }
8061
+ if (deleted > 0) {
8062
+ await this.compactIndex().catch(() => void 0);
8063
+ }
8064
+ for (const entry of entries) {
8065
+ if (!entry.isDirectory()) continue;
8066
+ const dateDir = path4.join(this.dir, entry.name);
8067
+ try {
8068
+ const remaining = await fsp6.readdir(dateDir);
8069
+ if (remaining.length === 0) {
8070
+ await fsp6.rmdir(dateDir).catch(() => void 0);
8071
+ }
8072
+ } catch {
8073
+ }
8074
+ }
8075
+ return deleted;
7868
8076
  }
7869
8077
  async clearHistory(id) {
7870
8078
  await this.ensureShardDir(id);
@@ -7886,13 +8094,42 @@ var DefaultSessionStore = class {
7886
8094
  const data = await this.load(id);
7887
8095
  const firstUser = data.events.find((e) => e.type === "user_input");
7888
8096
  const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
8097
+ let iterationCount = 0;
8098
+ let toolCallCount = 0;
8099
+ let toolErrorCount = 0;
8100
+ let fileChangeCount = 0;
8101
+ const toolBreakdown = {};
8102
+ let outcome = void 0;
8103
+ const lastEvent = data.events[data.events.length - 1];
8104
+ for (const e of data.events) {
8105
+ if (e.type === "in_flight_start") iterationCount++;
8106
+ else if (e.type === "tool_call_start") {
8107
+ toolCallCount++;
8108
+ toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
8109
+ } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
8110
+ else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
8111
+ }
8112
+ if (lastEvent?.type === "session_end") {
8113
+ outcome = "completed";
8114
+ } else if (lastEvent?.type === "in_flight_start") {
8115
+ outcome = "aborted";
8116
+ } else if (data.events.some((e) => e.type === "error")) {
8117
+ outcome = "error";
8118
+ }
7889
8119
  return {
7890
8120
  id,
7891
8121
  title,
7892
8122
  startedAt: data.metadata.startedAt,
8123
+ endedAt: data.metadata.endedAt,
7893
8124
  model: data.metadata.model ?? "unknown",
7894
8125
  provider: data.metadata.provider ?? "unknown",
7895
- tokenTotal: data.usage.input + data.usage.output
8126
+ tokenTotal: data.usage.input + data.usage.output,
8127
+ iterationCount: iterationCount > 0 ? iterationCount : void 0,
8128
+ toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
8129
+ toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
8130
+ fileChangeCount: fileChangeCount > 0 ? fileChangeCount : void 0,
8131
+ toolBreakdown: Object.keys(toolBreakdown).length > 0 ? toolBreakdown : {},
8132
+ outcome
7896
8133
  };
7897
8134
  } catch {
7898
8135
  return {
@@ -7991,9 +8228,10 @@ var FileSessionWriter = class {
7991
8228
  this.meta = meta;
7992
8229
  this.events = events;
7993
8230
  this.resumed = opts.resumed ?? false;
7994
- this.manifestFile = opts.dir ? path4.join(opts.dir, `${id}.summary.json`) : "";
8231
+ this.manifestFile = opts.dir ? path4.join(opts.dir, `${path4.basename(id)}.summary.json`) : "";
7995
8232
  this.filePath = opts.filePath ?? "";
7996
8233
  this.secretScrubber = opts.secretScrubber;
8234
+ this.onCloseCb = opts.onClose;
7997
8235
  this.summary = {
7998
8236
  id,
7999
8237
  title: "(empty session)",
@@ -8023,6 +8261,15 @@ var FileSessionWriter = class {
8023
8261
  appendFailCount = 0;
8024
8262
  lastAppendWarnAt = 0;
8025
8263
  secretScrubber;
8264
+ onCloseCb;
8265
+ // ── Enriched summary tracking ──────────────────────────────────────────
8266
+ iterationCount = 0;
8267
+ toolCallCount = 0;
8268
+ toolErrorCount = 0;
8269
+ toolBreakdown = {};
8270
+ fileChangeCount = 0;
8271
+ compactionCount = 0;
8272
+ outcome = void 0;
8026
8273
  /**
8027
8274
  * Scrub secrets out of conversation-turn events before they are observed
8028
8275
  * for the summary, written to the JSONL log, or surfaced on resume. Only
@@ -8100,8 +8347,22 @@ var FileSessionWriter = class {
8100
8347
  observeForSummary(event) {
8101
8348
  if (event.type === "tool_use") {
8102
8349
  this.openToolUses.add(event.id);
8350
+ } else if (event.type === "tool_call_start") {
8351
+ this.toolCallCount++;
8352
+ this.toolBreakdown[event.name] = (this.toolBreakdown[event.name] ?? 0) + 1;
8103
8353
  } else if (event.type === "tool_result") {
8104
8354
  this.openToolUses.delete(event.id);
8355
+ if (event.isError) {
8356
+ this.toolErrorCount++;
8357
+ this.outcome = "error";
8358
+ }
8359
+ } else if (event.type === "file_snapshot") {
8360
+ this.fileChangeCount += event.files.length;
8361
+ } else if (event.type === "compaction") {
8362
+ this.compactionCount++;
8363
+ }
8364
+ if (event.type === "error" || event.type === "provider_error") {
8365
+ this.outcome = "error";
8105
8366
  }
8106
8367
  if (event.type === "user_input" && this.summary.title === "(empty session)") {
8107
8368
  this.summary = { ...this.summary, title: userInputTitle(event.content) };
@@ -8112,18 +8373,35 @@ var FileSessionWriter = class {
8112
8373
  } else if (event.type === "session_end") {
8113
8374
  const total = event.usage.input + event.usage.output;
8114
8375
  if (total > 0) this.summary = { ...this.summary, tokenTotal: total };
8376
+ } else if (event.type === "in_flight_start") {
8377
+ this.iterationCount++;
8115
8378
  }
8116
8379
  }
8117
8380
  async close() {
8118
8381
  if (this.closing) return;
8119
8382
  this.closing = true;
8120
8383
  this.closed = true;
8384
+ this.summary = {
8385
+ ...this.summary,
8386
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
8387
+ iterationCount: this.iterationCount,
8388
+ toolCallCount: this.toolCallCount,
8389
+ toolErrorCount: this.toolErrorCount,
8390
+ fileChangeCount: this.fileChangeCount,
8391
+ compactionCount: this.compactionCount > 0 ? this.compactionCount : void 0,
8392
+ toolBreakdown: { ...this.toolBreakdown },
8393
+ outcome: this.outcome ?? "completed"
8394
+ };
8121
8395
  if (this.manifestFile) {
8122
8396
  try {
8123
8397
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
8124
8398
  } catch {
8125
8399
  }
8126
8400
  }
8401
+ try {
8402
+ await this.onCloseCb?.(this.summary);
8403
+ } catch {
8404
+ }
8127
8405
  try {
8128
8406
  await this.handle.close();
8129
8407
  } catch {
@@ -8165,7 +8443,7 @@ var FileSessionWriter = class {
8165
8443
  let targetCheckpointLine = -1;
8166
8444
  let afterTarget = false;
8167
8445
  for (let i = 0; i < lines.length; i++) {
8168
- const line = lines[i];
8446
+ const line = expectDefined3(lines[i]);
8169
8447
  if (!line.trim()) continue;
8170
8448
  let event;
8171
8449
  try {
@@ -8331,7 +8609,10 @@ function attachAutoExtend(events, policy = {}) {
8331
8609
  if (kind === "timeout" || kind === "idle_timeout") {
8332
8610
  if (progress > lastTimeoutProgress) {
8333
8611
  lastTimeoutProgress = progress;
8334
- const next2 = Math.min(Math.ceil(limit * (1 + factor)), ceiling.timeoutMs);
8612
+ const next2 = Math.min(
8613
+ Math.ceil(limit * (1 + factor)),
8614
+ ceiling.timeoutMs ?? DEFAULT_CEILING.timeoutMs
8615
+ );
8335
8616
  extend({ timeoutMs: next2 });
8336
8617
  } else {
8337
8618
  deny();
@@ -8345,7 +8626,7 @@ function attachAutoExtend(events, policy = {}) {
8345
8626
  }
8346
8627
  extendCounts.set(kind, count + 1);
8347
8628
  const field = FIELD_BY_KIND[kind];
8348
- const cap = ceiling[field];
8629
+ const cap = ceiling[field] ?? DEFAULT_CEILING[field];
8349
8630
  const next = Math.min(Math.ceil(limit * (1 + factor)), cap);
8350
8631
  extend({ [field]: next });
8351
8632
  })