@wrongstack/core 0.10.0 → 0.10.3

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 (42) hide show
  1. package/dist/{agent-bridge-DBBGP5O4.d.ts → agent-bridge-D_XcS2HL.d.ts} +3 -1
  2. package/dist/{agent-subagent-runner-KwufTWwn.d.ts → agent-subagent-runner-DuRtZmhl.d.ts} +13 -3
  3. package/dist/coordination/index.d.ts +8 -8
  4. package/dist/coordination/index.js +83 -21
  5. package/dist/coordination/index.js.map +1 -1
  6. package/dist/defaults/index.d.ts +11 -11
  7. package/dist/defaults/index.js +89 -23
  8. package/dist/defaults/index.js.map +1 -1
  9. package/dist/{events-Bw1K5E6K.d.ts → events-BrQiweXN.d.ts} +1 -1
  10. package/dist/execution/index.d.ts +6 -6
  11. package/dist/execution/index.js +32 -10
  12. package/dist/execution/index.js.map +1 -1
  13. package/dist/extension/index.d.ts +2 -2
  14. package/dist/{index-DgQWnZxP.d.ts → index-DAHEM_II.d.ts} +1 -1
  15. package/dist/{index-DIt5TIaF.d.ts → index-DnBHrz2y.d.ts} +2 -2
  16. package/dist/index.d.ts +17 -17
  17. package/dist/index.js +226 -101
  18. package/dist/index.js.map +1 -1
  19. package/dist/infrastructure/index.d.ts +2 -2
  20. package/dist/kernel/index.d.ts +3 -3
  21. package/dist/kernel/index.js.map +1 -1
  22. package/dist/{multi-agent-C1O8EFzb.d.ts → multi-agent-C8Z1i__e.d.ts} +2 -2
  23. package/dist/{multi-agent-coordinator-DoVSBFNx.d.ts → multi-agent-coordinator-CjG-FZKj.d.ts} +8 -3
  24. package/dist/{null-fleet-bus-T-s3JaOt.d.ts → null-fleet-bus-B3WdVY9i.d.ts} +28 -23
  25. package/dist/observability/index.d.ts +1 -1
  26. package/dist/{path-resolver-BfyK-I7w.d.ts → path-resolver-DumKAi0n.d.ts} +1 -1
  27. package/dist/{plan-templates-BWs6Je7j.d.ts → plan-templates-BUP4o-ds.d.ts} +2 -2
  28. package/dist/{provider-runner-BmPe2RuU.d.ts → provider-runner-Dlv8Fvw9.d.ts} +1 -1
  29. package/dist/sdd/index.d.ts +4 -4
  30. package/dist/sdd/index.js +30 -8
  31. package/dist/sdd/index.js.map +1 -1
  32. package/dist/security/index.js +3 -3
  33. package/dist/security/index.js.map +1 -1
  34. package/dist/{session-reader-CCSBfQmd.d.ts → session-reader-bfgsy2a0.d.ts} +7 -3
  35. package/dist/storage/index.d.ts +29 -10
  36. package/dist/storage/index.js +131 -65
  37. package/dist/storage/index.js.map +1 -1
  38. package/dist/{tool-executor-D7RxLtg4.d.ts → tool-executor-D87-_5Of.d.ts} +1 -1
  39. package/dist/types/index.d.ts +8 -8
  40. package/dist/types/index.js +25 -5
  41. package/dist/types/index.js.map +1 -1
  42. package/package.json +1 -1
@@ -30,8 +30,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
30
30
  }
31
31
  let mode;
32
32
  try {
33
- const stat4 = await fsp.stat(targetPath);
34
- mode = stat4.mode & 511;
33
+ const stat5 = await fsp.stat(targetPath);
34
+ mode = stat5.mode & 511;
35
35
  } catch {
36
36
  mode = opts.mode;
37
37
  }
@@ -297,8 +297,8 @@ var DefaultSessionStore = class {
297
297
  return JSON.parse(raw);
298
298
  } catch {
299
299
  const full = this.sessionPath(id, ".jsonl");
300
- const stat4 = await fsp.stat(full);
301
- const summary = await this.summarize(id, stat4.mtime.toISOString());
300
+ const stat5 = await fsp.stat(full);
301
+ const summary = await this.summarize(id, stat5.mtime.toISOString());
302
302
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
303
303
  console.warn(
304
304
  `[session-store] Failed to write manifest for "${id}":`,
@@ -1776,11 +1776,27 @@ var DefaultSessionReader = class {
1776
1776
  const data = await this.store.load(sessionId);
1777
1777
  for (const e of data.events) yield e;
1778
1778
  }
1779
- async search(q, sessionId) {
1779
+ async search(q, sessionId, sessionQuery) {
1780
1780
  const limit = q.limit ?? 100;
1781
1781
  const matcher = buildMatcher(q);
1782
1782
  const allowedTypes = q.types ? new Set(q.types) : null;
1783
- const ids = sessionId ? [sessionId] : (await this.store.list(1e3)).map((s) => s.id);
1783
+ let ids;
1784
+ if (sessionId) {
1785
+ ids = [sessionId];
1786
+ } else {
1787
+ const sessions = await this.store.list(1e3);
1788
+ const titleNeedle = sessionQuery?.titleContains?.toLowerCase();
1789
+ const filtered = sessions.filter((s) => {
1790
+ if (sessionQuery?.since && s.startedAt < sessionQuery.since) return false;
1791
+ if (sessionQuery?.until && s.startedAt > sessionQuery.until) return false;
1792
+ if (sessionQuery?.provider && s.provider !== sessionQuery.provider) return false;
1793
+ if (sessionQuery?.model && s.model !== sessionQuery.model) return false;
1794
+ if (sessionQuery?.minTokens !== void 0 && s.tokenTotal < sessionQuery.minTokens) return false;
1795
+ if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;
1796
+ return true;
1797
+ });
1798
+ ids = filtered.map((s) => s.id);
1799
+ }
1784
1800
  const hits = [];
1785
1801
  for (const id of ids) {
1786
1802
  let data;
@@ -2183,6 +2199,8 @@ var ReplayLogStore = class {
2183
2199
  writeChains = /* @__PURE__ */ new Map();
2184
2200
  /** Per-session hash → entry index, kept in memory after the first load. */
2185
2201
  cache = /* @__PURE__ */ new Map();
2202
+ /** Per-session entry count on disk, to detect when compaction is needed. */
2203
+ diskCount = /* @__PURE__ */ new Map();
2186
2204
  maxEntries;
2187
2205
  constructor(opts) {
2188
2206
  this.dir = opts.dir;
@@ -2205,17 +2223,30 @@ var ReplayLogStore = class {
2205
2223
  request: input.request,
2206
2224
  response: input.response
2207
2225
  };
2208
- let all = [...cache.values(), entry];
2209
- if (all.length > this.maxEntries) {
2210
- const evictCount = all.length - this.maxEntries;
2211
- all = all.slice(evictCount);
2212
- }
2213
- await this.writeAll(input.sessionId, all);
2214
- cache.clear();
2215
- for (const e of all) cache.set(e.hash, e);
2226
+ cache.set(hash, entry);
2227
+ const count = (this.diskCount.get(input.sessionId) ?? 0) + 1;
2228
+ if (count > this.maxEntries) {
2229
+ await this.compact(input.sessionId, cache);
2230
+ } else {
2231
+ await fsp.appendFile(this.filePath(input.sessionId), JSON.stringify(entry) + "\n", "utf8");
2232
+ this.diskCount.set(input.sessionId, count);
2233
+ }
2216
2234
  });
2217
2235
  return hash;
2218
2236
  }
2237
+ /**
2238
+ * Compact the replay log to keep only the most recent maxEntries.
2239
+ * Called when entry count exceeds the cap. Rewrites the entire file
2240
+ * but only happens O(n / maxEntries) times per session.
2241
+ */
2242
+ async compact(sessionId, cache) {
2243
+ const all = [...cache.values()];
2244
+ const keep = all.slice(-this.maxEntries);
2245
+ await this.writeAll(sessionId, keep);
2246
+ cache.clear();
2247
+ for (const e of keep) cache.set(e.hash, e);
2248
+ this.diskCount.set(sessionId, keep.length);
2249
+ }
2219
2250
  // ── Reads ───────────────────────────────────────────────────────────────
2220
2251
  /**
2221
2252
  * Look up an entry by hash. Returns `null` when the request has
@@ -2299,6 +2330,7 @@ var ReplayLogStore = class {
2299
2330
  cache = /* @__PURE__ */ new Map();
2300
2331
  for (const e of all) cache.set(e.hash, e);
2301
2332
  this.cache.set(sessionId, cache);
2333
+ this.diskCount.set(sessionId, all.length);
2302
2334
  return cache;
2303
2335
  }
2304
2336
  enqueue(sessionId, fn) {
@@ -2328,14 +2360,50 @@ var SessionRecovery = class {
2328
2360
  */
2329
2361
  async detectStale(sessionId) {
2330
2362
  const fp = this.filePath(sessionId);
2331
- let raw;
2363
+ const TAIL_SIZE = 8192;
2364
+ let stat5;
2332
2365
  try {
2333
- raw = await fsp.readFile(fp, "utf8");
2366
+ stat5 = await fsp.stat(fp);
2334
2367
  } catch (err) {
2335
2368
  if (err.code === "ENOENT") return null;
2336
2369
  return null;
2337
2370
  }
2338
- return this.parseForStale(sessionId, fp, raw);
2371
+ if (stat5.size === 0) return null;
2372
+ const position = Math.max(0, stat5.size - TAIL_SIZE);
2373
+ const buf = Buffer.alloc(TAIL_SIZE);
2374
+ let fh;
2375
+ try {
2376
+ fh = await fsp.open(fp, "r");
2377
+ const { bytesRead } = await fh.read(buf, 0, TAIL_SIZE, position);
2378
+ let eventCount = 0;
2379
+ const raw = buf.subarray(0, bytesRead).toString("utf8");
2380
+ for (const line of raw.split("\n")) {
2381
+ if (line.trim()) eventCount++;
2382
+ }
2383
+ const lines = raw.split("\n").filter((l) => l.trim());
2384
+ for (let i = lines.length - 1; i >= 0; i--) {
2385
+ try {
2386
+ const ev = JSON.parse(lines[i]);
2387
+ if (ev.type === "in_flight_start") {
2388
+ return {
2389
+ sessionId,
2390
+ path: fp,
2391
+ lastEventTs: ev.ts,
2392
+ context: ev.context,
2393
+ eventCount
2394
+ };
2395
+ }
2396
+ return null;
2397
+ } catch {
2398
+ continue;
2399
+ }
2400
+ }
2401
+ return null;
2402
+ } catch {
2403
+ return null;
2404
+ } finally {
2405
+ if (fh) await fh.close();
2406
+ }
2339
2407
  }
2340
2408
  /**
2341
2409
  * Generate a recovery plan for a session. The plan describes
@@ -2416,46 +2484,22 @@ var SessionRecovery = class {
2416
2484
  }
2417
2485
  return path11.join(this.dir, `${sessionId}.jsonl`);
2418
2486
  }
2419
- /**
2420
- * Stream-parse the last few lines of a JSONL log. We do NOT load
2421
- * the whole file into memory — for long-running sessions the log
2422
- * can be megabytes. Instead we read tail-ward and find the last
2423
- * `in_flight_start` / `in_flight_end` pair.
2424
- */
2425
- async parseForStale(sessionId, fp, raw) {
2426
- const lines = raw.split("\n");
2427
- let lastEvent = null;
2428
- let eventCount = 0;
2429
- for (const line of lines) {
2430
- if (!line.trim()) continue;
2431
- try {
2432
- const ev = JSON.parse(line);
2433
- lastEvent = ev;
2434
- eventCount++;
2435
- } catch {
2436
- }
2437
- }
2438
- if (!lastEvent) return null;
2439
- if (lastEvent.type === "in_flight_start") {
2440
- return {
2441
- sessionId,
2442
- path: fp,
2443
- lastEventTs: lastEvent.ts,
2444
- context: lastEvent.context,
2445
- eventCount
2446
- };
2447
- }
2448
- return null;
2449
- }
2450
2487
  };
2451
2488
  var GENESIS_PREV = "0".repeat(64);
2489
+ var DEFAULT_FSYNC_EVERY = 100;
2452
2490
  var ToolAuditLog = class {
2453
2491
  dir;
2454
2492
  /** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
2455
2493
  tailHash = /* @__PURE__ */ new Map();
2494
+ /** In-memory counter for entry indices — avoids re-reading the file on every write. */
2495
+ tailIndex = /* @__PURE__ */ new Map();
2496
+ /** Tracks writes since last fsync, per session. */
2497
+ unSyncedWrites = /* @__PURE__ */ new Map();
2456
2498
  writeChains = /* @__PURE__ */ new Map();
2499
+ fsyncEvery;
2457
2500
  constructor(opts) {
2458
2501
  this.dir = opts.dir;
2502
+ this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;
2459
2503
  }
2460
2504
  /**
2461
2505
  * Append a tool call/result pair to the chain. Returns the
@@ -2467,6 +2511,7 @@ var ToolAuditLog = class {
2467
2511
  let entry = null;
2468
2512
  await this.enqueue(input.sessionId, async () => {
2469
2513
  const prevHash = this.tailHash.get(input.sessionId) ?? GENESIS_PREV;
2514
+ const index = this.tailIndex.get(input.sessionId) ?? 0;
2470
2515
  const id = randomUUID();
2471
2516
  const ts = (/* @__PURE__ */ new Date()).toISOString();
2472
2517
  const content = {
@@ -2477,12 +2522,11 @@ var ToolAuditLog = class {
2477
2522
  toolUseId: input.toolUseId,
2478
2523
  input: input.input,
2479
2524
  output: input.output,
2480
- isError: input.isError
2525
+ isError: input.isError,
2526
+ index
2481
2527
  };
2482
2528
  const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
2483
2529
  entry = {
2484
- index: this.tailHash.has(input.sessionId) ? -1 : 0,
2485
- // placeholder; recomputed below
2486
2530
  id,
2487
2531
  ts,
2488
2532
  prevHash,
@@ -2491,15 +2535,12 @@ var ToolAuditLog = class {
2491
2535
  toolUseId: input.toolUseId,
2492
2536
  input: input.input,
2493
2537
  output: input.output,
2494
- isError: input.isError
2538
+ isError: input.isError,
2539
+ index
2495
2540
  };
2496
- const existing = await this.readAll(input.sessionId);
2497
- const index = existing.length;
2498
- const finalContent = { ...content, index };
2499
- const finalHash = createHash("sha256").update(stableStringify2(finalContent), "utf8").digest("hex");
2500
- entry = { ...entry, index, hash: finalHash };
2501
2541
  await this.appendLine(input.sessionId, entry);
2502
- this.tailHash.set(input.sessionId, finalHash);
2542
+ this.tailHash.set(input.sessionId, hash);
2543
+ this.tailIndex.set(input.sessionId, index + 1);
2503
2544
  });
2504
2545
  return entry;
2505
2546
  }
@@ -2581,9 +2622,34 @@ var ToolAuditLog = class {
2581
2622
  }
2582
2623
  async appendLine(sessionId, entry) {
2583
2624
  const fp = this.filePath(sessionId);
2584
- const existing = await this.readAll(sessionId);
2585
- const all = [...existing, entry];
2586
- await atomicWrite(fp, all.map((e) => JSON.stringify(e)).join("\n") + (all.length ? "\n" : ""));
2625
+ const line = JSON.stringify(entry) + "\n";
2626
+ await fsp.appendFile(fp, line, "utf8");
2627
+ const count = (this.unSyncedWrites.get(sessionId) ?? 0) + 1;
2628
+ this.unSyncedWrites.set(sessionId, count);
2629
+ if (this.fsyncEvery !== Infinity && count % this.fsyncEvery === 0) {
2630
+ await this.sync(sessionId, fp);
2631
+ }
2632
+ }
2633
+ /**
2634
+ * Explicitly sync the file to disk. Called automatically every
2635
+ * `fsyncEvery` writes, and available for callers who want to
2636
+ * force a sync before closing or during graceful shutdown.
2637
+ */
2638
+ async flush(sessionId) {
2639
+ await this.sync(sessionId, this.filePath(sessionId));
2640
+ }
2641
+ async sync(sessionId, fp) {
2642
+ try {
2643
+ const fh = await fsp.open(fp, "r+");
2644
+ try {
2645
+ await fh.sync();
2646
+ } finally {
2647
+ await fh.close();
2648
+ }
2649
+ } catch {
2650
+ } finally {
2651
+ this.unSyncedWrites.set(sessionId, 0);
2652
+ }
2587
2653
  }
2588
2654
  enqueue(sessionId, fn) {
2589
2655
  const prev = this.writeChains.get(sessionId) ?? Promise.resolve();
@@ -3787,8 +3853,8 @@ var CloudSync = class {
3787
3853
  const localPath = this.categoryToPath(cat);
3788
3854
  if (!localPath) continue;
3789
3855
  try {
3790
- const stat4 = await fsp.stat(localPath);
3791
- if (stat4.isDirectory()) {
3856
+ const stat5 = await fsp.stat(localPath);
3857
+ if (stat5.isDirectory()) {
3792
3858
  const files = await this.walkDir(localPath, localPath);
3793
3859
  for (const file of files) {
3794
3860
  const content = await fsp.readFile(file, "utf8");
@@ -3813,8 +3879,8 @@ var CloudSync = class {
3813
3879
  const localPath = this.categoryToPath(cat);
3814
3880
  if (!localPath) continue;
3815
3881
  try {
3816
- const stat4 = await fsp.stat(localPath);
3817
- if (stat4.isDirectory()) {
3882
+ const stat5 = await fsp.stat(localPath);
3883
+ if (stat5.isDirectory()) {
3818
3884
  const files = await this.walkDir(localPath, localPath);
3819
3885
  for (const file of files) {
3820
3886
  const content = await fsp.readFile(file);