@wrongstack/core 0.73.1 → 0.82.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/{agent-bridge-C0Ze7Ldm.d.ts → agent-bridge-C9P_HPez.d.ts} +2 -2
  2. package/dist/{agent-subagent-runner-BmITbs1Q.d.ts → agent-subagent-runner-2Aq0jOSj.d.ts} +107 -102
  3. package/dist/{compactor-D_ExJajC.d.ts → compactor-CJq7LQev.d.ts} +3 -3
  4. package/dist/{config-Dy0CK_o6.d.ts → config-_DZ7dN-T.d.ts} +77 -75
  5. package/dist/{context-y87Jc5ei.d.ts → context-ToHAp4-U.d.ts} +119 -90
  6. package/dist/coordination/index.d.ts +16 -16
  7. package/dist/coordination/index.js +382 -43
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +31 -31
  10. package/dist/defaults/index.js +524 -110
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{director-state-BmYi3DGA.d.ts → director-state-CgIc30qi.d.ts} +19 -19
  13. package/dist/{events-BBAlxBuw.d.ts → events-DnRqXaZ3.d.ts} +77 -39
  14. package/dist/execution/index.d.ts +53 -53
  15. package/dist/execution/index.js +67 -23
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/extension/index.d.ts +9 -9
  18. package/dist/extension/index.js +8 -1
  19. package/dist/extension/index.js.map +1 -1
  20. package/dist/{goal-store-C7jcumEh.d.ts → goal-store-DvWLNu52.d.ts} +4 -4
  21. package/dist/{index-yQbZ2NQx.d.ts → index-BNOLadHw.d.ts} +28 -28
  22. package/dist/{index-BN6i2Nfg.d.ts → index-N0_c4bHQ.d.ts} +45 -45
  23. package/dist/index.d.ts +233 -160
  24. package/dist/index.js +825 -160
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +9 -9
  27. package/dist/infrastructure/index.js +29 -7
  28. package/dist/infrastructure/index.js.map +1 -1
  29. package/dist/kernel/index.d.ts +14 -14
  30. package/dist/kernel/index.js +7 -0
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/logger-B72yyPc6.d.ts +12 -0
  33. package/dist/{logger-bOzkF5LL.d.ts → logger-C_27pj9i.d.ts} +12 -4
  34. package/dist/{mcp-servers-T0O6UN_w.d.ts → mcp-servers-Dck3T85_.d.ts} +20 -20
  35. package/dist/{mode-BO4SEUIv.d.ts → mode-CHo2XtHs.d.ts} +4 -4
  36. package/dist/models/index.d.ts +10 -10
  37. package/dist/models/index.js +8 -2
  38. package/dist/models/index.js.map +1 -1
  39. package/dist/{models-registry-BcYJDKLm.d.ts → models-registry-Be3osGt5.d.ts} +28 -28
  40. package/dist/{models-registry-Cuq1C8V9.d.ts → models-registry-Boz639EI.d.ts} +12 -12
  41. package/dist/{multi-agent-coordinator-BSBbZt0e.d.ts → multi-agent-coordinator-DllpCVkF.d.ts} +12 -12
  42. package/dist/{null-fleet-bus-BCIRT_nV.d.ts → null-fleet-bus-BY0AN-sr.d.ts} +129 -120
  43. package/dist/observability/index.d.ts +41 -41
  44. package/dist/observability/index.js.map +1 -1
  45. package/dist/{observability-BhnVLBLS.d.ts → observability-CoSNZdhX.d.ts} +4 -4
  46. package/dist/{parallel-eternal-engine-CjAYGaCw.d.ts → parallel-eternal-engine-D402RASp.d.ts} +49 -49
  47. package/dist/{path-resolver-BnqXa9Ze.d.ts → path-resolver-UPFTsDyD.d.ts} +6 -6
  48. package/dist/{permission-V5BLOrY6.d.ts → permission-14CChMmO.d.ts} +10 -8
  49. package/dist/{permission-policy-CBVx-d-8.d.ts → permission-policy-gW5htOo1.d.ts} +7 -7
  50. package/dist/{plan-templates-DBgrTGPu.d.ts → plan-templates-DRvPgkfZ.d.ts} +70 -32
  51. package/dist/{provider-runner-n3KkHT_w.d.ts → provider-runner-COAJM8tC.d.ts} +6 -6
  52. package/dist/{retry-policy-CG3qvH_e.d.ts → retry-policy-DSu6O6rD.d.ts} +4 -4
  53. package/dist/sdd/index.d.ts +47 -47
  54. package/dist/sdd/index.js +47 -22
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/security/index.d.ts +6 -6
  57. package/dist/security/index.js +7 -1
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-RvBR_YRW.d.ts → selector-11-fm95U.d.ts} +2 -2
  60. package/dist/{session-event-bridge-CDHxcmQU.d.ts → session-event-bridge-D0u-x576.d.ts} +7 -7
  61. package/dist/{session-reader-BIpwM60D.d.ts → session-reader-BQU-toaN.d.ts} +23 -23
  62. package/dist/{skill-CxuWrsKK.d.ts → skill-BJeF2DwY.d.ts} +1 -1
  63. package/dist/skills/index.d.ts +9 -9
  64. package/dist/skills/index.js +15 -3
  65. package/dist/skills/index.js.map +1 -1
  66. package/dist/storage/index.d.ts +15 -15
  67. package/dist/storage/index.js +398 -80
  68. package/dist/storage/index.js.map +1 -1
  69. package/dist/{system-prompt-CA11g6Jo.d.ts → system-prompt-C0rLCeyn.d.ts} +16 -11
  70. package/dist/{task-graph-D1YQbpxF.d.ts → task-graph-CikNdRTG.d.ts} +22 -22
  71. package/dist/types/index.d.ts +25 -25
  72. package/dist/types/index.js +61 -12
  73. package/dist/types/index.js.map +1 -1
  74. package/dist/utils/index.d.ts +46 -45
  75. package/dist/utils/index.js +64 -13
  76. package/dist/utils/index.js.map +1 -1
  77. package/dist/{wstack-paths-eMXnY1_X.d.ts → wstack-paths-BQMvEllz.d.ts} +10 -3
  78. package/package.json +1 -1
  79. package/dist/logger-DDd5C--Z.d.ts +0 -12
@@ -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));
@@ -7131,6 +7147,7 @@ function createDelegateTool(opts) {
7131
7147
  if (typeof i.task !== "string" || !i.task.trim()) {
7132
7148
  return { ok: false, error: "`task` is required." };
7133
7149
  }
7150
+ const target = i.role ?? i.name ?? "subagent";
7134
7151
  try {
7135
7152
  let director = await opts.host.ensureDirector();
7136
7153
  if (!director) {
@@ -7191,6 +7208,7 @@ function createDelegateTool(opts) {
7191
7208
  if (!cfg.timeoutMs) {
7192
7209
  cfg.timeoutMs = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
7193
7210
  }
7211
+ opts.events?.emit("delegate.started", { target, task: i.task });
7194
7212
  const subagentId = await director.spawn(cfg);
7195
7213
  const taskId = await director.assign({
7196
7214
  id: `${randomUUID()}`,
@@ -7225,6 +7243,17 @@ function createDelegateTool(opts) {
7225
7243
  });
7226
7244
  if ("__timeout" in result) {
7227
7245
  const partial2 = await readSubagentPartial(opts, subagentId);
7246
+ opts.events?.emit("delegate.completed", {
7247
+ target,
7248
+ task: i.task,
7249
+ ok: false,
7250
+ status: "host_timeout",
7251
+ summary: `[${target}] timed out \u2014 no result within ${Math.round(timeoutMs / 1e3)}s`,
7252
+ durationMs: timeoutMs,
7253
+ iterations: partial2?.events ?? 0,
7254
+ toolCalls: partial2?.toolUsesObserved ?? 0,
7255
+ subagentId
7256
+ });
7228
7257
  return {
7229
7258
  ok: false,
7230
7259
  stopReason: "host_timeout",
@@ -7241,6 +7270,24 @@ function createDelegateTool(opts) {
7241
7270
  const retryable = result.error?.retryable;
7242
7271
  const backoffMs = result.error?.backoffMs;
7243
7272
  const summary = buildDelegateSummary(i.role, result);
7273
+ let costUsd;
7274
+ try {
7275
+ costUsd = dir.snapshot().perSubagent[result.subagentId]?.cost;
7276
+ } catch {
7277
+ costUsd = void 0;
7278
+ }
7279
+ opts.events?.emit("delegate.completed", {
7280
+ target,
7281
+ task: i.task,
7282
+ ok: result.status === "success",
7283
+ status: result.status,
7284
+ summary,
7285
+ durationMs: result.durationMs,
7286
+ iterations: result.iterations,
7287
+ toolCalls: result.toolCalls,
7288
+ costUsd,
7289
+ subagentId: result.subagentId
7290
+ });
7244
7291
  return {
7245
7292
  ok: result.status === "success",
7246
7293
  status: result.status,
@@ -7262,10 +7309,21 @@ function createDelegateTool(opts) {
7262
7309
  summary
7263
7310
  };
7264
7311
  } catch (err) {
7312
+ const message = err instanceof Error ? err.message : String(err);
7313
+ opts.events?.emit("delegate.completed", {
7314
+ target,
7315
+ task: i.task,
7316
+ ok: false,
7317
+ status: "error",
7318
+ summary: `[${target}] failed \u2014 ${message}`,
7319
+ durationMs: 0,
7320
+ iterations: 0,
7321
+ toolCalls: 0
7322
+ });
7265
7323
  return {
7266
7324
  ok: false,
7267
7325
  stopReason: "error",
7268
- error: err instanceof Error ? err.message : String(err)
7326
+ error: message
7269
7327
  };
7270
7328
  }
7271
7329
  }
@@ -7571,6 +7629,12 @@ function defaultFormatTaskInput(task) {
7571
7629
  }
7572
7630
 
7573
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
+ }
7574
7638
  function repairToolUseAdjacency(messages) {
7575
7639
  const removedToolUses = [];
7576
7640
  const removedToolResults = [];
@@ -7578,7 +7642,7 @@ function repairToolUseAdjacency(messages) {
7578
7642
  let changed = false;
7579
7643
  const out = [];
7580
7644
  for (let i = 0; i < messages.length; i++) {
7581
- const original = messages[i];
7645
+ const original = expectDefined2(messages[i]);
7582
7646
  let msg = original;
7583
7647
  if (hasToolUse(msg)) {
7584
7648
  const nextIds = toolResultIds(messages[i + 1]);
@@ -7663,7 +7727,23 @@ function isEmptyMessage(msg) {
7663
7727
  }
7664
7728
 
7665
7729
  // src/storage/session-store.ts
7666
- var DefaultSessionStore = class {
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
+ }
7736
+ function sanitizeModel(model) {
7737
+ return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
7738
+ }
7739
+ function generateSessionId(startedAt, model) {
7740
+ const date = startedAt.slice(0, 10);
7741
+ const time = startedAt.slice(11, 19).replace(/:/g, "-");
7742
+ const suffix = randomBytes(2).toString("hex");
7743
+ const modelPart = model ? `_${sanitizeModel(model)}` : "";
7744
+ return `${date}/${time}Z${modelPart}_${suffix}`;
7745
+ }
7746
+ var DefaultSessionStore = class _DefaultSessionStore {
7667
7747
  dir;
7668
7748
  events;
7669
7749
  secretScrubber;
@@ -7672,19 +7752,29 @@ var DefaultSessionStore = class {
7672
7752
  this.events = opts.events;
7673
7753
  this.secretScrubber = opts.secretScrubber;
7674
7754
  }
7755
+ /** Absolute path to the session index file. */
7756
+ get indexFile() {
7757
+ return path4.join(this.dir, "_index.jsonl");
7758
+ }
7675
7759
  /** Join session ID to its absolute path within the store directory. */
7676
7760
  sessionPath(id, ext) {
7677
7761
  return path4.join(this.dir, `${id}${ext}`);
7678
7762
  }
7679
- async ensureShardDir(_id) {
7680
- await ensureDir(this.dir);
7681
- return this.dir;
7763
+ /**
7764
+ * Ensure the directory implied by the session ID exists. When the ID
7765
+ * contains a date prefix like `2026-06-06/...`, this creates the date
7766
+ * subdirectory so sessions group naturally by day.
7767
+ */
7768
+ async ensureShardDir(id) {
7769
+ const dirPath = path4.dirname(path4.join(this.dir, id));
7770
+ await ensureDir(dirPath);
7771
+ return dirPath;
7682
7772
  }
7683
7773
  async create(meta) {
7684
7774
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7685
- const id = meta.id ?? `${startedAt.replace(/[:.]/g, "-")}-${randomBytes(2).toString("hex")}`;
7775
+ const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
7686
7776
  const shardDir = await this.ensureShardDir(id);
7687
- const file = path4.join(shardDir, `${id}.jsonl`);
7777
+ const file = path4.join(shardDir, `${path4.basename(id)}.jsonl`);
7688
7778
  let handle;
7689
7779
  try {
7690
7780
  handle = await fsp6.open(file, "a", 384);
@@ -7698,7 +7788,8 @@ var DefaultSessionStore = class {
7698
7788
  return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
7699
7789
  dir: shardDir,
7700
7790
  filePath: file,
7701
- secretScrubber: this.secretScrubber
7791
+ secretScrubber: this.secretScrubber,
7792
+ onClose: (s) => this.appendToIndex(s)
7702
7793
  });
7703
7794
  } catch (err) {
7704
7795
  await handle.close().catch(() => {
@@ -7729,7 +7820,7 @@ var DefaultSessionStore = class {
7729
7820
  provider: data.metadata.provider
7730
7821
  },
7731
7822
  this.events,
7732
- { 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) }
7733
7824
  );
7734
7825
  return { writer, data };
7735
7826
  } catch (err) {
@@ -7759,6 +7850,15 @@ var DefaultSessionStore = class {
7759
7850
  async list(limit = 20) {
7760
7851
  try {
7761
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
+ }
7762
7862
  const ids = await this.collectSessionIds(this.dir);
7763
7863
  const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
7764
7864
  const out = sessions.filter((s) => s !== null);
@@ -7772,16 +7872,121 @@ var DefaultSessionStore = class {
7772
7872
  return [];
7773
7873
  }
7774
7874
  }
7775
- /** Recursively collect all session IDs from shard subdirectories. */
7776
- 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) {
7777
7972
  const ids = [];
7778
- 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
+ }
7779
7979
  for (const entry of entries) {
7780
- 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;
7781
7983
  if (entry.isDirectory()) {
7782
- 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));
7783
7986
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
7784
- 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);
7785
7990
  }
7786
7991
  }
7787
7992
  return ids;
@@ -7804,9 +8009,70 @@ var DefaultSessionStore = class {
7804
8009
  return summary;
7805
8010
  }
7806
8011
  }
7807
- async delete(id) {
7808
- 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);
7809
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;
7810
8076
  }
7811
8077
  async clearHistory(id) {
7812
8078
  await this.ensureShardDir(id);
@@ -7828,13 +8094,42 @@ var DefaultSessionStore = class {
7828
8094
  const data = await this.load(id);
7829
8095
  const firstUser = data.events.find((e) => e.type === "user_input");
7830
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
+ }
7831
8119
  return {
7832
8120
  id,
7833
8121
  title,
7834
8122
  startedAt: data.metadata.startedAt,
8123
+ endedAt: data.metadata.endedAt,
7835
8124
  model: data.metadata.model ?? "unknown",
7836
8125
  provider: data.metadata.provider ?? "unknown",
7837
- 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
7838
8133
  };
7839
8134
  } catch {
7840
8135
  return {
@@ -7933,9 +8228,10 @@ var FileSessionWriter = class {
7933
8228
  this.meta = meta;
7934
8229
  this.events = events;
7935
8230
  this.resumed = opts.resumed ?? false;
7936
- 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`) : "";
7937
8232
  this.filePath = opts.filePath ?? "";
7938
8233
  this.secretScrubber = opts.secretScrubber;
8234
+ this.onCloseCb = opts.onClose;
7939
8235
  this.summary = {
7940
8236
  id,
7941
8237
  title: "(empty session)",
@@ -7965,6 +8261,15 @@ var FileSessionWriter = class {
7965
8261
  appendFailCount = 0;
7966
8262
  lastAppendWarnAt = 0;
7967
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;
7968
8273
  /**
7969
8274
  * Scrub secrets out of conversation-turn events before they are observed
7970
8275
  * for the summary, written to the JSONL log, or surfaced on resume. Only
@@ -8042,8 +8347,22 @@ var FileSessionWriter = class {
8042
8347
  observeForSummary(event) {
8043
8348
  if (event.type === "tool_use") {
8044
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;
8045
8353
  } else if (event.type === "tool_result") {
8046
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";
8047
8366
  }
8048
8367
  if (event.type === "user_input" && this.summary.title === "(empty session)") {
8049
8368
  this.summary = { ...this.summary, title: userInputTitle(event.content) };
@@ -8054,18 +8373,35 @@ var FileSessionWriter = class {
8054
8373
  } else if (event.type === "session_end") {
8055
8374
  const total = event.usage.input + event.usage.output;
8056
8375
  if (total > 0) this.summary = { ...this.summary, tokenTotal: total };
8376
+ } else if (event.type === "in_flight_start") {
8377
+ this.iterationCount++;
8057
8378
  }
8058
8379
  }
8059
8380
  async close() {
8060
8381
  if (this.closing) return;
8061
8382
  this.closing = true;
8062
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
+ };
8063
8395
  if (this.manifestFile) {
8064
8396
  try {
8065
8397
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
8066
8398
  } catch {
8067
8399
  }
8068
8400
  }
8401
+ try {
8402
+ await this.onCloseCb?.(this.summary);
8403
+ } catch {
8404
+ }
8069
8405
  try {
8070
8406
  await this.handle.close();
8071
8407
  } catch {
@@ -8107,7 +8443,7 @@ var FileSessionWriter = class {
8107
8443
  let targetCheckpointLine = -1;
8108
8444
  let afterTarget = false;
8109
8445
  for (let i = 0; i < lines.length; i++) {
8110
- const line = lines[i];
8446
+ const line = expectDefined3(lines[i]);
8111
8447
  if (!line.trim()) continue;
8112
8448
  let event;
8113
8449
  try {
@@ -8273,7 +8609,10 @@ function attachAutoExtend(events, policy = {}) {
8273
8609
  if (kind === "timeout" || kind === "idle_timeout") {
8274
8610
  if (progress > lastTimeoutProgress) {
8275
8611
  lastTimeoutProgress = progress;
8276
- 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
+ );
8277
8616
  extend({ timeoutMs: next2 });
8278
8617
  } else {
8279
8618
  deny();
@@ -8287,7 +8626,7 @@ function attachAutoExtend(events, policy = {}) {
8287
8626
  }
8288
8627
  extendCounts.set(kind, count + 1);
8289
8628
  const field = FIELD_BY_KIND[kind];
8290
- const cap = ceiling[field];
8629
+ const cap = ceiling[field] ?? DEFAULT_CEILING[field];
8291
8630
  const next = Math.min(Math.ceil(limit * (1 + factor)), cap);
8292
8631
  extend({ [field]: next });
8293
8632
  })