@wrongstack/core 0.269.0 → 0.272.0

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 (76) hide show
  1. package/dist/{agent-bridge-PcHQl_UQ.d.ts → agent-bridge-jVSZiygR.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-DOLIwBRo.d.ts} +7 -7
  3. package/dist/{brain-BYcK__Ym.d.ts → brain-CdbbJWi3.d.ts} +71 -1
  4. package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
  5. package/dist/{config-C_ae2k86.d.ts → config-D2DGoGSQ.d.ts} +29 -2
  6. package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
  7. package/dist/coordination/index.d.ts +121 -17
  8. package/dist/coordination/index.js +738 -74
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +599 -86
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +23 -18
  14. package/dist/execution/index.js +136 -41
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +36 -6
  17. package/dist/execution/prompt-enhancer.js +35 -9
  18. package/dist/execution/prompt-enhancer.js.map +1 -1
  19. package/dist/extension/index.d.ts +6 -6
  20. package/dist/{global-mailbox-Bvrz1P3f.d.ts → global-mailbox-CQj_C9Dp.d.ts} +139 -3
  21. package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-ZXDjjR1y.d.ts} +9 -9
  22. package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CcJBd-g1.d.ts} +1 -1
  23. package/dist/hq/index.d.ts +93 -6
  24. package/dist/hq/index.js +619 -49
  25. package/dist/hq/index.js.map +1 -1
  26. package/dist/{index-whDfTANu.d.ts → index-2Lhk5v0o.d.ts} +2 -2
  27. package/dist/{index-CZQ6Pwbs.d.ts → index-BL7BAx0p.d.ts} +8 -8
  28. package/dist/{index-W4VJCzHa.d.ts → index-Qo4kTzgw.d.ts} +5 -5
  29. package/dist/index.d.ts +96 -56
  30. package/dist/index.js +1941 -352
  31. package/dist/index.js.map +1 -1
  32. package/dist/infrastructure/index.d.ts +6 -6
  33. package/dist/infrastructure/index.js +5 -3
  34. package/dist/infrastructure/index.js.map +1 -1
  35. package/dist/kernel/index.d.ts +9 -9
  36. package/dist/kernel/index.js.map +1 -1
  37. package/dist/{mcp-servers-DJdZiRcv.d.ts → mcp-servers-DS-YUXvF.d.ts} +3 -3
  38. package/dist/models/index.d.ts +5 -5
  39. package/dist/models/index.js +28 -5
  40. package/dist/models/index.js.map +1 -1
  41. package/dist/{models-registry-C3a-2-Yd.d.ts → models-registry-DP6pGHet.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-BvbdNQ14.d.ts} +1 -1
  43. package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-BxTfXBKo.d.ts} +6 -6
  44. package/dist/observability/index.d.ts +2 -2
  45. package/dist/{parallel-eternal-engine-D9y5Pkcc.d.ts → parallel-eternal-engine-Cf-GTegR.d.ts} +9 -9
  46. package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DztfnFcv.d.ts} +3 -3
  47. package/dist/{permission-CvYQNUqZ.d.ts → permission-CC7XFYWG.d.ts} +1 -1
  48. package/dist/{permission-policy-D5Ss8j4B.d.ts → permission-policy-cYR4RJmw.d.ts} +2 -2
  49. package/dist/{pipeline-l_zzFRh3.d.ts → pipeline-sNIkhXeB.d.ts} +2 -2
  50. package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-DYiKFmEb.d.ts} +11 -5
  51. package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-dYAbTs_i.d.ts} +3 -3
  52. package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-Dw8x0F7u.d.ts} +3 -3
  53. package/dist/{retry-policy-CtFhfwa8.d.ts → retry-policy-BV7nzeAd.d.ts} +1 -1
  54. package/dist/sdd/index.d.ts +8 -8
  55. package/dist/sdd/index.js +2 -0
  56. package/dist/sdd/index.js.map +1 -1
  57. package/dist/{secret-vault-BLsVmTIK.d.ts → secret-vault-eMBKfheR.d.ts} +9 -1
  58. package/dist/security/index.d.ts +5 -5
  59. package/dist/security/index.js +137 -10
  60. package/dist/security/index.js.map +1 -1
  61. package/dist/{selector-CXl2_y9W.d.ts → selector-C4ORTOid.d.ts} +1 -1
  62. package/dist/{session-event-bridge-Ccud20CC.d.ts → session-event-bridge-CeNpUL9w.d.ts} +1 -1
  63. package/dist/{session-reader-ZeXQmsmE.d.ts → session-reader-BepLSnGL.d.ts} +1 -1
  64. package/dist/storage/index.d.ts +45 -13
  65. package/dist/storage/index.js +374 -113
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +2 -2
  68. package/dist/tools/index.js +9 -2
  69. package/dist/tools/index.js.map +1 -1
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +202 -41
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/index.d.ts +17 -4
  74. package/dist/utils/index.js +48 -9
  75. package/dist/utils/index.js.map +1 -1
  76. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  import * as crypto2 from 'crypto';
2
- import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
2
+ import { randomBytes, createCipheriv, createDecipheriv, randomUUID, scryptSync, createHash } from 'crypto';
3
3
  import * as fsp2 from 'fs/promises';
4
4
  import * as path4 from 'path';
5
5
  import { isAbsolute, resolve } from 'path';
6
6
  import * as fs4 from 'fs';
7
+ import { createReadStream } from 'fs';
8
+ import { createInterface } from 'readline';
7
9
  import * as os from 'os';
8
10
  import { hostname } from 'os';
9
11
  import { execFile } from 'child_process';
@@ -706,6 +708,7 @@ function escapeRegex(s) {
706
708
  }
707
709
  var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
708
710
  var CACHE_MAX_SIZE = 2e3;
711
+ var NEVER_MATCH = /[^\s\S]/;
709
712
  function getCachedGlob(pattern) {
710
713
  const cached = COMPILED_GLOB_CACHE.get(pattern);
711
714
  if (cached) return cached;
@@ -715,7 +718,12 @@ function getCachedGlob(pattern) {
715
718
  COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
716
719
  }
717
720
  }
718
- const re = compileGlob(pattern);
721
+ let re;
722
+ try {
723
+ re = compileGlob(pattern);
724
+ } catch {
725
+ re = NEVER_MATCH;
726
+ }
719
727
  COMPILED_GLOB_CACHE.set(pattern, re);
720
728
  return re;
721
729
  }
@@ -1218,18 +1226,20 @@ var MODEL_FAMILY_RATIO = {
1218
1226
  deepseek: 3.5
1219
1227
  };
1220
1228
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
1229
+ var _estimateCacheOrder = [];
1221
1230
  var ESTIMATE_CACHE_MAX_SIZE = 5e4;
1222
1231
  function getCachedEstimate(key, compute) {
1223
1232
  const existing = ESTIMATE_CACHE.get(key);
1224
1233
  if (existing !== void 0) return existing;
1225
1234
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1226
- for (const k of ESTIMATE_CACHE.keys()) {
1227
- if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
1228
- ESTIMATE_CACHE.delete(k);
1235
+ while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
1236
+ const oldest = _estimateCacheOrder.shift();
1237
+ if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
1229
1238
  }
1230
1239
  }
1231
1240
  const estimate = compute(key);
1232
1241
  ESTIMATE_CACHE.set(key, estimate);
1242
+ _estimateCacheOrder.push(key);
1233
1243
  return estimate;
1234
1244
  }
1235
1245
  function estimateToolInputTokens(input) {
@@ -2073,8 +2083,6 @@ function resolveWstackPaths(opts) {
2073
2083
  projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
2074
2084
  };
2075
2085
  }
2076
-
2077
- // src/storage/session-store.ts
2078
2086
  function sanitizeModel(model) {
2079
2087
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
2080
2088
  }
@@ -2085,6 +2093,8 @@ function generateSessionId(startedAt, model) {
2085
2093
  const modelPart = model ? `_${sanitizeModel(model)}` : "";
2086
2094
  return `${date}/${time}Z${modelPart}_${suffix}`;
2087
2095
  }
2096
+
2097
+ // src/storage/session-store.ts
2088
2098
  var DefaultSessionStore = class _DefaultSessionStore {
2089
2099
  dir;
2090
2100
  events;
@@ -2102,6 +2112,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2102
2112
  _loadCache = /* @__PURE__ */ new Map();
2103
2113
  _indexCache = null;
2104
2114
  static LOAD_CACHE_MAX_ENTRIES = 50;
2115
+ static LIST_SCAN_CONCURRENCY = 32;
2105
2116
  constructor(opts) {
2106
2117
  this.dir = opts.dir;
2107
2118
  this.events = opts.events;
@@ -2324,15 +2335,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
2324
2335
  });
2325
2336
  return indexed.slice(0, limit);
2326
2337
  }
2327
- const ids = await this.collectSessionIds(this.dir);
2328
- const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
2329
- const out = sessions.filter((s) => s !== null);
2330
- out.sort((a, b) => {
2331
- if (a.startedAt < b.startedAt) return 1;
2332
- if (a.startedAt > b.startedAt) return -1;
2333
- return a.id.localeCompare(b.id);
2334
- });
2335
- return out.slice(0, limit);
2338
+ return await this.listFromDirectoryScan(limit);
2336
2339
  } catch {
2337
2340
  return [];
2338
2341
  }
@@ -2457,43 +2460,102 @@ var DefaultSessionStore = class _DefaultSessionStore {
2457
2460
  this._indexCache = null;
2458
2461
  return valid.length;
2459
2462
  }
2463
+ async listFromDirectoryScan(limit) {
2464
+ const refs = await this.collectSessionFiles(this.dir);
2465
+ const candidates = await mapWithConcurrency(
2466
+ refs,
2467
+ _DefaultSessionStore.LIST_SCAN_CONCURRENCY,
2468
+ async (ref) => {
2469
+ const manifest = await this.readSummaryManifest(ref.id);
2470
+ if (manifest) return { summary: manifest, needsBackfill: false };
2471
+ const summary = await this.summaryHeaderFor(ref);
2472
+ return summary ? { summary, needsBackfill: true } : null;
2473
+ }
2474
+ );
2475
+ const out = candidates.filter((s) => s !== null);
2476
+ out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
2477
+ const selected = out.slice(0, limit);
2478
+ const summaries = await mapWithConcurrency(
2479
+ selected,
2480
+ Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
2481
+ async (candidate) => {
2482
+ if (!candidate.needsBackfill) return candidate.summary;
2483
+ return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
2484
+ }
2485
+ );
2486
+ return summaries.filter((s) => s !== null);
2487
+ }
2488
+ async collectSessionFiles(dir, prefix = "", depth = 0) {
2489
+ let entries;
2490
+ try {
2491
+ entries = await fsp2.readdir(dir, { withFileTypes: true });
2492
+ } catch {
2493
+ return [];
2494
+ }
2495
+ const dirEntries = [];
2496
+ const files = [];
2497
+ for (const entry of entries) {
2498
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
2499
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
2500
+ continue;
2501
+ if (entry.isDirectory()) {
2502
+ dirEntries.push(entry);
2503
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
2504
+ if (entry.name === "_index.jsonl") continue;
2505
+ const base = entry.name.replace(/\.jsonl$/, "");
2506
+ const id = prefix ? `${prefix}/${base}` : base;
2507
+ files.push({ id, filePath: path4.join(dir, entry.name) });
2508
+ }
2509
+ }
2510
+ const childFileArrays = await Promise.all(
2511
+ dirEntries.map((entry) => {
2512
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
2513
+ return this.collectSessionFiles(path4.join(dir, entry.name), childPrefix, depth + 1);
2514
+ })
2515
+ );
2516
+ return [...childFileArrays.flat(), ...files];
2517
+ }
2460
2518
  /** Recursively collect session IDs from date-shard subdirectories.
2461
2519
  * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
2462
2520
  * Skips `.jsonl`/`.summary.json` root files, dot-files, and
2463
2521
  * sub-directories that belong to fleet/subagent sessions. */
2464
2522
  async collectSessionIds(dir, prefix = "", depth = 0) {
2465
- const ids = [];
2466
2523
  let entries;
2467
2524
  try {
2468
2525
  entries = await fsp2.readdir(dir, { withFileTypes: true });
2469
2526
  } catch {
2470
- return ids;
2527
+ return [];
2471
2528
  }
2529
+ const dirEntries = [];
2530
+ const fileIds = [];
2472
2531
  for (const entry of entries) {
2473
2532
  if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
2474
2533
  if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
2475
2534
  continue;
2476
2535
  if (entry.isDirectory()) {
2477
- const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
2478
- ids.push(...await this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1));
2536
+ dirEntries.push(entry);
2479
2537
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
2480
2538
  if (entry.name === "_index.jsonl") continue;
2481
2539
  const base = entry.name.replace(/\.jsonl$/, "");
2482
- ids.push(prefix ? `${prefix}/${base}` : base);
2540
+ fileIds.push(prefix ? `${prefix}/${base}` : base);
2483
2541
  }
2484
2542
  }
2485
- return ids;
2543
+ const childIdArrays = await Promise.all(
2544
+ dirEntries.map((entry) => {
2545
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
2546
+ return this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1);
2547
+ })
2548
+ );
2549
+ return [...childIdArrays.flat(), ...fileIds];
2486
2550
  }
2487
2551
  async summaryFor(id) {
2488
2552
  const manifest = this.sessionPath(id, ".summary.json");
2489
2553
  const t0 = Date.now();
2490
2554
  let outcome = "success";
2491
2555
  let errorMsg;
2556
+ const fromManifest = await this.readSummaryManifest(id, t0);
2557
+ if (fromManifest) return fromManifest;
2492
2558
  try {
2493
- const raw = await fsp2.readFile(manifest, "utf8");
2494
- this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
2495
- return JSON.parse(raw);
2496
- } catch {
2497
2559
  const full = this.sessionPath(id, ".jsonl");
2498
2560
  const stat6 = await fsp2.stat(full);
2499
2561
  const summary = await this.summarize(id, stat6.mtime.toISOString());
@@ -2512,6 +2574,78 @@ var DefaultSessionStore = class _DefaultSessionStore {
2512
2574
  errorMsg = "summary fallback \u2014 manifest rebuilt";
2513
2575
  this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
2514
2576
  return summary;
2577
+ } catch (err) {
2578
+ outcome = "failure";
2579
+ errorMsg = toErrorMessage(err);
2580
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
2581
+ return {
2582
+ id,
2583
+ title: "(damaged)",
2584
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2585
+ model: "unknown",
2586
+ provider: "unknown",
2587
+ tokenTotal: 0
2588
+ };
2589
+ }
2590
+ }
2591
+ async readSummaryManifest(id, startTime = Date.now()) {
2592
+ const manifest = this.sessionPath(id, ".summary.json");
2593
+ try {
2594
+ const raw = await fsp2.readFile(manifest, "utf8");
2595
+ this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
2596
+ return JSON.parse(raw);
2597
+ } catch {
2598
+ return null;
2599
+ }
2600
+ }
2601
+ async summaryHeaderFor(ref) {
2602
+ let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
2603
+ try {
2604
+ const stat6 = await fsp2.stat(ref.filePath);
2605
+ if (!stat6.isFile()) {
2606
+ return {
2607
+ id: ref.id,
2608
+ title: "(damaged)",
2609
+ startedAt: stat6.mtime.toISOString(),
2610
+ model: "unknown",
2611
+ provider: "unknown",
2612
+ tokenTotal: 0
2613
+ };
2614
+ }
2615
+ mtime = stat6.mtime.toISOString();
2616
+ } catch {
2617
+ return null;
2618
+ }
2619
+ try {
2620
+ for await (const event of this.iterSessionEvents(ref.filePath)) {
2621
+ if (event.type === "session_start") {
2622
+ return {
2623
+ id: ref.id,
2624
+ title: "(empty session)",
2625
+ startedAt: event.ts,
2626
+ model: event.model ?? "unknown",
2627
+ provider: event.provider ?? "unknown",
2628
+ tokenTotal: 0
2629
+ };
2630
+ }
2631
+ }
2632
+ return {
2633
+ id: ref.id,
2634
+ title: "(empty session)",
2635
+ startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2636
+ model: "unknown",
2637
+ provider: "unknown",
2638
+ tokenTotal: 0
2639
+ };
2640
+ } catch {
2641
+ return {
2642
+ id: ref.id,
2643
+ title: "(damaged)",
2644
+ startedAt: mtime,
2645
+ model: "unknown",
2646
+ provider: "unknown",
2647
+ tokenTotal: 0
2648
+ };
2515
2649
  }
2516
2650
  }
2517
2651
  /**
@@ -2636,39 +2770,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
2636
2770
  }
2637
2771
  async summarize(id, mtime) {
2638
2772
  try {
2639
- const data = await this.load(id);
2640
- const firstUser = data.events.find((e) => e.type === "user_input");
2641
- const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
2773
+ const file = this.sessionPath(id, ".jsonl");
2774
+ let title = "(empty session)";
2775
+ let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
2776
+ let endedAt;
2777
+ let model = "unknown";
2778
+ let provider = "unknown";
2779
+ let tokenIn = 0;
2780
+ let tokenOut = 0;
2642
2781
  let iterationCount = 0;
2643
2782
  let toolCallCount = 0;
2644
2783
  let toolErrorCount = 0;
2645
2784
  let fileChangeCount = 0;
2646
2785
  const toolBreakdown = {};
2647
2786
  let outcome;
2648
- const lastEvent = data.events[data.events.length - 1];
2649
- for (const e of data.events) {
2650
- if (e.type === "in_flight_start") iterationCount++;
2787
+ let lastEventType;
2788
+ let hasError = false;
2789
+ let sawStart = false;
2790
+ for await (const e of this.iterSessionEvents(file)) {
2791
+ lastEventType = e.type;
2792
+ if (e.type === "session_start") {
2793
+ if (!sawStart) {
2794
+ sawStart = true;
2795
+ startedAt = e.ts;
2796
+ model = e.model ?? "unknown";
2797
+ provider = e.provider ?? "unknown";
2798
+ }
2799
+ } else if (e.type === "session_end") {
2800
+ endedAt = e.ts;
2801
+ } else if (e.type === "user_input") {
2802
+ if (title === "(empty session)") title = userInputTitle(e.content);
2803
+ } else if (e.type === "llm_response") {
2804
+ tokenIn += e.usage.input ?? 0;
2805
+ tokenOut += e.usage.output ?? 0;
2806
+ } else if (e.type === "in_flight_start") iterationCount++;
2651
2807
  else if (e.type === "tool_call_start") {
2652
2808
  toolCallCount++;
2653
2809
  toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
2654
2810
  } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
2655
2811
  else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
2812
+ else if (e.type === "error" || e.type === "provider_error") hasError = true;
2656
2813
  }
2657
- if (lastEvent?.type === "session_end") {
2814
+ if (lastEventType === "session_end") {
2658
2815
  outcome = "completed";
2659
- } else if (lastEvent?.type === "in_flight_start") {
2816
+ } else if (lastEventType === "in_flight_start") {
2660
2817
  outcome = "aborted";
2661
- } else if (data.events.some((e) => e.type === "error")) {
2818
+ } else if (hasError) {
2662
2819
  outcome = "error";
2663
2820
  }
2664
2821
  return {
2665
2822
  id,
2666
2823
  title,
2667
- startedAt: data.metadata.startedAt,
2668
- endedAt: data.metadata.endedAt,
2669
- model: data.metadata.model ?? "unknown",
2670
- provider: data.metadata.provider ?? "unknown",
2671
- tokenTotal: data.usage.input + data.usage.output,
2824
+ startedAt,
2825
+ endedAt,
2826
+ model,
2827
+ provider,
2828
+ tokenTotal: tokenIn + tokenOut,
2672
2829
  iterationCount: iterationCount > 0 ? iterationCount : void 0,
2673
2830
  toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
2674
2831
  toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
@@ -2687,6 +2844,25 @@ var DefaultSessionStore = class _DefaultSessionStore {
2687
2844
  };
2688
2845
  }
2689
2846
  }
2847
+ async *iterSessionEvents(file) {
2848
+ const stream = createReadStream(file, { encoding: "utf8" });
2849
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
2850
+ try {
2851
+ for await (const line of lines) {
2852
+ if (!line.trim()) continue;
2853
+ try {
2854
+ const parsed = JSON.parse(line);
2855
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
2856
+ yield parsed;
2857
+ }
2858
+ } catch {
2859
+ }
2860
+ }
2861
+ } finally {
2862
+ lines.close();
2863
+ stream.destroy();
2864
+ }
2865
+ }
2690
2866
  metaFromEvents(id, events) {
2691
2867
  const start = events.find((e) => e.type === "session_start");
2692
2868
  const end = events.findLast((e) => e.type === "session_end");
@@ -3350,6 +3526,27 @@ function userInputTitle(content) {
3350
3526
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
3351
3527
  return (text || "(non-text input)").slice(0, 60);
3352
3528
  }
3529
+ function compareSessionSummaries(a, b) {
3530
+ if (a.startedAt < b.startedAt) return 1;
3531
+ if (a.startedAt > b.startedAt) return -1;
3532
+ return a.id.localeCompare(b.id);
3533
+ }
3534
+ async function mapWithConcurrency(items, concurrency, fn) {
3535
+ if (items.length === 0) return [];
3536
+ const out = new Array(items.length);
3537
+ let next = 0;
3538
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
3539
+ const workers = Array.from({ length: workerCount }, async () => {
3540
+ for (; ; ) {
3541
+ const idx = next++;
3542
+ if (idx >= items.length) return;
3543
+ const item = items[idx];
3544
+ if (item !== void 0) out[idx] = await fn(item);
3545
+ }
3546
+ });
3547
+ await Promise.all(workers);
3548
+ return out;
3549
+ }
3353
3550
 
3354
3551
  // src/storage/queue-store.ts
3355
3552
  init_atomic_write();
@@ -4647,6 +4844,83 @@ var ALGO = "aes-256-gcm";
4647
4844
  var KEY_FILE_MODE = 384;
4648
4845
  var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
4649
4846
  var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
4847
+ var KEK_MAGIC = Buffer.from("WSKW", "ascii");
4848
+ var KEK_SALT_BYTES = 16;
4849
+ var WRAPPED_KEY_FILE_SIZE = KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
4850
+ var SCRYPT_N = 1 << 15;
4851
+ var SCRYPT_R = 8;
4852
+ var SCRYPT_P = 1;
4853
+ var SCRYPT_MAXMEM = 64 * 1024 * 1024;
4854
+ function getVaultPassphrase() {
4855
+ const v = process.env["WRONGSTACK_VAULT_PASSPHRASE"];
4856
+ return v && v.length > 0 ? v : void 0;
4857
+ }
4858
+ function isWrappedKeyFile(buf) {
4859
+ return buf.length === WRAPPED_KEY_FILE_SIZE && buf.subarray(0, KEK_MAGIC.length).equals(KEK_MAGIC);
4860
+ }
4861
+ function deriveKEK(passphrase, salt) {
4862
+ return scryptSync(passphrase, salt, KEY_BYTES, {
4863
+ N: SCRYPT_N,
4864
+ r: SCRYPT_R,
4865
+ p: SCRYPT_P,
4866
+ maxmem: SCRYPT_MAXMEM
4867
+ });
4868
+ }
4869
+ function wrapDataKey(dataKey, keyVersion, passphrase) {
4870
+ const salt = randomBytes(KEK_SALT_BYTES);
4871
+ const iv = randomBytes(IV_BYTES);
4872
+ const kek = deriveKEK(passphrase, salt);
4873
+ const cipher = createCipheriv(ALGO, kek, iv);
4874
+ const ct = Buffer.concat([cipher.update(dataKey), cipher.final()]);
4875
+ const tag = cipher.getAuthTag();
4876
+ const out = Buffer.alloc(WRAPPED_KEY_FILE_SIZE);
4877
+ let off = 0;
4878
+ KEK_MAGIC.copy(out, off);
4879
+ off += KEK_MAGIC.length;
4880
+ out[off] = keyVersion & 255;
4881
+ off += 1;
4882
+ salt.copy(out, off);
4883
+ off += KEK_SALT_BYTES;
4884
+ iv.copy(out, off);
4885
+ off += IV_BYTES;
4886
+ tag.copy(out, off);
4887
+ off += TAG_BYTES;
4888
+ ct.copy(out, off);
4889
+ return out;
4890
+ }
4891
+ function unwrapDataKey(buf, keyFile) {
4892
+ const passphrase = getVaultPassphrase();
4893
+ if (!passphrase) {
4894
+ throw new ConfigError({
4895
+ message: `SecretVault: key file ${keyFile} is passphrase-protected \u2014 set the WRONGSTACK_VAULT_PASSPHRASE environment variable to unlock it.`,
4896
+ code: ERROR_CODES.CONFIG_INVALID,
4897
+ context: { keyFile }
4898
+ });
4899
+ }
4900
+ let off = KEK_MAGIC.length;
4901
+ const version = buf[off];
4902
+ off += 1;
4903
+ const salt = buf.subarray(off, off + KEK_SALT_BYTES);
4904
+ off += KEK_SALT_BYTES;
4905
+ const iv = buf.subarray(off, off + IV_BYTES);
4906
+ off += IV_BYTES;
4907
+ const tag = buf.subarray(off, off + TAG_BYTES);
4908
+ off += TAG_BYTES;
4909
+ const ct = buf.subarray(off, off + KEY_BYTES);
4910
+ const kek = deriveKEK(passphrase, salt);
4911
+ const decipher = createDecipheriv(ALGO, kek, iv);
4912
+ decipher.setAuthTag(tag);
4913
+ try {
4914
+ const key = Buffer.concat([decipher.update(ct), decipher.final()]);
4915
+ return { key: Buffer.from(key), version };
4916
+ } catch {
4917
+ throw new ConfigError({
4918
+ message: `SecretVault: failed to unlock key file ${keyFile} \u2014 wrong WRONGSTACK_VAULT_PASSPHRASE (key unwrap authentication failed).`,
4919
+ code: ERROR_CODES.CONFIG_INVALID,
4920
+ context: { keyFile }
4921
+ });
4922
+ }
4923
+ }
4650
4924
  function checkKeyFilePermissions(keyFile) {
4651
4925
  if (process.platform === "win32") return;
4652
4926
  try {
@@ -4739,25 +5013,56 @@ var DefaultSecretVault = class {
4739
5013
  const oldVersion = this._keyVersion;
4740
5014
  const newKey = randomBytes(KEY_BYTES);
4741
5015
  const newVersion = oldVersion + 1;
4742
- const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
4743
- KEY_FILE_MAGIC.copy(keyFileBuf, 0);
4744
- keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
4745
- newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
4746
5016
  fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
4747
- fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
5017
+ const passphrase = getVaultPassphrase();
5018
+ if (passphrase) {
5019
+ fs4.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
5020
+ } else {
5021
+ const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
5022
+ KEY_FILE_MAGIC.copy(keyFileBuf, 0);
5023
+ keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
5024
+ newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
5025
+ fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
5026
+ }
4748
5027
  checkKeyFilePermissions(this.keyFile);
4749
5028
  this.key = newKey;
4750
5029
  this._keyVersion = newVersion;
4751
5030
  return { oldVersion, newVersion };
4752
5031
  }
5032
+ /**
5033
+ * If WRONGSTACK_VAULT_PASSPHRASE is set but the key on disk is still stored
5034
+ * unwrapped (legacy v1 / versioned v2), re-write it in passphrase-wrapped (v3)
5035
+ * form. The data key is preserved, so all existing ciphertext keeps
5036
+ * decrypting. Best-effort: a write failure leaves the working unwrapped file
5037
+ * in place and is not fatal to load.
5038
+ */
5039
+ migrateToWrappedIfPassphrase() {
5040
+ const passphrase = getVaultPassphrase();
5041
+ if (!passphrase || !this.key) return;
5042
+ try {
5043
+ fs4.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
5044
+ mode: 384
5045
+ });
5046
+ checkKeyFilePermissions(this.keyFile);
5047
+ } catch {
5048
+ }
5049
+ }
4753
5050
  loadOrCreateKey() {
4754
5051
  if (this.key) return this.key;
4755
5052
  try {
4756
5053
  const buf = fs4.readFileSync(this.keyFile);
5054
+ if (isWrappedKeyFile(buf)) {
5055
+ const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
5056
+ this.key = key2;
5057
+ this._keyVersion = version;
5058
+ checkKeyFilePermissions(this.keyFile);
5059
+ return this.key;
5060
+ }
4757
5061
  if (buf.length === KEY_BYTES) {
4758
5062
  this.key = buf;
4759
5063
  this._keyVersion = 1;
4760
5064
  checkKeyFilePermissions(this.keyFile);
5065
+ this.migrateToWrappedIfPassphrase();
4761
5066
  return this.key;
4762
5067
  }
4763
5068
  if (buf.length === VERSIONED_KEY_FILE_SIZE) {
@@ -4781,6 +5086,7 @@ var DefaultSecretVault = class {
4781
5086
  this.key = Buffer.from(key2);
4782
5087
  this._keyVersion = version;
4783
5088
  checkKeyFilePermissions(this.keyFile);
5089
+ this.migrateToWrappedIfPassphrase();
4784
5090
  return this.key;
4785
5091
  }
4786
5092
  throw new ConfigError({
@@ -4793,11 +5099,20 @@ var DefaultSecretVault = class {
4793
5099
  }
4794
5100
  fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
4795
5101
  const key = randomBytes(KEY_BYTES);
5102
+ const passphrase = getVaultPassphrase();
5103
+ const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
4796
5104
  try {
4797
- fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
5105
+ fs4.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
4798
5106
  } catch (err) {
4799
5107
  if (err.code !== "EEXIST") throw err;
4800
5108
  const buf = fs4.readFileSync(this.keyFile);
5109
+ if (isWrappedKeyFile(buf)) {
5110
+ const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
5111
+ this.key = winnerKey;
5112
+ this._keyVersion = version;
5113
+ checkKeyFilePermissions(this.keyFile);
5114
+ return this.key;
5115
+ }
4801
5116
  if (buf.length === KEY_BYTES) {
4802
5117
  this.key = buf;
4803
5118
  this._keyVersion = 1;
@@ -5141,6 +5456,42 @@ var defaultIndexing = {
5141
5456
  watchExternal: true,
5142
5457
  debounceMs: 400
5143
5458
  };
5459
+ var IN_PROJECT_FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
5460
+ "provider",
5461
+ "apiKey",
5462
+ "baseUrl",
5463
+ "providers",
5464
+ "mcpServers",
5465
+ "hooks",
5466
+ "plugins",
5467
+ "sync",
5468
+ "yolo",
5469
+ "extensions"
5470
+ ]);
5471
+ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => console.warn(msg)) {
5472
+ const stripped = [];
5473
+ const out = {};
5474
+ for (const [k, v] of Object.entries(inProject)) {
5475
+ if (IN_PROJECT_FORBIDDEN_KEYS.has(k)) {
5476
+ stripped.push(k);
5477
+ continue;
5478
+ }
5479
+ out[k] = v;
5480
+ }
5481
+ if (stripped.length > 0) {
5482
+ warn(
5483
+ JSON.stringify({
5484
+ level: "warn",
5485
+ event: "config.in_project_unsafe_fields_ignored",
5486
+ path: sourcePath,
5487
+ ignoredKeys: stripped,
5488
+ message: `Ignored ${stripped.length} unsafe field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. These can only be set in your personal ~/.wrongstack/config.json, not in a project-committed file.`,
5489
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5490
+ })
5491
+ );
5492
+ }
5493
+ return out;
5494
+ }
5144
5495
  function deepMerge2(base, patch) {
5145
5496
  const opts = { arrayMode: "concat-primitives" };
5146
5497
  if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
@@ -5176,7 +5527,7 @@ var DefaultConfigLoader = class {
5176
5527
  ]);
5177
5528
  cfg = deepMerge2(cfg, global);
5178
5529
  cfg = deepMerge2(cfg, local);
5179
- cfg = deepMerge2(cfg, inProject);
5530
+ cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
5180
5531
  for (const [key, fn] of Object.entries(ENV_MAP)) {
5181
5532
  const v = process.env[key];
5182
5533
  if (v) fn(cfg, v);
@@ -7057,6 +7408,9 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
7057
7408
  }
7058
7409
 
7059
7410
  // src/security/permission-policy.ts
7411
+ function matchesTrust(patterns, subject) {
7412
+ return patterns.includes(subject) || matchAny(patterns, subject);
7413
+ }
7060
7414
  var DefaultPermissionPolicy = class {
7061
7415
  policy = {};
7062
7416
  loaded = false;
@@ -7193,7 +7547,7 @@ var DefaultPermissionPolicy = class {
7193
7547
  this._evalCache.set(cacheKey, decision);
7194
7548
  return decision;
7195
7549
  }
7196
- if (entry?.deny && subject && matchAny(entry.deny, subject)) {
7550
+ if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
7197
7551
  const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
7198
7552
  this._evalCache.set(cacheKey, decision);
7199
7553
  return decision;
@@ -7203,7 +7557,7 @@ var DefaultPermissionPolicy = class {
7203
7557
  this._evalCache.set(cacheKey, decision);
7204
7558
  return decision;
7205
7559
  }
7206
- if (entry?.allow && subject && matchAny(entry.allow, subject)) {
7560
+ if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
7207
7561
  const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
7208
7562
  this._evalCache.set(cacheKey, decision);
7209
7563
  return decision;
@@ -7927,6 +8281,16 @@ function handleMessageStop(state, ev) {
7927
8281
  async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
7928
8282
  const state = createStreamingState(req.model);
7929
8283
  logger.debug("Stream started", { providerId: provider.id, model: req.model });
8284
+ const TEXT_BATCH_SIZE = 4;
8285
+ let pendingText = "";
8286
+ let pendingCount = 0;
8287
+ const flushText = () => {
8288
+ if (pendingCount > 0) {
8289
+ events.emit("provider.text_delta", { ctx, text: pendingText });
8290
+ pendingText = "";
8291
+ pendingCount = 0;
8292
+ }
8293
+ };
7930
8294
  const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
7931
8295
  try {
7932
8296
  for (; ; ) {
@@ -7946,9 +8310,12 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
7946
8310
  break;
7947
8311
  case "text_delta":
7948
8312
  handleTextDelta(state, ev.text);
7949
- events.emit("provider.text_delta", { ctx, text: ev.text });
8313
+ pendingText += ev.text;
8314
+ pendingCount++;
8315
+ if (pendingCount >= TEXT_BATCH_SIZE) flushText();
7950
8316
  break;
7951
8317
  case "tool_use_start": {
8318
+ flushText();
7952
8319
  const idVal = ev.id;
7953
8320
  const nameVal = ev.name;
7954
8321
  handleToolUseStart(state, { id: idVal, name: nameVal });
@@ -7960,6 +8327,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
7960
8327
  handleToolUseInputDelta(state, ev);
7961
8328
  break;
7962
8329
  case "tool_use_stop": {
8330
+ flushText();
7963
8331
  const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
7964
8332
  handleToolUseStop(state, ev);
7965
8333
  events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
@@ -7969,6 +8337,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
7969
8337
  handleThinkingStart(state, ev);
7970
8338
  break;
7971
8339
  case "thinking_delta":
8340
+ flushText();
7972
8341
  handleThinkingDelta(state, ev.text);
7973
8342
  events.emit("provider.thinking_delta", { ctx, text: ev.text });
7974
8343
  break;
@@ -8000,6 +8369,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
8000
8369
  eventType: String(evAny.type),
8001
8370
  errorMessage: errMsg
8002
8371
  });
8372
+ flushText();
8003
8373
  events.emit("provider.stream_error", {
8004
8374
  ctx,
8005
8375
  eventType: String(evAny.type),
@@ -8010,6 +8380,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
8010
8380
  } catch (err) {
8011
8381
  if (signal.aborted) {
8012
8382
  state.stopReason = "end_turn";
8383
+ flushText();
8013
8384
  logger.debug("Stream aborted \u2014 returning partial state", {
8014
8385
  providerId: provider.id,
8015
8386
  model: req.model,
@@ -8038,6 +8409,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
8038
8409
  } catch {
8039
8410
  }
8040
8411
  }
8412
+ flushText();
8041
8413
  logger.debug("Stream completed", {
8042
8414
  providerId: provider.id,
8043
8415
  model: req.model,
@@ -8224,17 +8596,9 @@ function findPreserveStart(messages, preserveK) {
8224
8596
  const prev = messages[preserveStart - 1];
8225
8597
  if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
8226
8598
  if (typeof first.content === "string" || typeof prev.content === "string") break;
8227
- const resultIds = /* @__PURE__ */ new Set();
8228
- for (const block of first.content) {
8229
- pairRepairInnerIterations++;
8230
- if (block.type === "tool_result") resultIds.add(block.tool_use_id);
8231
- }
8232
- if (resultIds.size === 0) break;
8233
- const hasMatchingUse = prev.content.some((block) => {
8234
- pairRepairInnerIterations++;
8235
- return block.type === "tool_use" && resultIds.has(block.id);
8236
- });
8237
- if (!hasMatchingUse) break;
8599
+ const pairCheck = hasMatchingToolPair(first.content, prev.content);
8600
+ pairRepairInnerIterations += pairCheck.iterations;
8601
+ if (!pairCheck.matched) break;
8238
8602
  preserveStart--;
8239
8603
  }
8240
8604
  if (compactionDebugEnabled()) {
@@ -8253,9 +8617,34 @@ function findPreserveStart(messages, preserveK) {
8253
8617
  }
8254
8618
  return preserveStart;
8255
8619
  }
8620
+ function hasMatchingToolPair(resultContent, useContent) {
8621
+ let iterations = 0;
8622
+ let firstResultId;
8623
+ let resultIds;
8624
+ for (const block of resultContent) {
8625
+ iterations++;
8626
+ if (block.type !== "tool_result") continue;
8627
+ if (firstResultId === void 0) {
8628
+ firstResultId = block.tool_use_id;
8629
+ } else {
8630
+ resultIds ??= /* @__PURE__ */ new Set([firstResultId]);
8631
+ resultIds.add(block.tool_use_id);
8632
+ }
8633
+ }
8634
+ if (firstResultId === void 0) return { matched: false, iterations };
8635
+ for (const block of useContent) {
8636
+ iterations++;
8637
+ if (block.type !== "tool_use") continue;
8638
+ if (resultIds ? resultIds.has(block.id) : block.id === firstResultId) {
8639
+ return { matched: true, iterations };
8640
+ }
8641
+ }
8642
+ return { matched: false, iterations };
8643
+ }
8256
8644
  function eliseOldToolResults(messages, opts) {
8257
8645
  const preserveStart = findPreserveStart(messages, opts.preserveK);
8258
8646
  let hasOversized = false;
8647
+ let firstOversizedIndex = -1;
8259
8648
  let fastPathIterations = 0;
8260
8649
  let fastPathInnerIterations = 0;
8261
8650
  for (let i = 0; i < preserveStart && !hasOversized; i++) {
@@ -8267,6 +8656,7 @@ function eliseOldToolResults(messages, opts) {
8267
8656
  const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
8268
8657
  if (oversized) {
8269
8658
  hasOversized = true;
8659
+ firstOversizedIndex = i;
8270
8660
  break;
8271
8661
  }
8272
8662
  }
@@ -8289,26 +8679,29 @@ function eliseOldToolResults(messages, opts) {
8289
8679
  let changed = false;
8290
8680
  let fullPassIterations = 0;
8291
8681
  let fullPassInnerIterations = 0;
8292
- const next = new Array(messages.length);
8293
- for (let i = 0; i < messages.length; i++) {
8682
+ let next;
8683
+ for (let i = firstOversizedIndex; i < preserveStart; i++) {
8294
8684
  fullPassIterations++;
8295
8685
  const msg = messages[i];
8296
- if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
8297
- next[i] = msg;
8298
- continue;
8299
- }
8686
+ if (!msg || !Array.isArray(msg.content)) continue;
8300
8687
  const original = msg.content;
8301
- const newContent = original.map((b) => {
8688
+ let newContent;
8689
+ for (let idx = 0; idx < original.length; idx++) {
8690
+ fullPassInnerIterations++;
8691
+ const b = original[idx];
8692
+ if (!b) continue;
8302
8693
  if (b.type === "tool_use") {
8303
8694
  const tokens2 = estimateToolInputTokens(b.input);
8304
- if (tokens2 < opts.eliseThreshold) return b;
8695
+ if (tokens2 < opts.eliseThreshold) continue;
8305
8696
  const elidedInput = summarizeToolUseInputElision(b, tokens2);
8306
8697
  saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
8307
- return { ...b, input: elidedInput };
8698
+ newContent ??= original.slice();
8699
+ newContent[idx] = { ...b, input: elidedInput };
8700
+ continue;
8308
8701
  }
8309
- if (b.type !== "tool_result") return b;
8702
+ if (b.type !== "tool_result") continue;
8310
8703
  const tokens = estimateToolResultTokens(b.content);
8311
- if (tokens < opts.eliseThreshold) return b;
8704
+ if (tokens < opts.eliseThreshold) continue;
8312
8705
  saved += tokens;
8313
8706
  const elided = {
8314
8707
  type: "tool_result",
@@ -8316,15 +8709,14 @@ function eliseOldToolResults(messages, opts) {
8316
8709
  content: summarizeToolResultElision(b, tokens),
8317
8710
  is_error: b.is_error
8318
8711
  };
8319
- return elided;
8320
- });
8321
- if (newContent.every((b, idx) => b === original[idx])) {
8322
- next[i] = msg;
8323
- } else {
8712
+ newContent ??= original.slice();
8713
+ newContent[idx] = elided;
8714
+ }
8715
+ if (newContent) {
8716
+ next ??= messages.slice();
8324
8717
  next[i] = { ...msg, content: newContent };
8325
8718
  changed = true;
8326
8719
  }
8327
- fullPassInnerIterations += original.length;
8328
8720
  if (compactionDebugEnabled()) {
8329
8721
  const ratio = fullPassInnerIterations / fullPassIterations;
8330
8722
  if (ratio > 10) {
@@ -8351,7 +8743,7 @@ function eliseOldToolResults(messages, opts) {
8351
8743
  tokensSaved: saved,
8352
8744
  changed
8353
8745
  });
8354
- return { messages: changed ? next : messages, saved, changed };
8746
+ return { messages: changed && next ? next : messages, saved, changed };
8355
8747
  }
8356
8748
  function summarizeToolUseInputElision(block, tokens) {
8357
8749
  const fields = {};
@@ -10659,7 +11051,14 @@ var EternalAutonomyEngine = class {
10659
11051
  try {
10660
11052
  const reloaded = await loadGoal(this.goalPath, this.opts.events);
10661
11053
  iterationIndex = reloaded?.iterations ?? 0;
10662
- } catch {
11054
+ } catch (err) {
11055
+ console.error(JSON.stringify({
11056
+ level: "warn",
11057
+ event: "autonomy.goal_reload_failed",
11058
+ message: toErrorMessage(err),
11059
+ context: { goalPath: this.goalPath },
11060
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11061
+ }));
10663
11062
  }
10664
11063
  this.opts.onIteration?.({
10665
11064
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -10894,7 +11293,14 @@ ${lastFew}` : "No prior iterations yet.",
10894
11293
  } finally {
10895
11294
  clearTimeout(timer);
10896
11295
  }
10897
- } catch {
11296
+ } catch (err) {
11297
+ console.error(JSON.stringify({
11298
+ level: "warn",
11299
+ event: "autonomy.brainstorm_failed",
11300
+ message: toErrorMessage(err),
11301
+ context: { goal: goal.goal.slice(0, 100) },
11302
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11303
+ }));
10898
11304
  return null;
10899
11305
  }
10900
11306
  }
@@ -14774,6 +15180,68 @@ Working rules:
14774
15180
  - When in doubt, flag as medium rather than ignoring potential issues`
14775
15181
  // Budgets are set by the orchestrator per task — see fleet.ts header.
14776
15182
  };
15183
+ var SHADOW_AGENT = {
15184
+ id: "shadow-agent",
15185
+ name: "Shadow",
15186
+ role: "shadow-agent",
15187
+ prompt: `You are the Shadow Agent \u2014 a silent background monitor for the WrongStack fleet.
15188
+
15189
+ Your job is to observe, detect anomalies, and be ready to intervene \u2014 but only when commanded.
15190
+
15191
+ ## Core Responsibilities
15192
+
15193
+ 1. **Fleet Monitoring** (every 30s)
15194
+ - Call \`fleet_status\` + \`fleet_health\` on each heartbeat
15195
+ - Track what each agent is doing (task descriptions)
15196
+ - Detect stuck agents (>5min no events), idle agents, crashed agents
15197
+
15198
+ 2. **FleetBus Subscription**
15199
+ - Subscribe to \`subagent.*\` events to track lifecycle
15200
+ - Subscribe to \`tool.executed\` to monitor activity
15201
+ - Track agent joins (subagent.started) and leaves (subagent.stopped)
15202
+
15203
+ 3. **Mailbox Surveillance**
15204
+ - Monitor for \`control\` type messages starting with "hoop"
15205
+ - Detect orphan assigns (assign without result within 5min)
15206
+ - Cross-session awareness via shared project mailbox
15207
+
15208
+ 4. **Spike Detection**
15209
+ - Track task duration per agent
15210
+ - Flag agents that spawn and die within <5 seconds
15211
+ - Log spike events with reason (completed/error/killed/timeout)
15212
+
15213
+ 5. **Intervention Commands**
15214
+ Parse these from mailbox control messages:
15215
+ - \`hoop <agentId>\` \u2014 terminate specific agent
15216
+ - \`hoop all\` \u2014 terminate all running agents
15217
+ - \`shadow status\` \u2014 report current fleet snapshot
15218
+ - \`shadow mute\` \u2014 pause heartbeat monitoring
15219
+ - \`shadow resume\` \u2014 resume heartbeat monitoring
15220
+ - \`shadow interval <ms>\` \u2014 change heartbeat interval
15221
+ - \`shadow model <model-id>\` \u2014 change analysis model
15222
+
15223
+ ## Operating Rules
15224
+
15225
+ - **Silent by default**: Use DEBUG level logging unless anomaly detected
15226
+ - **Deterministic**: Same state always produces same actions \u2014 no randomness
15227
+ - **Report on anomaly**: When anomaly detected, use \`mail_send\` to broadcast warning
15228
+ - **Never auto-intervene**: Always report unless explicitly commanded
15229
+ - **Minimal footprint**: Small state, efficient snapshots
15230
+
15231
+ ## Startup Sequence
15232
+
15233
+ 1. Send broadcast: \`shadow:started { intervalMs, model, startTime }\`
15234
+ 2. Subscribe to FleetBus for all relevant events
15235
+ 3. Schedule heartbeat cron job at configured interval
15236
+ 4. Wait for commands or anomalies
15237
+
15238
+ ## Shutdown Sequence
15239
+
15240
+ 1. Cancel all cron jobs (\`cron_cancel\`)
15241
+ 2. Send broadcast: \`shadow:stopped { reason, finalState }\`
15242
+ 3. Clean up FleetBus subscriptions`
15243
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
15244
+ };
14777
15245
  var CRITIC_AGENT = {
14778
15246
  id: "critic",
14779
15247
  name: "Critic",
@@ -14814,6 +15282,7 @@ var FLEET_ROSTER = {
14814
15282
  "refactor-planner": REFACTOR_PLANNER_AGENT,
14815
15283
  "security-scanner": SECURITY_SCANNER_AGENT,
14816
15284
  "critic": CRITIC_AGENT,
15285
+ "shadow-agent": SHADOW_AGENT,
14817
15286
  ...Object.fromEntries(
14818
15287
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
14819
15288
  )
@@ -14825,6 +15294,8 @@ var FLEET_ROSTER_BUDGETS = {
14825
15294
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
14826
15295
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
14827
15296
  "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
15297
+ "shadow-agent": { timeoutMs: 24 * 60 * 60 * 1e3, maxIterations: 1e4, maxToolCalls: 5e3 },
15298
+ // Long-running background monitor
14828
15299
  ...Object.fromEntries(
14829
15300
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
14830
15301
  )
@@ -16032,7 +16503,14 @@ ${personaLine}Task: ${task}
16032
16503
  subagentIds.push(subagentId);
16033
16504
  taskIds.push(taskId);
16034
16505
  await coordinator.assign(spec);
16035
- } catch {
16506
+ } catch (err) {
16507
+ console.error(JSON.stringify({
16508
+ level: "warn",
16509
+ event: "parallel_engine.spawn_failed",
16510
+ message: toErrorMessage(err),
16511
+ context: { slot: i, task, subagentId },
16512
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16513
+ }));
16036
16514
  }
16037
16515
  })()
16038
16516
  );
@@ -16057,7 +16535,14 @@ ${personaLine}Task: ${task}
16057
16535
  } finally {
16058
16536
  clearTimeout(timer);
16059
16537
  }
16060
- } catch {
16538
+ } catch (err) {
16539
+ console.error(JSON.stringify({
16540
+ level: "warn",
16541
+ event: "parallel_engine.brainstorm_results_failed",
16542
+ message: toErrorMessage(err),
16543
+ context: { slotCount, taskIds },
16544
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16545
+ }));
16061
16546
  results = coordinator.results().slice(-taskIds.length);
16062
16547
  }
16063
16548
  await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
@@ -16091,7 +16576,14 @@ ${personaLine}Task: ${task}
16091
16576
  if (file) tasks.push(`[git] inspect and fix: ${file}`);
16092
16577
  }
16093
16578
  }
16094
- } catch {
16579
+ } catch (err) {
16580
+ console.error(JSON.stringify({
16581
+ level: "warn",
16582
+ event: "parallel_engine.git_status_failed",
16583
+ message: toErrorMessage(err),
16584
+ context: { projectRoot: this.opts.projectRoot },
16585
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16586
+ }));
16095
16587
  }
16096
16588
  }
16097
16589
  if (tasks.length < this.slots) {
@@ -19705,6 +20197,7 @@ var NULL_FLEET_BUS = new FleetBus();
19705
20197
  // src/models/models-registry.ts
19706
20198
  init_atomic_write();
19707
20199
  var DEFAULT_URL = "https://models.dev/api.json";
20200
+ var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
19708
20201
  var DEFAULT_TTL_SECONDS = 24 * 3600;
19709
20202
  var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
19710
20203
  var FAMILY_BY_NPM = {
@@ -19750,7 +20243,7 @@ var DefaultModelsRegistry = class {
19750
20243
  overlayCacheFile;
19751
20244
  constructor(opts) {
19752
20245
  this.cacheFile = opts.cacheFile;
19753
- this.url = opts.url ?? DEFAULT_URL;
20246
+ this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
19754
20247
  this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
19755
20248
  this.fetchImpl = opts.fetchImpl ?? fetch;
19756
20249
  this.seed = opts.seed;
@@ -19795,6 +20288,10 @@ var DefaultModelsRegistry = class {
19795
20288
  const cached = await this.readCacheAt(this.cacheFile);
19796
20289
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
19797
20290
  this.fetchedAt = new Date(cached.fetchedAt);
20291
+ const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
20292
+ console.warn(
20293
+ `ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`
20294
+ );
19798
20295
  return cached.payload;
19799
20296
  }
19800
20297
  if (overlayAvailable) {
@@ -19880,7 +20377,13 @@ var DefaultModelsRegistry = class {
19880
20377
  return json;
19881
20378
  } catch {
19882
20379
  const cached = await this.readCacheAt(this.overlayCacheFile);
19883
- if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) return cached.payload;
20380
+ if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
20381
+ const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
20382
+ console.warn(
20383
+ `ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`
20384
+ );
20385
+ return cached.payload;
20386
+ }
19884
20387
  return void 0;
19885
20388
  }
19886
20389
  }
@@ -19977,6 +20480,16 @@ var DefaultModelsRegistry = class {
19977
20480
  return path4.resolve(this.cacheFile);
19978
20481
  }
19979
20482
  };
20483
+ function formatAge(seconds) {
20484
+ if (seconds < 60) return "<1m";
20485
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
20486
+ if (seconds < 86400) {
20487
+ const h = Math.floor(seconds / 3600);
20488
+ const m = Math.floor(seconds % 3600 / 60);
20489
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
20490
+ }
20491
+ return `${Math.floor(seconds / 86400)}d`;
20492
+ }
19980
20493
  function hasEntries(payload) {
19981
20494
  return payload !== void 0 && Object.keys(payload).length > 0;
19982
20495
  }